Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Town and markets

The town of Harrison grows by settlers. Settlers create demand. Demand, split among sellers, sets what a player’s business earns. The player’s work is to find a market with few sellers and open a business in it.

settlers(t) = min( SETTLER_MAX, SETTLER_START + floor( (t - founded) / SETTLER_EVERY ) )
level(n) = the highest milestone whose "at" is <= n

The town is founded when the claim is created. The constants are SETTLER_START = 8, SETTLER_MAX = 60, and one new settler every 40 minutes.

Milestones. Hours is the real time after founding at which the settler count first reaches the milestone.
LevelNameSettlersHours after foundingMarkets opened
0Founding00Saw filing, Freight hauling
1A town of 15 settlers154.7Cedar shakes, Seedlings
2A real town of 303014.7General goods
3The steamboat stops here4524.7Room and board

The count reaches its cap of 60 about 34.7 hours after founding. When the level rises, the ledger records “Town milestone” and lists the markets that opened. Every level up is recorded, including levels skipped over while the player was away.

Markets. K is the capacity of one seller in units per hour. D is demand per settler per hour. P is the base price in bd ft per unit. Upkeep is bd ft per hour. Cogs is the share of each sale spent on goods. Comp is the number of simulated competitors at founding. Unlock is the milestone level.
MarketBusinessUnitCostUpkeepKDPCogsCompUnlockNeeds building
Saw filingFiling shopsaw6004540.253010%10–
Freight haulingTeamster stableload1,20010030.129025%20barn
Cedar shakesShake shedbundle9006060.35280%01–
SeedlingsTree nurserybundle8006030.16020%01–
General goodsGeneral storeorder2,50040100.62060%12–
Room and boardBoarding housenight3,0007080.253540%03cabin

Shake shed has no cost of goods share. It consumes redcedar logs instead: 0.25 of a log per bundle sold, taken first from the landing deck and then from the sawmill yard. Each market grants one perk to its owner.

Perks.
BusinessPerk
Filing shopYour own saws are filed free.
Teamster stableYour own hauling to town mills costs half.
Shake shedSplits redcedar logs from your landing or mill. No cedar, no shakes.
Tree nurseryYour own replanting is free.
General storeThe busiest trade in a growing town.
Boarding houseYour crew rests 25% faster.

The filing shop waives the saw filer’s fee, the stable halves hauling shares and Mercantile freight, the nursery waives replanting cost, and the boarding house multiplies crew rest by 0.75. The shake shed and the general store have no perk beyond their sales.

demand = settlers * d units per hour
sellers = competitors + (1 if the player owns the business)
ratio = demand / max( sellers * k, 0.5 * k )
price = round( p * clamp( ratio, 0.5, 1.8 ) ^ 0.7 )
yourSales = min( k, demand / sellers ) if you own it, else 0
then min( yourSales, cedarOnHand / 0.25 ) for the shake shed
net per hour = yourSales * price * (1 - cogs) - upkeep (upkeep only if you own it)
status = locked if town level < unlock
underserved if ratio > 1.3
balanced if ratio >= 0.8
crowded otherwise

Income is integrated over time. Over each interval, lumber += net * hours, and lumber never drops below zero, so upkeep cannot make it negative. The amount is also added to earned[market], which is allowed to go negative and is not floored.

When the player has no cedar, yourSales is zero and the shake shed pays only its upkeep.

Competitors are a count per market. They change in steps of at most 30 minutes, and only for markets the town has unlocked.

roll = hash01( floor(t / 30 minutes), len(id) * 7 + k ) a fixed pseudo-random number in [0,1)
h = step length in hours
if ratio > 1.25 and roll < 0.10 * h * 2: competitors += 1
else if ratio < 0.70 and competitors > 0 and roll > 1 - 0.12 * h * 2: competitors -= 1
hash01(a, b) = fractionalPart( sin( a * 12.9898 + b * 78.233 ) * 43758.5453 )

For a full 30-minute step, h is 0.5, so entry has probability 0.10 and exit 0.12. For a shorter step the probability shrinks in proportion. The roll depends only on the clock and the market, not on the player, so two players at the same moment see the same roll.

Implementation note: while the page stays open a competitor almost never enters or leaves. Changes show up when the player returns after being away, because the update then runs in full 30-minute steps. The competitor rate depends on how often the page runs the update.

requires: the market is unlocked (town level >= unlock)
the player does not already own it
the selected parcel is cleared, has no building, is not meadow, is not the landing
the required building exists, if the market names one
lumber >= cost
effect: the town is brought up to date, lumber -= cost,
parcel.building = "biz:" + id,
town.biz[id] = { r, c, since: now }

The placement check does not look for a field, so a business can be opened on a parcel that has one. There is no way to close or sell a business, and upkeep runs for as long as the claim exists. A business adds its cost to the appraisal.

Inputs are the clock, the founding time, the player’s businesses, the landing deck and sawmill yard (for cedar) and the selected parcel. Outputs are lumber over time, the earned figures (read by the Merchant board), competitor counts and ledger lines. State: S.town = { founded, at, comp, earned, biz, level }, where at is the last update time.

The prototype fakes a market economy from settler count: demand per settler, a hash standing in for other players’ decisions, and a count of competitors. In a shared game “sellers” should be the real businesses that exist, and “settlers” should be the real population or the server’s schedule of arrivals. A server must own the business registry (one record per player business), the pricing function, and one fixed update step for everyone, so that outcomes do not depend on how often a client polls. Milestones are the unlock schedule for the whole world, so they should be a world setting and not a per-player clock. If every player has a private founding date, as in the prototype, the milestone gates do not coordinate anyone.

Source: prototype/index.html · constants MARKETS, MILESTONES, SETTLER_EVERY, SETTLER_START, SETTLER_MAX in rules.json · logic settlers, townLevel, marketNow, updateTown, hash01, openBiz, takeCedar