v0.4.3 fixed load/unload bugs. empty cars must be staged prior to loading. rolling stock is now conserved.

This commit is contained in:
Jesse
2026-08-14 08:33:32 -04:00
parent 393a669418
commit d1314066bd
13 changed files with 2258 additions and 3822 deletions
+70
View File
@@ -21,6 +21,76 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
## Unreleased
## 0.4.3 — 2026-08-14
### A load has to have somewhere to go
Jesse walked through how loading an industry is meant to work at the table, and the engine got four of
the five steps exactly right. It got the first one wrong, in both directions.
**You could stage cargo against no car.** §9.3 requires an empty car of the right type standing on the
industry's track before anything else happens — *"otherwise you are just dropping cargo onto the
tracks, pointless waste"*. The engine checked for that car only at the **last** step, when the load
came off WORK. So you could fetch cargo with the Freight Agent, walk it M→A→W across three Stages and
three Laborers, and only then find there was nowhere to put it. Worse than waste: from the moment the
load lands on M the industry track is **locked**, so no train can come in to spot the car you now need.
The only way out was a Freight Agent unjam — another whole turn, to undo a move the rules should never
have offered.
Now gated in three places, all counted rather than merely present:
- **`freightAgent.stockOutbound`** — no cargo is fetched unless a matching empty is spotted and not
already promised to a load already in the box or on the sign.
- **`laborer.startLoad`** — the same check again, because it is not redundant: coupling is mandatory,
so a crew running over that industry track *must* pick up the spotted empty, and the cargo you
staged an hour ago can be left with nothing.
- **`laborer.beginUnload`** — the mirror. The red Inbound box is where an inbound load lands, and it
was checked only at the final step too. Counted, because only the W box must be free to begin: once
a load moves W→A a second can start behind it, and every red box in play holds exactly one car.
**Strict type matching throughout** — a hopper load cannot be swapped onto a tank. And `startLoad` no
longer always takes `outboundBox[0]`: it starts the first load that has a car to land on, so a Power
Plant holding a hopper load and a tank load against one spotted tank works the tank instead of
reporting the whole facility blocked.
**Passengers are deliberately exempt and it is written down.** People can wait on a platform for a
train that has not arrived, so a Passenger Facility still stocks freely. Freight cannot: a crate on the
ground is not a shipment.
**Measured before building it, which is why it was worth building.** Requiring the car removes **78% of
the `stockOutbound` moves the menu offered** (704 → 157 across 200 games) — but **99.3% of the moves the
bot actually took survive** (138 of 139). The rule deletes offers a competent player would never have
used. Bot unchanged at 7.3 mean. On the unload side it removes 2.2% of offers, and the jam it prevents
was caught happening three times in 200 games.
### The Blocked panel says what to do, and in what order
The refusal codes never reach a player, because the menu simply stops offering the action — so with
78% of stocking moves gone the panel was the only thing that could explain it. It said *"green box
empty — nothing to load (needs a Freight Agent action)"*, which is step two told to someone who has not
done step one. It now says to bring a car in first, names the commodity, and distinguishes "MEN is
occupied" from "there is nothing here to load onto", which send you to fix quite different things.
### Rolling stock is exactly conserved
`TODO.md` recorded the inbound path minting ~1.3 cars a game and blocked tuning `ROLLING_STOCK_SUPPLY`
until it was settled. Re-audited: **exactly conserved, 100 games out of 100, range 0..0.** The
asymmetry had already been closed from the other end when `unloadBegan` and `passengersDetrained`
started taking their replacement empty out of the Division Yard instead of conjuring it — a load is a
car that moved, not a car that appeared, which is exactly the tabletop procedure Jesse described.
Both conjuring fallbacks now **throw** instead of minting, so the leak cannot come back quietly;
neither fired across the suite or the audit. `ROLLING_STOCK_SUPPLY` is unblocked for the rebalance.
(The original audit's arithmetic was off in the same way mine was on the first attempt: cars set out on
a card live in `card.standing` and are easy to leave out of the count, which makes a conserved game
look like a leaking one.)
### Replays
Two of five died on the rule change — they replayed 138/336 and 81/347, which on screen looks exactly
like a game that ended early. Re-recorded; three published, all verified to their last intent.
## 0.4.2 — 2026-08-13
### A completed run pays the whole table