v0.8.0 — the board replays what everyone else did, instead of arriving rearranged

TODO #13, #15 and #18 — Gitea#20 steps 2-4 pointed at a seated player's own screen.
Every accepted intent, and every automatic phase that does anything, becomes an
ordered presentation step. A bot's whole switching turn used to land in one push;
now it arrives as a run of steps, the district panel follows whoever is acting,
and a [N behind] … [Skip] row says how far the board is from the game.

Solitaire runs the same path — one collector inside submit(), which both session
kinds already funnel through — which is where its automatic phases finally get a
visible beat.

Dwell is assigned by kind: switching holds the screen, turn bookkeeping costs
nothing, and the clock turning over earns the beat. Tunable per viewer without a
rebuild, and off entirely at pace 0.

Also: switching was the one class of action logging unattributed, and now names
its train. Reasoning, measurements and the three things that turned out wrong are
in CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6cF1iYvJ1kNmzYBzu4QX6
This commit is contained in:
Jesse.Markowitz
2026-09-09 15:31:46 -04:00
co-authored by Claude Opus 5
parent 312e0301e0
commit 02289e94b8
25 changed files with 2669 additions and 145 deletions
+60 -18
View File
@@ -128,32 +128,72 @@ specific paths below have not been exercised at a table. **More testing is plann
## The common board, and watching play happen — Gitea#20
**This is v0.8.0.** One shared, seatless display of the public game, usable on a TV or in OBS on its
own and publishable into the table's Jitsi meeting. The plan is `docs/plans/jitsi-common-board.md`,
seven steps, of which 1-4 are the useful release and 5-7 are the Jitsi publisher.
One shared, seatless display of the public game, usable on a TV or in OBS on its own and publishable
into the table's Jitsi meeting. The plan is `docs/plans/jitsi-common-board.md`, seven steps.
**THE RELEASE SPLIT, settled with Jesse 2026-09-09. Jesse: "13 is the key. Watching on a TV is the
bonus."**
- **v0.8.0 — #13, #15 and #18: the watchable table.** The step collector, steps on the `Session`
interface, **foreign-district rendering**, the client animation queue, pacing by kind, and the
behind-counter. **The design is the plan's § v0.8.0**, which supersedes the parts of steps 2-4 it
covers. Needs no HTTP work at all.
- **v0.8.1 — the seatless board page.** `display.json`, `viewToken`, `/api/display/stream`,
`/display.html`, an all-districts layout: most of step 2 and step 3 without its canvas. Cheap once
0.8.0 lands, and nothing in it moves #13 forward.
- **v0.9.0 — the Jitsi publisher.** Steps 5-7 plus step 3's canvas pipeline. Held off deliberately:
it needs Chromium in the image (several hundred MB onto a 63 MB `.s9pk`) and measurement on
`phoenix.local`, and the only self-hosted Jitsi available needs an authenticated moderator to open
a room, so "waiting for moderator" is the ordinary path here rather than an edge case.
**The correction that set that split:** `Frame.cells` is ONE district — the viewer's own
(`view.ts:510`, from `areaOf(s, viewer)`), exactly as **Reference · #13** already said. So a step
stream alone does not answer #13; the data would arrive with nowhere to be drawn. Rendering a
district you do not own is the core of 0.8.0, not part of the seatless page. Two other decisions
taken with it: **no WebSocket and no new runtime dependency** (SSE down + POST up, the pattern
`server/http.ts` already uses), and `protocolVersion` on the wire in 0.8.0.
**Step 1 is BUILT** — v0.7.9.2 through v0.7.9.5 (#91, #92, #95, #97), with one item struck off
rather than implemented (#103). **The plan was reconciled against the code in v0.7.9.8** and now says
which of its "current code findings" are history: it had drifted badly enough to send the next reader
fixing things twice. Steps 2-7 were never implemented and their findings have NOT been re-verified —
check each before building on it. See **Reference · #103**. Items that look
like screen polish live here because they need step 4's ordered presentation mechanism and nothing
cheaper.
rather than implemented (#103). **The plan was reconciled against the code in v0.7.9.8** and again
on 2026-09-09, and now says which of its "current code findings" are history: it had drifted badly
enough to send the next reader fixing things twice. Steps 2-7 were never implemented; step 4's
findings were re-verified 2026-09-09 and steps 5-7's were NOT — check each before building on it.
See **Done · 103**. Items that look like screen polish live here because they need step 4's ordered
presentation mechanism and nothing cheaper.
**The design is settled — the plan's § v0.8.0 is the authority.** In outline: public steps animate
the board while the existing private Push supplies hand, menu and objective (so there is no new
redaction surface); the collector is a shared `sim/` module both `LocalSession.submit()` and
`GameSession.submit()` call, so solitaire and multiplayer run one code path; dwell is assigned **by
kind** with switching protected at 1s and bookkeeping at zero; and a `[N behind] … [Skip]` row shows
the lag, carries #15's caption, and never blocks input. Two measurements that decided it: a
switching turn runs to the engine's cap of **6 moves** (bursts of 14, 6, 6, 6 in `seed-1917398`),
and dwell-by-kind costs ~40s of animation across a 60-stage game against 3.6 minutes for a flat
700ms.
- [ ] **#13** — I cannot see what the other players did — bots included. **Settled 2026-08-29 as the
harder reading**: not log legibility but the ordered, per-action presentation of everyone else's
turns. Jesse: "It's not fun to do my turn and have magic happen in the background." This is
Gitea#20 step 4 pointed at a player's own screen. See **Reference · #13**.
Gitea#20 step 4 pointed at a player's own screen. **DESIGNED 2026-09-09 — the plan's § v0.8.0.**
The answer is foreign-district rendering with focus following the actor; without it a step
stream has nowhere to draw, because `Frame.cells` is the viewer's district alone. See
**Reference · #13**.
- [ ] **#15** — A "most recent action" line under the status block. The text already exists and is
already correct — this is placement, not content. **Decide with #13**: in solitaire "most
recent" is the right unit; in multiplayer what you missed is everything that happened while you
were WAITING. See **Reference · #15**.
were WAITING. **DESIGNED 2026-09-09 — it is the caption in the `[N behind] … [Skip]` row, not a
separate line.** The queue IS "everything that happened while you were waiting", which is the
unit this entry could not choose. See **Reference · #15**.
- [ ] **#18** — Give every phase a visible beat. Not a timing problem — `pump` runs every automatic
phase before the page renders once, so they are never drawn at all. **A `sleep` fixes nothing;
it needs the async stepped pump that Gitea#20 step 4 specifies**, which is why it lives here
rather than under The screen. See **Reference · #18**.
rather than under The screen. **DESIGNED 2026-09-09 — it is a dwell setting on the shared queue,
not a feature.** Phases where nothing happened dwell at ZERO (Jesse: "if nothing happens during
a phase then we shouldn't lose time to it"); a flat second per phase is rejected on the same
arithmetic this entry already worked out. Solitaire gets it through `LocalSession`, the same
path multiplayer gets #13 through. See **Reference · #18**.
- [ ] **#75** — Let the game join a call and talk to the table — the chat, audio and nudge half of the
idea Gitea#20 took the visual half of. Long-term. See **Reference · #75**.
@@ -1761,7 +1801,7 @@ where it belongs, and it is still open.
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.4, from the queue
### Shipped through v0.7.9.8, 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.
@@ -1996,11 +2036,13 @@ the numbers stay so cross-references above and below still resolve.
`# fail 0`. `pretest` is `tsc --noEmit && node scripts/build-web.ts` now, and the same planted
error exits 1 with the tests never running.
**Why it mattered THIS week rather than generally.** v0.8.0 is steps 2-7 of the common board —
a display stream, credentials, persistence, and a Chromium supervisor — which is almost
entirely `src/server/` and is exactly the half the test command could not see. Found while
answering "anything else before 0.8.0", which is the only reason it was found at all: nothing
about a green suite would ever have said so.
**Why it mattered THIS week rather than generally.** The next release is steps 2-4 of the
common board — a display stream, credentials, persistence and the display-step collector, which
is almost entirely `src/server/` and is exactly the half the test command could not see. (The
Chromium supervisor was in this list when the entry was written; steps 5-7 became v0.9.0 on
2026-09-09, and the point stands without it.) Found while answering "anything else before
0.8.0", which is the only reason it was found at all: nothing about a green suite would ever
have said so.
103. ~~**The common-board plan had drifted from the code it is the source for.**~~ — done 2026-09-07
in v0.7.9.8. `docs/plans/jitsi-common-board.md` was written 2026-08-27, still said "No