v0.8.0.11 — sixteen fixes from the second multiplayer playtest
Arrivals name whose Office they reached, and no longer tell every seat they can work the train. The turn chart follows the animation queue, so being five behind looks five behind across the whole screen rather than half of it. Pause sits beside Skip and preserves the dwell a held step still owed. A one-render look at another player's Office Area. The district summary counts the board being shown. LIMITS is printed beneath its card instead of through its border. The Mainline region divider is visible. Only Hilly mentions FAST/SLOW, because it is the only card that reads it. A passenger Modifier on a Whistle Post reports itself dormant rather than claiming the facility "only receives". An automatic phase says what the Division is doing instead of answering by negation. The version appears once in the header rather than twice on every .s9pk. Save files carry the join code, the Stage and the date. The New Train phase, reviewed before being changed: the make-up panel now says what the train STILL needs rather than only what its card calls for, explains that a player adds one car before the round passes on, marks the train being loaded on the Division map, and gives an addable car in the yard the same amber every other clickable thing on the page wears. Reasoning, measurements and the reports behind each are in CHANGELOG.md. 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
4adf149ba5
commit
9a9e50b3c6
+32
-6
@@ -137,6 +137,8 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
region?: number;
|
||||
direction?: string;
|
||||
stagesLeft?: number;
|
||||
/** Being made up at a Division Point right now, so the map can mark the train you are loading. */
|
||||
beingMadeUp?: boolean;
|
||||
}[];
|
||||
cap: number | null;
|
||||
tip: string;
|
||||
@@ -515,13 +517,18 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
const arrow = t.facing === 'w' ? '\u25c0' : '\u25b6';
|
||||
const loaded = cars.filter((x) => /^loaded/.test(x) || /caboose/.test(x)).length;
|
||||
const label = cars.length === 0 ? `${t.label} ${arrow}` : `${t.label} ${arrow}${cars.length}`;
|
||||
// THE TRAIN THE MAKE-UP PANEL IS TALKING ABOUT. Amber, because that is what the rest of the
|
||||
// page uses for "this is the thing you are acting on" (Jesse, playtest 2026-09-16).
|
||||
const building = t.beingMadeUp === true;
|
||||
const inRegion = c.regions > 1 && typeof t.region === 'number';
|
||||
const dir = t.direction === 'west' ? ' \u25c0 west' : t.direction === 'east' ? ' east \u25b6' : '';
|
||||
const stages =
|
||||
typeof t.stagesLeft === 'number'
|
||||
? ` \u00b7 ${t.stagesLeft} Stage${t.stagesLeft === 1 ? '' : 's'} still to run across this card`
|
||||
: '';
|
||||
out += `<g class="bs-train" data-tip="${esc(t.label)} \u2014 carrying ${esc(cars.join(', ') || 'no cars')}${
|
||||
out += `<g class="bs-train${building ? ' bs-building' : ''}" data-tip="${esc(t.label)}${
|
||||
building ? ' \u2014 BEING MADE UP NOW: add cars from the Division Yard' : ''
|
||||
} \u2014 carrying ${esc(cars.join(', ') || 'no cars')}${
|
||||
cars.length ? ` (${loaded} loaded)` : ''
|
||||
}${inRegion ? ` \u00b7 region ${(t.region ?? 0) + 1} of ${c.regions}, counted west to east${dir}` : ''}${esc(stages)}${
|
||||
// What the card prints. A train on the Mainline is exactly where "why did that leave without
|
||||
@@ -682,7 +689,17 @@ export function officeSvg(
|
||||
const c0 = Math.min(...cols);
|
||||
const c1 = Math.max(...cols);
|
||||
const width = (c1 - c0 + 1) * (W + PAD);
|
||||
const height = (r1 - r0 + 1) * (H + PAD) + 4;
|
||||
/**
|
||||
* A BAND BENEATH THE BOTTOM ROW FOR THE LIMITS LABELS, and only when there are labels to put in it.
|
||||
*
|
||||
* The bottom card's lower edge lands at `height - 7`, and the label's baseline was `height - 4` —
|
||||
* so its 8px glyphs spanned `height - 12` to `height - 4` and the card's own border ran straight
|
||||
* through the middle of the word (Jesse, playtest 2026-09-16: *"the text is split by the bottom
|
||||
* border of the limits card… it should be printed directly beneath the card"*). Raising the text
|
||||
* instead would have pushed it onto the card, over the rails; the room has to be made below.
|
||||
*/
|
||||
const limitBand = limits ? 14 : 0;
|
||||
const height = (r1 - r0 + 1) * (H + PAD) + 4 + limitBand;
|
||||
|
||||
// Screen position of a card. Rows count DOWN from the top row, so the Running Track sits highest
|
||||
// and the district hangs beneath it, as the rules describe it.
|
||||
@@ -1218,7 +1235,9 @@ export function officeSvg(
|
||||
if (limits) {
|
||||
const edge = (x: number, side: string): string =>
|
||||
`<line class="bs-limitline" x1="${x}" y1="0" x2="${x}" y2="${height}"/>` +
|
||||
`<text class="bs-limitlab" x="${x + (side === 'w' ? 4 : -4)}" y="${height - 4}" ` +
|
||||
// Baseline inside the band below the cards: the glyphs run from `height - 19` to `height - 11`
|
||||
// and the bottom row's edge is at `height - 21`, so the whole word clears the card border.
|
||||
`<text class="bs-limitlab" x="${x + (side === 'w' ? 4 : -4)}" y="${height - 11}" ` +
|
||||
`text-anchor="${side === 'w' ? 'start' : 'end'}">LIMITS</text>`;
|
||||
out += edge(px(limits.west) - PAD / 2, 'w') + edge(px(limits.east) + W + PAD / 2, 'e');
|
||||
}
|
||||
@@ -1267,9 +1286,12 @@ export const BOARD_CSS = `
|
||||
.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}
|
||||
/* The vertical bars a Mainline card is divided into (§2.1). They are the ruler the train is measured
|
||||
against, not something to look at instead of the train — but they were drawn so faint they could
|
||||
not be made out at all (Jesse, playtest 2026-09-16: "the dividing line is barely visible"). A
|
||||
ruler you cannot read is not restraint, so this is lifted to the tie colour and given a longer
|
||||
dash: still quieter than the rail, and now actually there. */
|
||||
.bs-region{stroke:#98a3b2;stroke-width:1.6;stroke-dasharray:4 2}
|
||||
/* #94 — the one red mark on the Division map, so it reads as a stop rather than as decoration. */
|
||||
.bs-flag line{stroke:#9aa3b0;stroke-width:1.6}
|
||||
.bs-flag polygon{fill:#d2453f;stroke:#7d211d;stroke-width:0.8}
|
||||
@@ -1302,6 +1324,10 @@ export const BOARD_CSS = `
|
||||
.bs-slot.bs-car-cch.bs-loaded{fill:rgba(90,169,230,.85)}
|
||||
.bs-slot.bs-car-cab.bs-loaded{fill:rgba(192,90,90,.85)}
|
||||
.bs-train rect{fill:#2f6b3d;stroke:#8fd6a0;stroke-width:1.2}
|
||||
/* The train the New Train phase is loading, in the page's action amber, so the make-up panel on the
|
||||
right and the train on the map at the top left are visibly the same subject. */
|
||||
.bs-train.bs-building rect{fill:#4a3a1c;stroke:#c8912f;stroke-width:2}
|
||||
.bs-train.bs-building .bs-tlab{fill:#f2d49a}
|
||||
.bs-crew rect{fill:#8a6d1f;stroke:#e0c060;stroke-width:1.2}
|
||||
/* Each car in the train, in the order it is seated. Loaded is solid, empty is hollow, and the
|
||||
engine is the one that carries the arrow — which is what makes "reverse" mean something. */
|
||||
|
||||
Reference in New Issue
Block a user