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
+24
-2
@@ -42,6 +42,12 @@ export type DivisionRoster = {
|
||||
actor: number | null;
|
||||
/** The player this map is being drawn for. */
|
||||
viewer: number;
|
||||
/**
|
||||
* Division nodes to flash — a Mainline card that has just become a different card (Realignment).
|
||||
* Playtest, 2026-09-15: the log said a card had been converted and the map said nothing, so the one
|
||||
* play that changes the Division itself was invisible on the map of it.
|
||||
*/
|
||||
flash?: readonly number[];
|
||||
};
|
||||
|
||||
export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | null): string {
|
||||
@@ -134,6 +140,8 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
}[];
|
||||
cap: number | null;
|
||||
tip: string;
|
||||
/** Set on a card that has just changed under the players' feet — drawn with a brief pulse. */
|
||||
flash?: boolean;
|
||||
/** Which SEAT's district this cell belongs to, or null for Mainline and Division Points. */
|
||||
seat: number | null;
|
||||
/** Set on an Office cell when a roster was supplied: whose district this is. */
|
||||
@@ -168,7 +176,11 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
cells.push({ ...c, x: 0, y: 0 });
|
||||
};
|
||||
|
||||
// The node's own index, so a cell can be matched against `roster.flash`. `continue` below skips the
|
||||
// rest of the body, never this.
|
||||
let nodeIndex = -1;
|
||||
for (const n of nodes) {
|
||||
nodeIndex++;
|
||||
if (n.kind === 'office') {
|
||||
const cap = n.capacity;
|
||||
const ad = n.trains.flat();
|
||||
@@ -261,6 +273,7 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
push({
|
||||
kind: dp ? 'dp' : 'ml',
|
||||
label: n.label,
|
||||
...(roster?.flash?.includes(nodeIndex) ? { flash: true } : {}),
|
||||
sub: n.capacity === null
|
||||
? 'no limit — trains queue'
|
||||
: [free, inYard.length > 0 ? `${inYard.length} in the yard` : ''].filter(Boolean).join(' · '),
|
||||
@@ -356,7 +369,7 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
|
||||
cells.forEach((c) => {
|
||||
const full = c.cap !== null && c.trains.length >= c.cap;
|
||||
out += `<g class="bs-dcell bs-d${c.kind}${full ? ' bs-full' : ''}" data-tip="${esc(c.tip)}">`;
|
||||
out += `<g class="bs-dcell bs-d${c.kind}${full ? ' bs-full' : ''}${c.flash ? ' bs-changed' : ''}" data-tip="${esc(c.tip)}">`;
|
||||
out += `<rect x="${c.x}" y="${c.y}" width="${c.w}" height="${CH}" rx="5"/>`;
|
||||
/**
|
||||
* WHOSE IS IT, IS IT THEIR MOVE, AND IS IT MINE — answered by colour and one suffix rather
|
||||
@@ -1248,6 +1261,12 @@ export const BOARD_CSS = `
|
||||
and leave at the other, and a seated layout must not be read as a ring. */
|
||||
.bs-stop line{stroke:#e0a060;stroke-width:2.6;stroke-linecap:round}
|
||||
.bs-end{fill:#e0a060;font:10px ui-monospace,monospace;letter-spacing:.03em}
|
||||
/* A card that has just BECOME a different card (Realignment). The same amber the rest of the page
|
||||
spends on "it is happening here", pulsing only while the step that did it is on screen — so the
|
||||
change is seen on the map rather than only read in the log. */
|
||||
.bs-dcell.bs-changed rect{stroke:#e0a060;stroke-width:2.4;animation:bs-changed-pulse 1.1s ease-in-out infinite}
|
||||
@keyframes bs-changed-pulse{0%,100%{stroke-opacity:1}50%{stroke-opacity:.35}}
|
||||
@media (prefers-reduced-motion: reduce){.bs-dcell.bs-changed rect{animation:none}}
|
||||
/* The vertical bars a Mainline card is divided into (§2.1). Drawn faint: they are the ruler the
|
||||
train is measured against, not something to look at instead of the train. */
|
||||
.bs-region{stroke:#4a5361;stroke-width:1.2;stroke-dasharray:3 3}
|
||||
@@ -1329,7 +1348,10 @@ export const BOARD_CSS = `
|
||||
.bs-arrow{fill:#5f6b7a;font:10px ui-monospace,monospace}
|
||||
.bs-cn{fill:#e6e9ee;font:600 11px ui-monospace,monospace}
|
||||
.bs-coord{fill:#5f6b7a;font:9px ui-monospace,monospace}
|
||||
.bs-name{fill:#e6e9ee;font:600 11px ui-monospace,monospace}
|
||||
/* stroke:none (Gitea#24). A name takes the class \`bs-turn\` while it is that player's move, and \`.bs-turn\` is
|
||||
also the turn ARROW's rule, which strokes its shape 2.4px grey. Declared after it, this keeps that
|
||||
outline off the letters, which it smeared into an unreadable blur. */
|
||||
.bs-name{fill:#e6e9ee;stroke:none;font:600 11px ui-monospace,monospace}
|
||||
.bs-name.bs-you{fill:#5aa9e6}
|
||||
/* Their move — wins over .bs-you when both apply, because whose turn it is changes every few
|
||||
seconds and which railroad is yours never does.
|
||||
|
||||
Reference in New Issue
Block a user