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:
co-authored by
Claude Opus 5
parent
40f07b0710
commit
83a5450866
@@ -39,7 +39,7 @@ import type { GameEvent } from './events.ts';
|
||||
import { areaAtSeat, areaOf, trainNeedingCars } from './apply.ts';
|
||||
import { legalActions } from './legal.ts';
|
||||
import type { CrewTray, DivisionNode, GameState, PlayerIndex, RollingStock, SeatIndex, TrayId } from './state.ts';
|
||||
import { coordKey, freshTurns, playerAtSeat, playerLeftOf, subdivisions, totalRevenue, turnOf } from './state.ts';
|
||||
import { coordKey, freshTurns, playerAtSeat, playerLeftOf, pooled, subdivisions, totalRevenue, turnOf } from './state.ts';
|
||||
|
||||
export type AdvanceResult = {
|
||||
events: GameEvent[];
|
||||
@@ -959,9 +959,11 @@ function collide(
|
||||
consist: [...tray.consist],
|
||||
});
|
||||
// Gap 2c — engines and cabooses return to the Division Yard, everything else to Classification.
|
||||
// `pooled` because a car reaching a yard is back in the common supply: the load's origin stamp
|
||||
// (state.ts) belongs to the load, not to the car that happened to be carrying it.
|
||||
for (const car of tray.consist) {
|
||||
if (car.type === 'caboose') s.yards.divisionYard.push(car);
|
||||
else s.yards.classificationYard.push(car);
|
||||
if (car.type === 'caboose') s.yards.divisionYard.push(pooled(car));
|
||||
else s.yards.classificationYard.push(pooled(car));
|
||||
}
|
||||
s.trays.delete(id);
|
||||
s.freeTrays.push(id);
|
||||
@@ -1031,9 +1033,10 @@ function retireTrain(
|
||||
side: Direction,
|
||||
events: GameEvent[],
|
||||
): void {
|
||||
// `pooled` — see `trainsDestroyed` above; a load's origin stamp does not survive the yard.
|
||||
for (const car of tray.consist) {
|
||||
if (car.type === 'caboose') s.yards.divisionYard.push(car);
|
||||
else s.yards.classificationYard.push(car);
|
||||
if (car.type === 'caboose') s.yards.divisionYard.push(pooled(car));
|
||||
else s.yards.classificationYard.push(pooled(car));
|
||||
}
|
||||
s.trays.delete(id);
|
||||
s.freeTrays.push(id);
|
||||
|
||||
Reference in New Issue
Block a user