--- id: CB-WP-0027 kind: product title: "The commentary track: the meta view beside the table, and what the player says while playing" status: done state_hub_workstream_id: "e011da8d-de9e-48bf-a3f2-a9e715ef222f" --- # Purpose ``` structural tier M (touches two canonical interfaces — ADR-0007 D5's input contract, which currently admits only pointer facts, and the recorded-session format, which is `deny_unknown_fields`) chaos d8 = 7 → no override declared tier M ``` Declaration 10 of chaos window 2. Tier M: survey and decision merged into one document; adversarial review optional. ## The maintainer's ask > *"Extend the UI to provide the evaluation meta view to the game. Game > interface to the left, meta and commentary to the right. As a test > player I can provide comments about how I perceive the game or why I am > playing a move — signal about unclear situations, frustration etc. — > bound to the game state, reviewable for potential optimizations."* ## This is `GameDesign.md` §5's trial protocol, made ergonomic **The protocol already exists and nobody would use it as written.** A trial game is a `--record`ed session plus a sibling `trials/.md` written *afterwards*, from memory. The spec says it "must cost almost nothing or it will not be done" — and then asks the player to reconstruct their own reasoning after the fact. A comment typed **while the position is on screen** is better in three ways that matter to this project: 1. **It is contemporaneous.** *"Why am I playing this"* is available for about ten seconds and then it is gone. 2. **It is bound to a position**, so it points at something replayable rather than at a memory. 3. **It captures the class of signal nothing else here can.** Confusion, frustration, and *"I don't understand what this does"* are invisible to every gate in the project, and they are the observations that produce design findings rather than engine findings. ## Why this is worth more than a comment box **GameDesign §3.1 makes a note unreportable until it has a reproduction.** Three findings are stuck there right now: F12 (GR-A13 wasted SOLVE), F14 (GR-E03/E04 never played) and F15 (the series question). Each is real and none can go to `ground-game`. **A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have.** *"This felt pointless"* attached to a replayable position is a different artifact from *"this felt pointless"* in a file — the reader can go and look. So the pass is not "add a text box". It is **closing the register's input path for findings that come from playing rather than from building.** ## The failure this must not reproduce **A pile of prose nobody reads is this project's signature defect.** The message that sat unread for four days; the ten rulings that arrived and were never collected. A commentary feature that stores comments and surfaces them nowhere is that failure in a new medium, and it would be the third instance. > **Storage is not the deliverable. Surfacing is.** T04 carries this and no task may be called done while comments are write-only. ## Task: decide where comments live and what the page may send ```task id: CB-WP-0027-T01 status: done priority: high state_hub_task_id: "3586f3af-ad45-4bc6-bea2-b6eaedf05fbf" ``` `decisions/ADR-0014-*.md` (tier M merges survey and decision). **Three questions, and the second is the one that bites.** **(a) Where does a comment live?** Candidates: inside the recorded `ScenarioFile`; in the `trials/-.md` sibling `GameDesign.md` §5 already specifies; or a third file keyed to the recording. Note CB-WP-0026 added four fields to `ScenarioFile` and the schema is `deny_unknown_fields` — **a scenario is a game record, and mixing a player's prose into it may be the wrong shape.** Say which and why. **(b) What may the page send?** ADR-0007 **Decision 5** currently binds the browser to *raw pointer facts* — `down on id`, `up on id` — and forbids JavaScript from constructing commands. **A comment is not a command**, so D5 is arguably untouched; but the transport widens from two ids to arbitrary user text, and that must be *decided* rather than assumed. State whether D5 is amended or merely scoped, and what stops the new channel from becoming a command channel later. **(c) What binds a comment to the game?** Round and step are readable but weak. **The end-state hash is the strong binding** — it is what makes a session comparable to its replay (`Summary::end_state_hash`), so a comment keyed to a hash points at a position a reader can reach. Decide whether the binding is the hash, the command index, or both, and what happens to a comment whose position no longer replays. **Also decide the retention question**, because it is a boundary this project has not faced: these comments are **the maintainer's own words about his own game**, and one of them will eventually be unflattering about the design or about the engine. State whether they are private to the repo, whether they travel to `ground-game`, and who decides. **Done 2026-08-06.** [ADR-0014](../decisions/ADR-0014-the-commentary-track.md), six decisions. **(b) turned out to be the easy one, because the control already exists.** `PointerFact::parse` **refuses any unrecognised field**, so a comment cannot reach the command path even by accident. **D5 is scoped, not amended**: `/command` carries pointer facts, `/note` carries text, and `Note` has **no code path to `GroundCommand`** — `resolve()` takes a `PointerFact` and nothing else. The control is a test that posts a note whose body is a well-formed pointer fact and asserts the game does not advance; without it, "separate channels" is a claim about code layout. **(a) the trial log, not the scenario** — and the reason is not strictness. A scenario is executed, replayed and hashed; prose in it is data the runner must ignore, which is how a format rots. The log reuses `FindingRegister.md`'s idiom: a table between HTML-comment markers, so `design.py`'s parser is the model. **(c) the state hash binds; round and step are for reading.** An orphaned comment — hash no longer in its recording — is **reported, not deleted**, same reasoning as `gr-e01`'s rewrite and a green reproduction's alarm. **Retention: raw comments never leave clay-borg.** A comment reaches `ground-game` only by being promoted to a register finding, by a human, at which point the wording is chosen deliberately. *"The DARVO sequence is infuriating"* is useful signal and a bad way to open a message to the game's designer. **The maintainer decides per comment** — not a rule, not a default, not the agent. ## Task: the table on the left, the meta on the right ```task id: CB-WP-0027-T02 status: done priority: high state_hub_task_id: "0971185c-b72c-43ec-a356-c221ef6d8165" ``` Two columns: the game as it is today on the left, a meta panel on the right. The log moves into the right column — it is commentary on the game, not part of the table. **What goes in the meta panel**, beyond the log and the comment box: - the **session tally** (CB-WP-0024 T04), which currently only appears at the end; - the **register's open notes** for this game, if any — the point of the panel is that the player can see what has already been observed; - whatever CB-WP-0025's witness produces, **when it is wired** — it is not yet, and this task does not wire it. **Controls:** - the layout must survive a narrow viewport — a two-column desktop layout that unreadably overlaps on a laptop is a regression, and the existing page is responsive by accident rather than by design, so this is the moment it becomes deliberate; - **every existing coverage probe must still pass.** `RENDERED` in `lib.rs` asserts each view field appears in the *parsed* document; a reflow that drops one fails the build, and that is the gate working; - the game column must be usable with the meta column collapsed. A player who does not want to write anything must not be worse off. **Done 2026-08-06.** CSS grid, `minmax(0,1fr)` on both tracks and a single-column fallback under 64rem. **`minmax(0, …)` is the load-bearing part.** A grid child defaults to min-content width, so without it the SVG table refuses to shrink and pushes the meta column off-screen instead of narrowing — the layout would look correct on the developer's monitor and be broken everywhere else. **The running tally moved into the panel**, so it is visible *while playing*. It only appeared on the ending page before, and a session score you see once the game is over informs nothing. Three tests: the log is in the meta column and the table is not; an empty panel draws **no furniture** (with the inverse, so it does not pass for a panel that never renders); and the narrow-viewport rule exists — asserted on the stylesheet, which is **weaker than laying it out**, and said so rather than dressed up. ## Task: capture what the player says, bound to where they said it ```task id: CB-WP-0027-T03 status: done priority: high state_hub_task_id: "4a23ba0d-9b23-4381-b6b0-2b8959106270" ``` A comment box in the meta panel, posting to the existing guarded endpoint. **The escaping is not incidental.** `esc()` exists and every interpolated value goes through it, with a comment in the source saying *"cannot currently carry a `<`" is how injection bugs are written*. This is the first user-authored free text the page has ever handled, so it is the first time that comment is load-bearing rather than precautionary. **Controls:** - **a comment containing `` renders escaped **and still readable** — the test asserts the player's words survive, because escaping that eats the text is its own defect. ## Task: surface them, or this pass has failed ```task id: CB-WP-0027-T04 status: done priority: high state_hub_task_id: "df8afbef-6ff7-4aa2-9ff7-b3fa05f676cc" ``` **The load-bearing task.** Comments must be readable *outside* the session that produced them, by a command, without opening a file by hand. - `make design` (or a sibling) reports comments **with their positions**, because a comment without its position is prose again; - a comment can be **promoted to a register note** — that is the input path this pass exists to close, and it should cost one step; - **age is reported.** GameDesign §3.1 expires notes at 30 days on the existing `PROVISIONAL_WARN_DAYS` machinery; comments should not get a private mechanism. **Controls:** - a comment written in a session is visible from a command in a *later* session — the whole failure mode is signal that only exists while someone is looking at it; - **the reporting path is exercised by the self-test**, not only the parsing. `design-baseline.py` had a self-test and a reporting path with no CI, and the reporting path was where it rotted (ADR-0012 D8); - promoting a comment to a note produces a register row that `tools/design.py` accepts — if the register cannot express it, the shape is wrong and that is a finding about ADR-0012, not a bug. **Done 2026-08-06.** `tools/trials.py`, `make trials`, wired into `make self-tests`. **The report distinguishes three states, and that distinction came from running it.** The first version called any note without a recording an **orphan** — so a live session reported every note as broken, because the recording is only written at game end. A metric that cries wolf is one nobody reads, **which is the exact failure this pass exists to prevent**. Now: `ok`, `pending` (no recording yet), `orphan` (the recording exists and the position is not in it). Only the third is a target-0 number. **The self-test exercises the reporting path**, not just the parser — it runs `report` against a fixture and asserts on what it printed. `design-baseline.py` had a green self-test and an unexercised reporting path, and that is where it rotted (ADR-0012 D8). **A latent Makefile defect surfaced.** `make trials` did nothing: `trials` is also a *directory*, so Make saw an up-to-date file. `design`, `difficulty` and `trials` — added by CB-WP-0022, CB-WP-0025 and this pass — were **all** missing from `.PHONY`; only the one that collided revealed it. All three are declared now. ## Task: evidence ```task id: CB-WP-0027-T05 status: done priority: medium state_hub_task_id: "c0beb2d6-f998-4c36-aafa-c9ebd10c5086" ``` `evidence/CB-EV-0025-*.md`. - **Whether a comment actually got written during play**, by the maintainer, unprompted. The protocol's own test is whether it costs little enough to use; anything else is a claim about a feature nobody exercised. - **Whether any stuck note moved.** F12, F14 and F15 are unreportable for want of a reproduction; if the commentary path unblocks even one, that is the pass's real result. - **What the escaping test found**, since it is the first hostile input this renderer has seen. - **Whether the two-column layout cost any coverage probe**, and what that says about how tightly the gate is coupled to the document's shape. - **Quote CB-WP-0025's cost by re-running the instrument** — and note that CB-EV-0019 §4's unbounded chain is still unbounded. **Done 2026-08-06.** [CB-EV-0025](../evidence/CB-EV-0025-the-commentary-track.md). **The first bullet is unanswered, and that is the honest result.** Nobody has written a note in anger. I exercised the mechanism myself, which proves the plumbing and proves nothing about the ergonomics — **the person who wrote the feature is the worst possible witness to whether it is worth using.** The protocol's claim is that it must cost almost nothing or it will not be done, and that remains untested. - **ADR-0007 D5 was the easy part**, because `PointerFact::parse` already refuses unrecognised fields — strictness at a boundary paying off years early, and the clearest instance of it in this project. - **`esc()` held**, and the test asserts the harder half: the player's words are still *readable* after escaping. An escaper that dropped the text would pass "no script tag" while destroying the note. - **The reflow cost no coverage probe** — all 41 tests passed through a full restructure. CB-WP-0024 saw the opposite when a probe named a *rendering* rather than a *fact*, and the contrast is the useful part. - **Running it changed the design for the third pass running** (after CB-WP-0024's `role` column and CB-WP-0025's `K=2`). The ADRs here are good at deciding and poor at predicting. - **Chaos window 2 closes with zero overrides**, so its retirement condition is untestable — now final rather than projected. **Not built, and named:** promotion of a comment to a register note is still manual. `make trials` reports; it does not promote.