v0.8.0.17 — four things the game knew and the screen did not say
All four reported from a table on Day 1 of v0.8.0.16, and all the same shape. ABS SIGNALS COULD ONLY BE PLAYED ON ONE MAINLINE CARD, while its tooltip said "any Mainline card". The engine was never wrong: check accepts any node whose kind is mainline and legalActions filters by check, so all of them were legal. The failure was the LABEL — describeIntent named i.placement and never i.node, so every placement described itself as plain "play ABS Signals", and the action list drops duplicate labels. All but the lowest-index node were discarded before the menu saw them. This is the THIRD time that trap has fired and the file documents the other two three lines apart: a turnout's two rotations, and three Department discards. Same fix — name what distinguishes them. The card is also called what the card face calls it. prettyKey rendered absSignals as "Abs Signals" beside a tooltip saying ABS, an acronym no key-splitter can recover, so the authored names now win. Three of those names were transcribed in sentence case and were CORRECTED rather than adopted: the repository says "Yard Office" 36 times against "Yard office" twice. A lookup that imports its own source's typos is the drift it exists to prevent. NOTHING ON A MAINLINE CARD SHOWED WHAT WAS STANDING ON IT. Played, ABS left no mark and you found out by hovering — the same complaint the Heavy Grade wedge answered, and it matters more here because ABS decides whether a second train on that card is safe. It draws a signal mast with a lit lamp now; a signal is the literal object and needs no room for words, which is what lets it sit clear of a name as long as "Uncontrolled Siding" on a 152px cell. The Mainline modifiers draw as BRK, AIR and HLP. Realignment is deliberately not among them: reduce takes the `became` branch and changes node.card, so a realigned Trestle IS an Uncontrolled Siding afterwards. Asserted, so the absence reads as a finding. A FREIGHT AGENT TURN SAID A CAR MOVED WHEN NONE HAD. Three faults behind one line. It asserted an outcome, where §6.3 requires no action and the bot declines deliberately — unjamming a healthy box destroys a load that cost a whole action to stock. An idle Agent was then silent, which read as a dropped turn; a new freightAgentIdled event says so and why, reducing to nothing exactly like switchingEnded. And the work named a coordinate rather than the industry, though a `place` helper has existed for precisely that since the switching lines moved to it. "Loaded a loaded boxcar INTO the green Outbound box at the Freight House", with the direction in capitals because to-or-from was the question asked. THE LOG AND THE ACTION MENU SPELLED THE SAME SQUARE DIFFERENTLY. view.ts wrote (col,row) — X,Y, east/west then north/south — with a comment saying why; narrate.ts wrote the internal storage order with no comment at all. So the menu offered a move to "(1,-1)" and the log reported it at "(-1,1)", side by side. Pinned by a test that renders one square through BOTH describers and compares them to each other: a test written against either file alone would have passed. THE DOCUMENTATION IS REACHABLE FROM A RUNNING GAME, AND ALL OF IT IS PUBLISHED. v0.8.0.16 published the Quickstart and nothing it points at — its §8 links five documents by relative path and every one 404'd on the package, verified against the running container. The build publishes the full set, and the test reads the links OUT OF the guide rather than listing them. They are linked from the This Game card, where reference already lives, rather than the header that must not wrap; no mode awareness is needed, because solitaire and multiplayer are the same page on the same origin. THE REFERENCES DROPPED THE VERSION FROM THEIR NAMES. Four described v0.8.0.16 and had since the v0.8.0.15 audit; the v0.4.5 was the prototype edition they were first written against, kept only because 36 citations pointed at it — and it read as documentation five minor versions stale. They are quickstart.md, rules.md, home-deck.md, mainline-deck.md and components.md now, kept current with each release rather than published as editions. Two errors surfaced while checking them against this release, which is the argument for doing it: home-deck.md filed ABS Signals under Enhancements "played into your district" that "change what a square does" — it does neither, this release's bug written down — and mainline-deck.md, which lists everything playable onto a Mainline card, never mentioned it at all. 1010 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUizFYCMHRWhbWwXhp7WPR
This commit is contained in:
co-authored by
Claude Opus 5
parent
dc31555625
commit
b90c0413d2
@@ -71,6 +71,22 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
* Self-contained on purpose: the replay embeds this by `toString()`, so it may not reach for
|
||||
* anything outside its own body.
|
||||
*/
|
||||
/**
|
||||
* The Mainline modifiers, as the three letters drawn on a card.
|
||||
*
|
||||
* DECLARED INSIDE THIS FUNCTION, like every constant around it, because `sim/replay.ts` emits
|
||||
* `divisionSvg.toString()` into the replay page — a module-scope const it closed over would be
|
||||
* undefined there, and the page threw exactly that way before this was moved.
|
||||
*
|
||||
* Keyed by the name the VIEW builds (`prettyKey`'d in `view.ts`), because that is what arrives
|
||||
* here — not the engine's key, which this file never sees. A modifier with no tag draws nothing
|
||||
* rather than a raw key: `flatMap` over a missing entry yields none, so a card added upstream is
|
||||
* silently unmarked instead of printing "rotaryDumps" at a player.
|
||||
*
|
||||
* ABS Signals is deliberately absent: it has a signal mast of its own.
|
||||
*/
|
||||
const MOD_TAGS: Record<string, string> = { Brakeman: 'BRK', Airbrakes: 'AIR', Helpers: 'HLP' };
|
||||
|
||||
const CW = { dp: 118, ml: 152, run: 78 };
|
||||
/**
|
||||
* TALL ENOUGH FOR TWO REGISTERS OF CHIPS, on every cell so the rail runs level across the row.
|
||||
@@ -144,6 +160,10 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
tip: string;
|
||||
/** Set on a card that has just changed under the players' feet — drawn with a brief pulse. */
|
||||
flash?: boolean;
|
||||
/** ABS Signals standing on this Mainline card — drawn as a signal mast, not only described. */
|
||||
abs?: boolean;
|
||||
/** The Mainline modifiers on this card, as short tags — see `MOD_TAGS`. */
|
||||
mods?: string[];
|
||||
/** 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. */
|
||||
@@ -291,6 +311,10 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
// words with no gameplay attached — reported exactly that way.
|
||||
(n.what ? `\n\n${n.what}` : ''),
|
||||
seat: null,
|
||||
// Attachments are drawn, not only described: `modifiers` carries ABS Signals alongside the
|
||||
// Mainline modifiers, and the name is the one the view already built for the tooltip.
|
||||
abs: !dp && n.modifiers.includes('ABS Signals'),
|
||||
mods: dp ? [] : n.modifiers.flatMap((m) => MOD_TAGS[m] ?? []),
|
||||
// A Division Point is one region — the queue trains enter and leave the Division through.
|
||||
regions: dp ? 1 : (n.regions ?? 0),
|
||||
gradeUp: dp ? null : (n.gradeUp ?? null),
|
||||
@@ -416,6 +440,57 @@ export function divisionSvg(nodes: DivisionView[], roster?: DivisionRoster | nul
|
||||
out += `</g>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* ABS SIGNALS, DRAWN RATHER THAN ONLY SAID — reported from a table on Day 1 Stage 1 of
|
||||
* v0.8.0.16: "when played on the trestle, there was no on-the-card indication; it's only when
|
||||
* you look at the tooltip for trestle that you see that ABS exists."
|
||||
*
|
||||
* Exactly the complaint the grade wedge below answers, and the card it protects is the one a
|
||||
* player is deciding whether to run a second train onto. A signal is the literal object — ABS
|
||||
* is Automatic Block Signals — so the mark is a mast with a lit lamp rather than a badge
|
||||
* reading "ABS", and it needs no room for text, which is what lets it sit clear of a name as
|
||||
* long as "Uncontrolled Siding" on a 152px card.
|
||||
*
|
||||
* TOP-RIGHT, above the rail and clear of the chip registers: the flag owns the cell ends at
|
||||
* rail height and the grade wedge owns the bottom-right, so this is the corner left.
|
||||
*/
|
||||
if (c.abs) {
|
||||
const mx = c.x + c.w - 10;
|
||||
const top = c.y + 5;
|
||||
out += `<g class="bs-abs">`;
|
||||
out += `<line class="bs-abs-mast" x1="${mx}" y1="${top}" x2="${mx}" y2="${top + 15}"/>`;
|
||||
// Two lamps, the upper one lit: a signal showing an aspect, not a bare post.
|
||||
out += `<circle class="bs-abs-lit" cx="${mx}" cy="${top + 3}" r="2.6"/>`;
|
||||
out += `<circle class="bs-abs-dark" cx="${mx}" cy="${top + 9}" r="2.6"/>`;
|
||||
out += `</g>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* THE MAINLINE MODIFIERS, DRAWN RATHER THAN ONLY SAID (Jesse, 2026-09-21) — the other half of
|
||||
* the ABS report above: "Brakeman, Airbrakes, Helpers and Realignment should also be drawn on
|
||||
* the card, not just the tooltip."
|
||||
*
|
||||
* ONLY THREE OF THOSE FOUR CAN EVER BE HERE. Realignment does not sit on a card — `reduce`
|
||||
* takes the `became` branch and CHANGES `node.card`, so a realigned Trestle simply is an
|
||||
* Uncontrolled Siding afterwards and the card face already says so. The other three are
|
||||
* `gradeOnly`, so in practice this is a Heavy Grade's row.
|
||||
*
|
||||
* TAGS, NOT NAMES, and only because the measurements leave no choice: "Brakeman · Airbrakes ·
|
||||
* Helpers" is 30 characters where about eleven fit beside a card name on a 152px cell. Three
|
||||
* letters is the most that fits while still mapping to one card each, and the tooltip — which
|
||||
* has always named them in full — is what expands it. The mark says THAT there is one, which
|
||||
* is the half that was missing.
|
||||
*
|
||||
* Right-aligned on the name row: the only band on the cell that is clear, with the chip
|
||||
* registers starting at `CHIP_Y` below and the capacity line and grade wedge at the foot.
|
||||
* Shifted left of the signal when a card carries both.
|
||||
*/
|
||||
if (c.mods && c.mods.length > 0) {
|
||||
out +=
|
||||
`<text class="bs-mod" text-anchor="end" x="${c.x + c.w - (c.abs ? 20 : 7)}" y="${c.y + 14}">` +
|
||||
`${esc(c.mods.join('·'))}</text>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* WHICH WAY A HEAVY GRADE CLIMBS, drawn rather than only said.
|
||||
*
|
||||
@@ -1392,6 +1467,12 @@ export const BOARD_CSS = `
|
||||
.bs-grade{fill:#e08060;font:10px ui-monospace,monospace}
|
||||
.bs-mod{font:10px ui-monospace,monospace}
|
||||
text.bs-mod{fill:#c8a04a}
|
||||
/* A signal standing on a Mainline card carrying ABS Signals. Green is otherwise unused on the
|
||||
Division row apart from the Division Point's dashed border, so a lit lamp does not compete with
|
||||
the amber "it is happening here" or the red flag for meaning. */
|
||||
.bs-abs-mast{stroke:#9aa3b0;stroke-width:1.6}
|
||||
.bs-abs-lit{fill:#4fae6a;stroke:#2c6b40;stroke-width:0.8}
|
||||
.bs-abs-dark{fill:#2a3038;stroke:#59626f;stroke-width:0.8}
|
||||
.bs-enh{fill:#7fb0e6;font:9px ui-monospace,monospace}
|
||||
.bs-enh-spent{fill:#5b6b7d;text-decoration:line-through}
|
||||
.bs-rowlab{fill:#5f6b7a;font:600 9px ui-monospace,monospace;letter-spacing:.1em}
|
||||
|
||||
Reference in New Issue
Block a user