v0.4.9 — X,Y coordinates, no-switching can still clear the mainline, Q3 corrected, a clearance bug, three sounds, and the box art
A playtest review of seed 58228926 (day 6), plus one long-standing display complaint and the first real audio beyond a placeholder. - Coordinate labels read X,Y everywhere shown to a player, not the internal Y,X storage order. Display-only. - "No switching" now means may not add or drop cars, not "never touch it" — these trains can still be moved onto Secondary Track to clear the mainline. - Q3 corrected: Expedite governs WHERE a train may be left standing, not WHEN it leaves. The forced same-Stage departure is gone; a new fault costs 1 Revenue if an expedited train is left off the station when a Mainline Phase begins. Resolves "3/4 Express prints a rule it can never use" as a side effect. - evaluateClearance now checks every occupant on a Mainline card before offering a judgment call, instead of returning on whichever it found first — found while explaining a playtest report, fixed with a regression test. - Three new synthesised sounds: arrive, depart, crash. - The splash page shows the box art. Full detail, measurements and reasoning in CHANGELOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FvU99NEakShRMg3nN3fHAZ
This commit is contained in:
+24
-30
@@ -280,25 +280,25 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
};
|
||||
case 'trainArrived':
|
||||
/**
|
||||
* AN EXPEDITED TRAIN IS NOT COMING BACK ROUND — AND IS NOT GONE YET EITHER.
|
||||
* AN EXPEDITED ARRIVAL IS AN ORDINARY ARRIVAL, WITH ONE STANDING OBLIGATION.
|
||||
*
|
||||
* This said "it will highball again next Mainline Phase" to every arrival, which is backwards
|
||||
* for an Expedited train. Corrected once to "it leaves again this same Mainline Phase", which
|
||||
* was ALSO wrong and in a way that cost the player work: the expedited departure was moved to
|
||||
* Supervisor Shift precisely so the Porters and Laborers get their Stage with the train, so
|
||||
* Cargo IS available and only Local Operations is not. Telling a player there is "no turn in
|
||||
* which to work it" talked them out of the one turn they had.
|
||||
*
|
||||
* So both branches now name the phases: what you can still do with this train, and when it
|
||||
* goes. That is the whole of the reported confusion — "some trains seem to be moving before I
|
||||
* can switch or do other operations on them".
|
||||
* This used to say the train stands through Cargo and is forced out at the end of the Stage —
|
||||
* true once, and wrong: Q3 means the train must not be PARKED anywhere but the station, not
|
||||
* that it is rushed out early. It is switched, worked and released exactly like any other
|
||||
* arrival; the only difference is what happens if it is left on Secondary Track when the next
|
||||
* Mainline Phase begins (`expediteFault`).
|
||||
*/
|
||||
return {
|
||||
tone: 'plain',
|
||||
text: e.expedited
|
||||
? `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — its card prints EXPEDITE, so it stands through the Cargo phase and departs at the END OF THIS STAGE. Porters and Laborers can work it now; it is normally gone before your next Local Operations turn, so it cannot be switched — unless §8.1 holds it for a ruling, in which case it stays and runs like any other train.`
|
||||
? `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — its card prints EXPEDITE, so keep it on the Office square: parked anywhere else in the district when the next Mainline Phase begins costs a Revenue point. It works and switches normally in the meantime.`
|
||||
: `Train ${e.trainNumber} ARRIVED at the ${e.office} carrying ${carsLabel(e.consist)} — it stands here for the rest of this Stage. You can work it in Cargo now, switch it in the NEXT Stage's Local Operations, and it departs in that Stage's Mainline Phase.`,
|
||||
};
|
||||
case 'expediteFault':
|
||||
return {
|
||||
tone: 'bad',
|
||||
text: `Train ${e.trainNumber} was left at ${e.where}, off the station, when the Mainline Phase began — an expedited train must be kept ready to highball. Station Master fault.`,
|
||||
};
|
||||
case 'trainDiverted':
|
||||
return {
|
||||
tone: 'good',
|
||||
@@ -582,7 +582,7 @@ export function impediments(s: GameState, player: PlayerIndex = 0): Impediment[]
|
||||
// the board says it on the card instead.
|
||||
if (b.kind === 'noStopping') continue;
|
||||
out.push({
|
||||
where: `${tray.trainNumber === null ? 'crew' : `Train ${tray.trainNumber}`} → (${b.coord.row},${b.coord.col})`,
|
||||
where: `${tray.trainNumber === null ? 'crew' : `Train ${tray.trainNumber}`} → (${b.coord.col},${b.coord.row})`,
|
||||
why: b.why,
|
||||
// Not "stuck": these are the shape of the district and of the other trains in it, which is
|
||||
// the puzzle rather than a fault. Amber, not red.
|
||||
@@ -602,12 +602,10 @@ export function impediments(s: GameState, player: PlayerIndex = 0): Impediment[]
|
||||
* said so.
|
||||
*
|
||||
* Reported again as "passenger trains arrive at my Office and move on before I can load or unload
|
||||
* them", which turned out to be three separate silences. This block only looked at trains carrying
|
||||
* them", which turned out to be two separate silences. This block only looked at trains carrying
|
||||
* a LOADED coach, so a train arriving to PICK UP said nothing at all. A Whistle Post's Office card
|
||||
* carries a passenger facility with zero Porters, so the engine answered `RESOURCE_SPENT` — "all
|
||||
* Porters used" — to a player who had used none. And an expedited train leaves at the end of the
|
||||
* Stage it arrives, so its one Load/Unload phase is now-or-never and nothing said which trains
|
||||
* those are.
|
||||
* Porters used" — to a player who had used none.
|
||||
*
|
||||
* §9.2 also requires an empty coach in the DIVISION YARD to swap into the train, which no panel
|
||||
* mentioned at all.
|
||||
@@ -620,8 +618,6 @@ export function impediments(s: GameState, player: PlayerIndex = 0): Impediment[]
|
||||
.filter((t): t is NonNullable<typeof t> => !!t && t.consist.some((c) => c.type === 'coach'));
|
||||
|
||||
if (coachTrains.length > 0) {
|
||||
const label = (t: (typeof coachTrains)[number]): string =>
|
||||
t.trainNumber === null ? 'the local crew' : `Train ${t.trainIsExtra ? 'X' : ''}${t.trainNumber}`;
|
||||
const say = (why: string, severity: Impediment['severity'] = 'waiting'): void => {
|
||||
out.push({ where: 'Office — passengers', why, severity });
|
||||
};
|
||||
@@ -658,17 +654,6 @@ export function impediments(s: GameState, player: PlayerIndex = 0): Impediment[]
|
||||
say('no EMPTY coach in the Division Yard to swap into the train (§9.2 requires one)');
|
||||
}
|
||||
}
|
||||
|
||||
// NOW OR NEVER. An expedited train leaves at the end of this Stage, so this is the only
|
||||
// Load/Unload phase it will ever stand for — the exact thing the playtest report was about.
|
||||
for (const t of coachTrains) {
|
||||
if (!t.departsThisStage) continue;
|
||||
say(
|
||||
`${label(t)} is EXPEDITED — it leaves at the END of this Stage, so this is the only ` +
|
||||
'Load/Unload phase it will stand for. Work it now or not at all.',
|
||||
'risk',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,6 +733,15 @@ export function cuesFor(events: readonly GameEvent[]): string[] {
|
||||
// A train running the whole length of the Division pays every player, and it is the one event
|
||||
// nobody made happen on the turn it lands. It should be heard, not found in the log.
|
||||
if (e.type === 'trainCompleted') out.push('completed');
|
||||
// A train pulling into an Office. `trainDiverted` (the Yard Office siding) is deliberately not
|
||||
// included — that one goes straight to a work track without the A/D stop `arrive` depicts.
|
||||
if (e.type === 'trainArrived') out.push('arrive');
|
||||
// Only an OFFICE departure, not every highball: a fresh make-up leaving a Division Point already
|
||||
// sounds `train` ("All aboard"), and a train clearing the whole Division already sounds
|
||||
// `completed` — this is the one case neither of those covers.
|
||||
if (e.type === 'trainHighballed' && e.from === 'the Office') out.push('depart');
|
||||
// §10 — the one event nobody wants to hear and everybody needs to.
|
||||
if (e.type === 'trainsDestroyed') out.push('crash');
|
||||
if (e.type === 'stageBegan') {
|
||||
// A Stage BEGINNING is the previous one ending — except the first, which is the game opening
|
||||
// and has nothing behind it. A Day boundary rings the bell only: sounding both would collide,
|
||||
|
||||
Reference in New Issue
Block a user