v0.6.1 — five of six playtest bugs: one button per train, and a load that has to go somewhere

Gameplay testing on 0.4.9d returned six reports. Five are fixed; the sixth could not be
reproduced and is written up in TODO.md with the two questions that would pin it down.

TWO TRAINS AT ONE PLATFORM ANSWERED TO ONE BUTTON. `porter.board` and `porter.detrain`
carried no tray, so there was one button per platform however many trains stood at it and
the reducer filled the first empty coach on the A/D tracks. `check` and the reducer were not
even asking the same question: `check` skipped a train whose card refuses passenger work and
the reducer did not. Both intents now carry an optional `trayId`, one function resolves the
train and the coach for check/execute/reduce alike, `legal.ts` offers one candidate per train,
and the label names it.

A LOAD COULD BE MADE AND BROKEN WITHOUT GOING ANYWHERE. A Freight House could unload the
boxcar it had just loaded; a platform could detrain the passengers it had just boarded. Full
Revenue at both ends for a movement that never happened. Jesse's rule: a load made anywhere in
an Office Area may not be broken anywhere in that Office Area, ever — it has to be carried to
another district. The load carries the seat that made it (`RollingStock.origin`), stripped by
`pooled` at every yard push. Measured at -0.60 +/- 0.10 Revenue a game (t = -6.1) over 400
paired deals: 78 worse, 3 better, 319 unchanged — free Revenue coming off the board, not a nerf.

THE GROCER'S WAREHOUSE SHIPPED AND THE REFINERY RECEIVED. Both were `flow: 'both'` on the
reading that "Freight House" was a collective term for exactly those two, and therefore what
§9.3 described. The engine has dealt a Freight House CARD since before v0.4.9, so §9.3 names
it and the argument goes. The card set agrees: all three Refinery modifiers grant +1 outbound.
Refinery outbound-only, Grocer's inbound-only, Freight House the one two-way industry — which
leaves exactly the one same-district pairing the rule above refuses.

NOT REPRODUCED: cars left behind when backing up over them. Five layouts tried, including cars
spotted at an industry; every one couples the lot. Three are pinned in `apply.test.ts`. One way
to create such cars was closed anyway — `flyingSwitch` wrote its cut past `carsOn`.

Both published replays that had gone dead were re-recorded; a rules change retires a save, and
`harness.test.ts` is what catches it.

The same change ships as v0.4.9e on the 0.4.9 line, branched from the v0.4.9d commit — the engine
files these fixes touch are identical across the two lines, so the patch applied cleanly both ways.

