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
+89 -24
View File
@@ -1,7 +1,7 @@
{
"seed": 927523,
"title": "12 Revenue · seed 927523",
"note": "12 Revenue over 5 Days · 2 train(s) on the timetable · 20 cards down · no collisions · played by developer",
"title": "11 Revenue · seed 927523",
"note": "11 Revenue over 5 Days · 2 train(s) on the timetable · 21 cards down · no collisions · played by developer",
"rules": {
"startingHand": "threeRandom",
"revenue": {
@@ -320,21 +320,24 @@
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray3"
},
{
"type": "porter.board",
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray3"
},
{
"type": "porter.detrain",
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray3"
},
{
"type": "loadUnload.end"
@@ -589,21 +592,24 @@
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray2"
},
{
"type": "porter.board",
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray2"
},
{
"type": "porter.detrain",
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray2"
},
{
"type": "loadUnload.end"
@@ -951,36 +957,38 @@
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray3"
},
{
"type": "porter.board",
"at": {
"row": 0,
"col": 0
}
},
{
"type": "porter.detrain",
"at": {
"row": 0,
"col": 0
}
},
"trayId": "tray3"
},
{
"type": "loadUnload.end"
},
{
"type": "localOps.choose",
"option": "freightAgent"
"option": "draw"
},
{
"type": "freightAgent.clearInbound",
"at": {
"row": 0,
"col": 0
"type": "draw.fromHomeOffice"
},
{
"type": "card.play",
"cardId": "c162",
"placement": {
"row": -2,
"col": 2
},
"index": 0
"variant": 1
},
{
"type": "draw.end"
},
{
"type": "loadUnload.end"
@@ -1179,6 +1187,12 @@
"carType": "coach",
"loaded": false
},
{
"type": "newTrain.placeCar",
"trayId": "tray3",
"carType": "coach",
"loaded": false
},
{
"type": "laborer.advanceLoad",
"at": {
@@ -1236,7 +1250,58 @@
"option": "switch"
},
{
"type": "switch.end"
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 1
},
"reverse": true
},
{
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 0
},
"reverse": false
},
{
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 1
},
"reverse": true
},
{
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 0
},
"reverse": false
},
{
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 1
},
"reverse": true
},
{
"type": "switch.move",
"trayId": "tray3",
"to": {
"row": 0,
"col": 0
},
"reverse": false
},
{
"type": "laborer.advanceLoad",