v0.7.9.3 — the reference says what only the code knows, and saves get a rule

The generated card reference now carries the half TODO #15a said was the
point of generating it: every Enhancement's `live` / `dormantSolo` /
`unbuilt` status — whether its printed effect actually resolves yet — and
the opponent-directed Action and Space-use cards, none of which is dealt
in any deck. A transcription cannot carry either fact.

Card counts are removed throughout, as ruled: they move with play balance
so a document printing them is stale on the next retune. Where a count
matters it is a yes/no "is this dealt at all", which is a fact about the
design rather than the current tuning. #88 closes with it — it asked
whether `card-reference.md`'s industry rows were stale, deliberately
without rewriting them since Laborer counts are a balance decision. They
are; nothing in the engine changed; that file is simply no longer where
anyone looks. The balance question it guarded is #70.

Save compatibility becomes a general rule in `README.md` § Design notes
rather than a fact restated per version: a save is a list of moves and
reopens by being re-played through the CURRENT rules, so any change that
makes a once-legal move illegal stops an older one there — a deck change
being the likeliest breaker. It fails safe every time. #40 generalised,
#32's version-specific note dropped, #52 carries the ruling that
versioned replays are a post-1.0 question.

#94 opened: a Red Flag set out at an Office's Limits holds the next train
from that side and is drawn nowhere. `DivisionView`'s office node has no
`redFlag` field and `board-svg.ts` never mentions one, so after the single
log line announcing it there is nothing on screen. Same class as Gitea#21
and #22, and already on the common board's step 1 list.