Also carries the two "Queued 2026-08-22, from playing on StartOS" TODO items that were staged
before this work started (Games in Progress readability, and getting back into a game after
losing a browser). They are notes, and items 9-12 below them are numbered against them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nbvwWMef8CuEP6t5cgkTv
This commit is contained in:
Jesse.Markowitz
2026-08-21 23:54:24 -04:00
co-authored by Claude Opus 5
parent 40f07b0710
commit 83a5450866
25 changed files with 2853 additions and 1811 deletions
+32 -23
View File
@@ -231,42 +231,51 @@ export const INDUSTRY_PROFILES: readonly IndustryProfile[] = [
{ kind: 'freightHouse', name: 'Freight House', carTypes: ['boxcar'], flow: 'both', baseOut: 1, baseIn: 1, baseLoaders: 1, lockouts: ['grocersWarehouse'], copies: 6 },
{ kind: 'mineTipple', name: 'Mine Tipple', carTypes: ['hopper'], flow: 'outbound', baseOut: 1, baseIn: 0, baseLoaders: 1, lockouts: ['powerPlant'], copies: 6 },
/**
* BOTH DIRECTIONS, per the card reference — this was `outbound` and it contradicted the rules.
* OUTBOUND ONLY. A Refinery ships oil out and receives nothing; reported from playtesting and
* confirmed by Jesse (v0.4.9e): "only ships out tanks, does not receive anything".
*
* `card-reference.md`: "Oil Refinery | Tank car | Both | 3 | 2 | 2 | 4", and in prose — "'Freight
* House' is not a card. It is the collective term for a freight facility that loads *and* unloads
* — the Grocer's Warehouse and the Oil Refinery." §9.3's "Passenger Facilities and Freight Houses
* permit cars to move each direction" therefore names exactly these two, and the engine had both
* of them one-way.
* It was briefly `flow: 'both'`, on the reading that "'Freight House' is not a card — it is the
* collective term for a freight facility that loads *and* unloads, the Grocer's Warehouse and the
* Oil Refinery", which made §9.3's "Passenger Facilities and Freight Houses permit cars to move
* each direction" name exactly those two. That premise is dead: `glossary.md` and
* `rules-v0.2.md` corrected the Freight House to a card of its own, dealt like any other industry,
* so §9.3 names the Freight House and nothing else, and card-reference.md's "Both" column loses
* the only argument it had.
*
* The consequence was silent: `usableGrant` drops a Modifier's grant on a direction its host
* cannot use, so every +1 inbound beside a Refinery went nowhere.
*
* The base numbers stay at the engine's own scale (1 per direction it allows) rather than the card
* reference's 2/2 — every industry here is scaled down the same way, Mine Tipple included, and
* raising one of them alone would be a balance change rather than a correction. Flagged in TODO.
* The card set says the same thing on its own. All three Refinery modifiers — Pipelines, Oil
* Depot, Viscosity Breakers — grant `+1 outbound`; a two-way Refinery would be the one industry in
* the game with no way to raise the direction it is supposed to use half its capacity on.
* `StationMaster-Home-Deck-v0.4.5.md` prints it "Outbound, 1 out / 0 in".
*/
{ kind: 'refinery', name: 'Refinery', carTypes: ['tank'], flow: 'both', baseOut: 1, baseIn: 1, baseLoaders: 1, lockouts: ['powerPlant'], copies: 3 },
{ kind: 'refinery', name: 'Refinery', carTypes: ['tank'], flow: 'outbound', baseOut: 1, baseIn: 0, baseLoaders: 1, lockouts: ['powerPlant'], copies: 3 },
{ kind: 'powerPlant', name: 'Power Plant', carTypes: ['hopper', 'tank'], flow: 'inbound', baseOut: 0, baseIn: 1, baseLoaders: 1, lockouts: ['mineTipple', 'refinery'], copies: 6 },
{ kind: 'packingSheds', name: 'Packing Sheds', carTypes: ['reefer'], flow: 'outbound', baseOut: 1, baseIn: 0, baseLoaders: 1, lockouts: ['grocersWarehouse'], copies: 3 },
/**
* BOTH DIRECTIONS — see the Refinery above; "Grocer's Warehouse | Boxcar | Both | 2 | 2 | 2 | 3".
* INBOUND ONLY — the mirror of the Refinery above, and the same correction. Reported from
* playtesting and confirmed by Jesse (v0.4.9e): "Grocer's Warehouse should be receive only, does
* not ship anything out". `StationMaster-Home-Deck-v0.4.5.md` prints it "Inbound, 0 out / 1 in".
*
* Reported from play: "grocer's warehouse didn't get extra outbound slot for truck dock." It could
* not: the Truck Dock printed +1 outbound at the time and this was `flow: 'inbound'`, so the grant
* was dropped on a direction the facility did not have. The same trap still swallows an Ice House
* set beside a Grocer's that has been left one-way.
*
* `TODO.md` had previously recorded this as "checked, and there is no bug" on the reasoning that a
* Grocer's is inbound-only. That premise was the bug.
* THE ICE HOUSE IS THEREFORE A DEAD CARD BESIDE A GROCER'S, and that is the design, not an
* oversight: `usableGrant` drops a Modifier's grant on a direction its host cannot use, and the
* Home Deck sheet says so outright — "a bonus beside a facility that cannot use its direction is
* not usable", naming the Truck Dock's inbound bonus beside the outbound-only Packing Sheds as the
* other example. The Truck Dock (+1 inbound) and Local Small Groceries (+1 Laborer) are the two
* that do work here.
*/
{ kind: 'grocersWarehouse', name: "Grocer's Warehouse", carTypes: ['boxcar', 'reefer'], flow: 'both', baseOut: 1, baseIn: 1, baseLoaders: 1, lockouts: ['packingSheds', 'freightHouse'], copies: 3 },
{ kind: 'grocersWarehouse', name: "Grocer's Warehouse", carTypes: ['boxcar', 'reefer'], flow: 'inbound', baseOut: 0, baseIn: 1, baseLoaders: 1, lockouts: ['packingSheds', 'freightHouse'], copies: 3 },
];
/** Legacy alias; the engine still reads FREIGHT_PROFILES in places. */
export const FREIGHT_PROFILES = INDUSTRY_PROFILES;
/** §9.3 — the collective term for an industry that both loads and unloads. */
/**
* §9.3 — "Passenger Facilities and Freight Houses permit cars to move each direction".
*
* ONE CARD ANSWERS TO THIS NOW: the Freight House itself. It was briefly three, while the Refinery
* and the Grocer's Warehouse were also `both` on a reading of the term the glossary has since
* corrected — a Freight House is a card, not a collective noun. Kept as a predicate on `flow`
* rather than a comparison against the kind, because it is the DIRECTION §9.3 is talking about.
*/
export function isFreightHouse(p: IndustryProfile): boolean {
return p.flow === 'both';
}