v0.8.0.10 — playtest fixes: clearance rulings, the log, the map, and a save file
From the first two multiplayer playtests of v0.8.0.9, each traced before fixing. The engine: - A train on a card BEHIND the one departing no longer triggers a clearance ruling or an opposite-direction bar (#26). Reproduced from the exported save: X15 was held over X18 behind it, and X18 then collided into the full Whistle Post. Games in progress holding a ruling the engine no longer asks for will not resume (28 of 40 recorded four-seat games); shipped as is at Jesse's call. - `mainlineModified` carries the card's previous kind, so the log can say what a Realignment converted (#27). The screen: - The turn chart and the Division map name the player whose move is on screen while bot turns replay, not the live actor (#25). - The owning player's name is no longer outlined by the turn arrow's stroke, which made it unreadable (#24). - A Mainline card flashes on the map when a Realignment changes it (#28). - The history is held back with the board and revealed step by step, instead of arriving whole while the board is still catching up (#29). - A ruling made by holding the office reads "Superintendent Player X" (#30), and no line names a player twice (#31). - A seated player can download their own game as a save file: the play page's Save replay button, fed by GET /api/save?token=… (#32). The StartOS action cannot do this — an action result is text only. Closes #24 Closes #25 Closes #26 Closes #27 Closes #28 Closes #29 Closes #30 Closes #31 Closes #32 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nnuCv8UodHucFfx3LWEoX
This commit is contained in:
co-authored by
Claude Opus 5
parent
76c6e103b3
commit
4adf149ba5
+18
-12
@@ -15,7 +15,7 @@
|
||||
* panel cannot drift from the rules.
|
||||
*/
|
||||
|
||||
import { MAX_CONSIST, crewTrayCount } from '../engine/content.ts';
|
||||
import { MAINLINE_PROFILES, MAX_CONSIST, crewTrayCount } from '../engine/content.ts';
|
||||
import { adTrackCount, coordKey, seatOf, turnOf } from '../engine/state.ts';
|
||||
import type { GameState, GridCoord, PlayerIndex, RollingStock, SeatIndex, TrayId } from '../engine/state.ts';
|
||||
import { areaOf, canAdvanceLoad, canBoard, canDetrain, canStartLoad, facilityCarType, facilityCarTypes, freightRuleSpentHere, isFreight, laborersLeft, movesFor, passengerRefusal, portersLeft } from '../engine/apply.ts';
|
||||
@@ -134,7 +134,7 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
case 'actorChanged':
|
||||
return {
|
||||
tone: 'quiet',
|
||||
text: e.player === null ? 'No player acts — automatic phase' : `Player ${e.player} to act`,
|
||||
text: e.player === null ? 'No player acts — automatic phase' : 'to act',
|
||||
};
|
||||
|
||||
// -- local operations
|
||||
@@ -211,13 +211,19 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
? `Played ${card(e.cardId)} onto ${at(e.placement)}`
|
||||
: `Played ${card(e.cardId)}`,
|
||||
};
|
||||
case 'mainlineModified':
|
||||
case 'mainlineModified': {
|
||||
// WHICH CARD, NOT JUST WHICH WAY IT WENT. "Mainline card 3 converted to plains" left the reader
|
||||
// to remember what card 3 had been (playtest, 2026-09-15), and the card it WAS is the half that
|
||||
// says what the play was worth.
|
||||
const kindName = (k: string | undefined): string =>
|
||||
MAINLINE_PROFILES.find((m) => m.kind === k)?.name ?? k ?? 'that card';
|
||||
return {
|
||||
tone: 'plain',
|
||||
text: e.became
|
||||
? `Realignment: Mainline card ${e.node} converted to ${e.became}`
|
||||
: `Played ${e.key} on Mainline card ${e.node}`,
|
||||
? `Realignment: Mainline card ${e.node}, ${kindName(e.from)}, converted to ${kindName(e.became)}`
|
||||
: `Played ${e.key} on Mainline card ${e.node}${e.from ? ` (${kindName(e.from)})` : ''}`,
|
||||
};
|
||||
}
|
||||
case 'redFlagSpent':
|
||||
return {
|
||||
tone: 'good',
|
||||
@@ -225,8 +231,8 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
};
|
||||
case 'redFlagRuled':
|
||||
return e.flag
|
||||
? { tone: 'plain', text: `Player ${e.player} flagged the approaching train` }
|
||||
: { tone: 'plain', text: `Player ${e.player} waved the train through` };
|
||||
? { tone: 'plain', text: 'Flagged the approaching train' }
|
||||
: { tone: 'plain', text: 'Waved the train through' };
|
||||
case 'redFlagsSet':
|
||||
return {
|
||||
tone: 'good',
|
||||
@@ -496,13 +502,13 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
? { tone: 'bad', text: `${e.reason.toUpperCase()} · ${e.delta} Revenue (now ${e.total})` }
|
||||
: { tone: 'good', text: `+${e.delta} Revenue (now ${e.total}) — ${e.reason}` };
|
||||
case 'phaseEnded':
|
||||
return { tone: 'quiet', text: `Player ${e.player} finished ${phaseLabel(e.phase)}` };
|
||||
return { tone: 'quiet', text: `Finished ${phaseLabel(e.phase)}` };
|
||||
|
||||
// -- §3.3, extended play (Gitea#11)
|
||||
case 'extensionVoted':
|
||||
return e.agree
|
||||
? { tone: 'plain', text: `Player ${e.player} would play one more Day` }
|
||||
: { tone: 'plain', text: `Player ${e.player} called time — the game ends here` };
|
||||
? { tone: 'plain', text: 'Would play one more Day' }
|
||||
: { tone: 'plain', text: 'Called time — the game ends here' };
|
||||
case 'dayExtended':
|
||||
return { tone: 'clock', text: `── The table plays on: Day ${e.day} is added to the timetable ──` };
|
||||
case 'playConcluded':
|
||||
@@ -511,8 +517,8 @@ export function narrate(e: GameEvent, ctx: NarrateContext = {}): Narration {
|
||||
// -- §11, the Yard Office (Gitea#5)
|
||||
case 'yardOfficeRuled':
|
||||
return e.take
|
||||
? { tone: 'plain', text: `Player ${e.player} sent ${train(e.trainId)} into the Yard Office` }
|
||||
: { tone: 'plain', text: `Player ${e.player} kept ${train(e.trainId)} at the Train Order Office` };
|
||||
? { tone: 'plain', text: `Sent ${train(e.trainId)} into the Yard Office` }
|
||||
: { tone: 'plain', text: `Kept ${train(e.trainId)} at the Train Order Office` };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user