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
@@ -94,6 +94,18 @@ function nodeIndexOfOffice(s: GameState, seat: SeatIndex): number {
|
||||
|
||||
const step = (d: Direction): number => (d === 'east' ? 1 : -1);
|
||||
|
||||
/** The Division node a train is standing on — its Office, its Mainline card, or its Division Point. */
|
||||
function nodeIndexOfTray(s: GameState, tray: CrewTray): number | null {
|
||||
const at = tray.position;
|
||||
if (at.at === 'grid') return nodeIndexOfOffice(s, at.seat);
|
||||
if (at.at === 'mainline') return at.index;
|
||||
if (at.at === 'divisionPoint') {
|
||||
const side = at.side;
|
||||
return s.division.nodes.findIndex((n) => n.kind === 'divisionPoint' && n.side === side);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// advance
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1067,10 +1079,27 @@ function evaluateClearance(
|
||||
* constrained. Each Office upgrade to a Control Point splits one in two and buys capacity.
|
||||
*/
|
||||
const subdivision = subdivisions(s).find((group) => group.includes(targetIndex)) ?? [targetIndex];
|
||||
/**
|
||||
* ONLY WHAT IS AHEAD (Gitea#26). §8.1 asks about a train the considered train would FOLLOW, and one
|
||||
* moving TOWARDS it — both of which are ahead of it. A Subdivision runs the length of every Whistle
|
||||
* Post between two Control Points, so it can hold a train BEHIND the one departing: in the reported
|
||||
* game X15 highballed west from an Office while X18, also westbound, was still crossing the card to its
|
||||
* east. Counting X18 put a meaningless ruling to the Superintendent; holding X15 kept the Whistle Post's
|
||||
* one A/D track full, and X18 arrived into it and was destroyed. A train behind and moving away is no
|
||||
* threat at all.
|
||||
*
|
||||
* "Behind" is strictly behind the card the departing train stands on. A train on that same card is still
|
||||
* counted, exactly as before: which of two trains sharing a card is in front is `entryConflict`'s region
|
||||
* question, and this is not the place to answer it.
|
||||
*/
|
||||
const from = nodeIndexOfTray(s, tray);
|
||||
const behind = (onCard: number): boolean =>
|
||||
from !== null && from >= 0 && (tray.direction === 'east' ? onCard < from : onCard > from);
|
||||
const occupants: { tray: TrayId; onCard: number }[] = [];
|
||||
for (const i of subdivision) {
|
||||
const n = s.division.nodes[i];
|
||||
if (!n || n.kind !== 'mainline') continue;
|
||||
if (behind(i)) continue;
|
||||
for (const t of n.transits) if (t.tray) occupants.push({ tray: t.tray, onCard: i });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user