Skip to content

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

Town mills

Three mills along the river buy logs from the landing deck. Each mill bids per log by species. The bid falls as the mill’s log yard fills. A wagon carries the whole deck to one mill and returns with lumber.

Mills. Haul is the share of the load the carrier keeps. Trip is the round trip of the wagon. Cap is the yard capacity in logs. Rate, period and phase drive the yard model below.
MillDirectionMilesHaul shareTrip (min)Yard capRate (logs/hr)Period (hr)Phase
Harrison millwest34%11601090
Medimont milleast68%1.5120872.1
Springston millwest1013%224014134.2
Species multipliers. A key _ means every species not listed. A mill with no entry pays 1.0 for every species. West is downriver, east is upriver.
MillMultipliers by species key
Harrison mill–
Medimont millrc 1.3, l 1.15, lp 1.1, _ 0.8
Springston millwp 1.3, pp 1.12, df 1.05, cw 0.85, _ 0.95
yardFactor(mill, stock) = 1.3 - 0.9 * (stock / mill.cap)
multiplier = mill.mult[species] if present
else mill.mult["_"] if present
else 1.0
bid(mill, stock, species) = max( 20, round( value(species) * multiplier * yardFactor ) )

The unit is bd ft of lumber per log. An empty yard multiplies value by 1.3. A full yard multiplies it by 0.4. A half-full yard multiplies it by 0.85.

Each mill has a yard stock in logs. Stock is stored with the time it was last advanced, and is brought up to date by stepping forward in pieces of at most 10 minutes.

inflow(t) = rate * (1 + 0.9 * sin( 2*pi * (t / 3600000) / period + phase ))
for each step of dt (at most 10 minutes), h = dt / 1 hour:
stock += (inflow(t) - rate) * h
- 0.15 * rate * h * (stock / cap - 0.5)
stock = clamp(stock, 0, cap)

The sine term uses absolute hours since 1970, so every mill follows the same curve for every player. Over a full period the inflow averages the processing rate, and the second term pulls stock gently toward half full.

A mill’s first stock, when it is first read, is:

stock0 = round( cap * (0.35 + 0.4 * fractionalPart(phase * 7)) )

That is about 56 logs at Harrison, 76 at Medimont and 122 at Springston.

The offer for a deck is computed from the mill’s current stock.

gross = sum over species of deck[species] * bid(mill, stock, species)
net = round( gross * (1 - mill.haul * (0.5 if the player owns a teamster stable else 1)) )

The interface also shows a trend, which compares the yard factor two hours ahead (stock projected with the model above) to the factor now. A change of less than 3 percent reads as steady.

haul(mill):
requires: no wagon already out, and at least 1 log in the landing deck
offer = offer(mill, deck) (bids are locked now)
wagon = { leftAt: now, logs: count, deck, mill, net: offer.net }
deck = empty
settle(wagon), when now - leftAt >= mill.trip:
lumber += wagon.net
produced += wagon.net (counts toward the deed)
mill stock = min(cap, stock + wagon.logs)
ledger entry dated leftAt + trip

Only one wagon travels at a time. A wagon always carries the whole landing deck. Logs that arrive at the landing while the wagon is out wait for the next trip. The price is fixed at departure; yard changes during the trip do not affect it. A settled delivery raises the mill’s yard stock by the number of logs, up to the cap.

If the player is away, the wagon settles on the next load of the game, and the ledger entry carries the arrival time.

Inputs are the landing deck, the chosen mill, the clock and whether the player owns a teamster stable. Outputs are lumber, a changed yard stock, and an increase in produced. Time-based state: for each mill, S.mills[id] = { stock, at }, and the wagon record { leftAt, logs, deck, mill, net }.

The constant WAGON_TRIP (four minutes) exists in the script but is not read anywhere. Trip length comes from each mill.

In the prototype each player has a private copy of every mill yard that follows the same sine curve, so a delivery by one player changes nothing for another. In a shared game the yard is one number per mill, and it must belong to the server, because bids are the main price signal and every player’s deliveries move it. The server also has to own the wagon: departure time, locked bid and arrival. The closed-form inflow is a stand-in for other settlers; once real players deliver, it can be reduced or removed. Bids that are locked at departure mean a server must store the net payout with the trip, not recompute it on arrival.

Source: prototype/index.html · constants MILLS, WAGON_TRIP in rules.json · logic bidFor, yardFactor, inflow, projectStock, updateMills, millState, offer, haul, settleWagon