No engine change. 897 tests pass, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3Qk7uresKCHksdZajXCLg
This commit is contained in:
Jesse.Markowitz
2026-09-07 14:50:11 -04:00
co-authored by Claude Opus 5
parent 819996faa2
commit e734481d65
6 changed files with 353 additions and 105 deletions
+108 -42
View File
@@ -77,7 +77,7 @@ Not items. Things that are true of every change, and that have gone wrong when s
1. **Play it at a table** — #39 #35 #42a #40
2. **The common board, and watching play happen — Gitea#20** — #13 #15 #18 #91 #75
3. **Multiplayer, sessions and operations** — #8 #7 #76 #77 #79
4. **The screen** — #44 #81 #33 #36
4. **The screen** — #94 #44 #81 #33 #36
5. **Replays and saved games** — #14 #47 #48 #49 #50 #51 #52
6. **Rules** — #12 #80 #82 #83 #85
7. **Play balance** — #61 #62 #63 #64 #67 #68 #69 #70 #71 #72 #73 #66 #65 #74
@@ -119,10 +119,10 @@ specific paths below have not been exercised at a table. **More testing is plann
the dealt game matches what was chosen. It took three attempts to become reachable at all —
reachable is not the same as correct. See **Reference · #42a**.
- [ ] **#40** — **A save from before v0.7.4 may not replay, and nobody has been told.** It fails safe
— the server declines the save, names the move and leaves the file untouched — so this is a line
in a release note rather than code, and worth knowing when a bug report arrives with a save that
will not load. See **Reference · #40**.
- [ ] **#40** — **An older save may not replay, and players are not told so anywhere they will see
it.** Not a v0.7.4 fact and not a bug: a save is re-played through the current rules, so any
narrowing of what is legal can stop one. The rule is written down now (`README.md` § Design
notes); what is still owed is a line **wherever a build is announced**. See **Reference · #40**.
---
@@ -195,6 +195,12 @@ happening, and the turn structure that is still solitaire-shaped.
What is drawn and where, for a player at the board. The three items settled on 2026-08-30 shipped in
v0.7.9; what is left is the history panel and the end-of-game statistics.
- [ ] **#94** — **A Red Flag standing at an Office's Limits is drawn nowhere.** It is set out on the
board, it holds the next train from that side, and after the one log line announcing it there is
nothing on screen saying it is there. Confirmed in code rather than inferred. **The same class
as Gitea#21 and #22 — the engine is right and the screen is silent — and it is already on
Gitea#20 step 1's list, so doing it now is 0.8.0 groundwork.** See **Reference · #94**.
- [ ] **#44** — How much history the panel holds should be configurable. The cap is `slice(-60)` with
no recorded reason anywhere. **Where the setting lives is an open question and the item exists
to ask it** — a StartOS action, per game, or per browser. The replay viewer already answers the
@@ -455,12 +461,26 @@ below); reachable is not the same as correct. Early item for the next play sessi
#### #40 — A save from before v0.7.4 may not replay, and nobody has been told.
**A save from before v0.7.4 may not replay, and nobody has been told.** The Red Flags intent
changed shape, a make-up that was legal may now be refused, and a Yard Office arrival asks a
question no older history has an answer for. It fails safe — the server declines the save, names
the move and leaves the file untouched — and `WHISTLE-4086` did survive on `phoenix.local`, so it
is "may not" rather than "will not". Worth a line wherever the build is announced, and worth
knowing when a bug report arrives with a save that will not load.
**An older save may not replay, and players are not told so anywhere they will see it.** A save is
a list of moves and reopens by being re-played through the CURRENT rules, so any change that makes a
once-legal move illegal stops it there. **A deck change is the likeliest breaker** — a history
naming a card the deck no longer deals has no legal answer at all — but any narrowing does it. This
is the design working, not a fault: it fails safe every time, declining the load, naming the move
and leaving the file untouched.
**So this item is no longer "saves before v0.7.4"** and should never be restated per version
(Jesse, 2026-09-07). The specific 0.7.4 breakages — the Red Flags intent changing shape, a make-up
that was legal being refused, a Yard Office arrival asking a question no older history answers —
are recorded here as the ORIGIN of the rule rather than as the rule, and `WHISTLE-4086` did survive
on `phoenix.local`, which is why it is "may not" rather than "will not".
**What is actually owed:** the general rule is written down in `README.md` § Design notes, and a
line belongs wherever a build is announced. Nothing in code.
**Versioned, migratable replays are a post-1.0 question, deliberately deferred** (Jesse,
2026-09-07): "once we get to a solid 1.0 release we will consider a system to version the replays so
they are not as fragile — but not worth any effort right now." The reason it would be wasted effort
now is that every migration would be written against rules that change again next release.
### The common board, and watching play happen — Gitea#20
@@ -782,6 +802,39 @@ and whether they took it the moment their turn arrived.
### The screen
#### #94 — A RED FLAG STANDING AT THE LIMITS IS DRAWN NOWHERE.
**A RED FLAG STANDING AT AN OFFICE'S LIMITS IS DRAWN NOWHERE.** Found 2026-09-07 while checking
which of the Gitea#20 step 1 findings were still real. It is real, and it is a play bug now rather
than a common-board one.
**What the engine does**, traced rather than assumed. `maneuver.redFlags` emits `redFlagsSet`, whose
reducer sets `node.redFlag = side` on that Office's Division node (`apply.ts:2307`). From then on
`redFlagStop` holds the next train arriving from that side — `dest.redFlag === from` → `spendFlag`,
which removes the flag and emits `redFlagSpent` (`advance.ts:1216, 1173`). So it is a standing token
on the board that stops a train, exactly as a flag on the table would be.
**What the screen does.** `narrate` announces it once — "RED FLAGS set out on the Eastern Limits" —
and then it is gone with the scroll. The Office node in `DivisionView` carries `kind`, `label`,
`trains`, `capacity`, `modifiers`, `gradeUp`, `seat`, `running` and `switching`, and **no `redFlag`
field at all**; `redFlag` appears nowhere in `board-svg.ts` and nowhere in the web layer. The map
draws the Office, its A/D tracks and the trains standing on it, and not the flag at its Limits.
**So a player who set a flag out three Stages ago has nothing telling them it is still there, and an
opponent who missed the line never knew.** Then a train stops short, and the only explanation is a
log entry that has scrolled away. That is the shape of Gitea#21 — a correct refusal with no visible
reason — and of Gitea#22 — a board that does not show what the rules are acting on.
**Why it belongs before 0.8.0 rather than in it:** the plan already lists "add the Red Flag holder to
the public player projection — it is public game state but is currently absent from `Frame`" as part
of step 1. The field has to exist on the projection either way, so every hour spent on it is 0.8.0
groundwork rather than a detour. **The drawing is the open question, not the data** — a flag at the
Limits column is the obvious rendering, and `board-svg.ts` already draws those edges (`edge()`), so
there is somewhere to hang it.
**Not fixed. Sized: small on the data, a judgement call on the picture.**
#### #44 — The history panel's 60-line cap is hard-coded, and how much history it…
**The history panel's 60-line cap is hard-coded, and how much history it holds should be
@@ -995,6 +1048,17 @@ bitten once**: both published replays were dead — one got 42 intents into 360,
carry a ruleset stamp and the page should say "this replay was recorded under an older
ruleset and stops at Stage N" rather than presenting a truncated game as a whole one.
**THE FULL FIX IS DEFERRED PAST 1.0, ON JESSE'S CALL 2026-09-07:** "once we get to a solid 1.0
release we will consider a system to version the replays so they are not as fragile — but not worth
any effort right now." The reason is sound and worth keeping: every migration would be written
against rules that change again the next release, so the work would be redone rather than reused.
**What that ruling does NOT defer is the honesty.** A truncated replay presented as a whole game is
a wrong answer, not a missing feature, and saying "this stops at Stage N" needs no version stamp at
all — `fromSave` already knows how many intents it replayed of how many it was given
(`session.ts:454` reports exactly that for multiplayer). That half is small and stands alone. The
general rule now lives in `README.md` § Design notes; #40 is the "tell the players" half.
### Rules
#### #12 — PARTLY REPRODUCED: "when I back up to collect standing cars and, furth…
@@ -1685,9 +1749,20 @@ counts move with play balance, so a document that prints them is stale on the ne
same rule was applied to `content.ts`'s own comments on 2026-08-22 — see the pass recorded in
CHANGELOG for what came out and what was kept.
Not started. Worth deciding first whether this is a build step writing Markdown into `docs/`,
or a page on the site beside the replay viewer — the site can render it from the same view-model
the game uses, which argues for the second.
**BUILT 2026-09-07 in v0.7.9.2, completed in v0.7.9.3, as the first of the two options** — a build step writing Markdown,
`scripts/build-card-reference.ts` → `docs/rules/as-built.md`, with `test/card-reference.test.ts`
re-running the generator and failing when the checked-in file disagrees. All six sections are
there, and so is the honesty column: every Enhancement carries its `live` / `dormantSolo` /
`unbuilt` status, and the opponent-directed cards say plainly that none of them is dealt.
**The counts are omitted as ruled** — where a count matters it is rendered as a yes/no "is this
dealt at all", which is a fact about the design rather than about the current tuning.
**WHAT REMAINS, and it is the second option rather than a gap in the first:** a page on the site,
beside the replay viewer, rendered from the same view-model the game uses. The argument for it is
unchanged — a player cannot read a file in the repo — and it is now cheap, because the projection
work is done and only the presentation is missing. **Do it with Gitea#20 step 3**, which builds a
renderer for exactly this kind of read-only public page; building a second one first would be the
waste.
#### #86 — Real audio, as committed assets.
@@ -1719,37 +1794,29 @@ placeholder, not the finished sound. Sound therefore defaults to OFF.
- Keep the synthesised versions as the fallback for anything not sourced, so a missing file is
a quieter game rather than a broken one.
#### #88 — card-reference.md's industry table may still be stale beyond Grocer's…
#### #88 — CLOSED — card-reference.md's industry table is no longer anybody's source.
**`card-reference.md`'s industry table may still be stale beyond Grocer's Warehouse, the Oil
**CLOSED 2026-09-07 in v0.7.9.3, by removing the question rather than answering it.** This asked
Refinery and Freight House (corrected v0.5.0) — Mine Tipple, Produce Shed and Power Plant were
NOT re-verified.** The v0.5.0 pass corrected three rows (and the "Freight House is not a card"
claim across `card-reference.md`, `glossary.md`, `rules-v0.2.md` and `open-questions.md`) on
Jesse's explicit call. Checking `content.ts` while making that change turned up that
`mineTipple` and `powerPlant` are ALSO base 1 out/in + 1 Laborer in the engine — the same
uniform model as the three that were corrected — while `card-reference.md` still prints Mine
Tipple 3/3/4 and Power Plant 3/3/4, and the "Throughput — why these Laborer counts" section
right below the table is built entirely on those higher numbers. Flagged inline in
`card-reference.md` rather than silently rewritten — this needs the same kind of decision Jesse
made for the other three, not an assumption that the same correction applies, since raising or
lowering Laborer counts is also a balance question, not only a docs one.
whether `card-reference.md`'s Mine Tipple, Produce Shed and Power Plant rows were stale — it prints
3/3/4 for two of them while `content.ts` has every industry at base 1 out / 1 in / 1 Laborer — and
deliberately did NOT rewrite them, on the grounds that changing a Laborer count is a balance
decision rather than a documentation one. **That reasoning still stands and nothing was changed in
the engine.**
**v0.4.9e narrows it.** The Direction column for the Grocer's Warehouse and the Oil Refinery was
the wrong half of that v0.5.0 pass and has been put back to one-way each, from gameplay testing
and Jesse's confirmation. The *numbers* in those two rows are still the card reference's own
(1 Laborer, 3–4 track) and still unverified against the engine, so this entry stands as written
for all five industries — what changed is only that the two rows the v0.5.0 pass claimed to have
re-verified turn out to have been re-verified against a premise rather than against a card.
---
What changed is that `card-reference.md` is no longer where anyone looks. `docs/rules/as-built.md`
is generated from `content.ts` and carries the industry table the game actually runs; the old file
keeps its SUPERSEDED banner, now pointing forward, and its numbers are read as what the v0.4.5
placeholder said. **The balance question the entry was really guarding is #70** (the rolling stock
supply and the uniform 1/1/1 industry model, both marked provisional in `content.ts`) — that is
where it belongs, and it is still open.
## Done
Closed items, kept because several are the only record of a ruling or a lesson. Newest first within
each group.
### Shipped through v0.7.9.2, from the queue
### Shipped through v0.7.9.3, from the queue
Closed items, newest first. Kept because several of them are the only record of a ruling or a lesson;
the numbers stay so cross-references above and below still resolve.
@@ -1757,12 +1824,11 @@ the numbers stay so cross-references above and below still resolve.
32. ~~**Tell the 0.4.9 playtesters their saves are dead, before they find out.**~~ — done
2026-09-07. `PLAYTEST-0.7.4.md` was written for exactly this and did its job; Jesse, 2026-09-07:
"a temporary document to help some of the playtesters out on making the big jump, but that is no
longer needed." The jump is made, so the note is retired rather than committed. **The durable
fact, which is why this entry stays:** a save written by v0.4.9h does not replay on 0.7.x — the
deck changed, so it stops at its first `card.play` — and that includes the saves attached to
Gitea#15 and #17. It fails safe, naming the move and leaving the file untouched, so a bug report
arriving with a save that will not load is this and not a new fault. #40 is the same shape on the
main line and is still open.
longer needed." The jump is made, so the note is retired rather than committed. **No per-version
save-compatibility fact is tracked from here**, on Jesse's call 2026-09-07: a save replays
through the current rules, so an older one breaking is the design working rather than an event
to log each time. The general rule lives in `README.md` § Design notes; #40 is the same shape and
has been generalised to match.
92. ~~**Two multiplayer information leaks in the shared narration log.**~~ — done 2026-09-07 in
v0.7.9.2, found while planning Gitea#20 step 1. The **seed** was announced in the opening line of