clay-borg/workplans/CB-WP-0027-the-commentary-track.md

355 lines
16 KiB
Markdown
Raw Normal View History

CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
---
id: CB-WP-0027
kind: product
title: "The commentary track: the meta view beside the table, and what the player says while playing"
CB-WP-0027 T05: evidence — and the one question this file cannot answer CB-EV-0025. T05 asks whether a comment actually got written during play, unprompted. It has not been, and that is the pass's real acceptance test. Everything built here is evidence the mechanism works; none of it is evidence it gets used. I exercised it 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. Stated plainly rather than answered optimistically, because CB-WP-0022 built a register whose first run found ten answers nobody had collected: this project's failures are about whether things get READ. ADR-0007 D5 turned out to be the easy part. PointerFact::parse already refuses any unrecognised field, so a comment could not reach the command path even by accident. That is strictness at a boundary paying off years early, and the clearest instance in this project of it being cheaper than vigilance across one. esc() held against its first hostile input, 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 in the output" while destroying the note. The reflow cost no coverage probe -- all 41 pre-existing render tests passed through a full restructure into two columns. CB-WP-0024 saw the opposite when a probe was tied to a rendering ("17 remaining"), and the contrast is the useful part: a probe that names a FACT survives a reflow, a probe that names a PRESENTATION does not. Running it changed the design for the third pass running, after CB-WP-0024's role column and CB-WP-0025's K=2 infeasibility. The pattern is consistent enough to state: the ADRs in this project are good at deciding and poor at predicting, and the loop's value comes disproportionately from the code loop rather than from steps 1-3. Chaos window 2 closes with zero overrides in twelve declarations, so its retirement condition is untestable -- final now rather than projected. The window's verdict should be that d8 made the mechanism unevaluable. Also recorded rather than dismissed: make all failed once at env-test and passed on re-run. A cb-play server and several cargo processes were running concurrently, which is the likely cause -- but "likely" is doing work in that sentence and I did not chase it. A gate that fails intermittently and is re-run until green is a gate being trained not to matter. Not built, and named: promoting a comment to a register note is still manual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:39:12 +02:00
status: done
state_hub_workstream_id: "e011da8d-de9e-48bf-a3f2-a9e715ef222f"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
---
# 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/<date>.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
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
status: done
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
priority: high
state_hub_task_id: "3586f3af-ad45-4bc6-bea2-b6eaedf05fbf"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
```
`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/<date>-<slug>.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.
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
**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.
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
## Task: the table on the left, the meta on the right
```task
id: CB-WP-0027-T02
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
status: done
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
priority: high
state_hub_task_id: "0971185c-b72c-43ec-a356-c221ef6d8165"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
```
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.
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
**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.
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
## Task: capture what the player says, bound to where they said it
```task
id: CB-WP-0027-T03
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
status: done
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
priority: high
state_hub_task_id: "4a23ba0d-9b23-4381-b6b0-2b8959106270"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
```
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 `<script>` renders as text**, asserted on the
emitted document — the first real test of `esc()` against hostile input
rather than against suit names;
- a comment is bound to the position per T01's decision, and a test
asserts the binding **resolves**: a stored comment names a state that
the recording can reach;
- the empty comment is not stored — a blank note is noise in the register;
- **the game is playable with comments disabled**, and a test says so. The
commentary track must not become a dependency of playing.
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
**Done 2026-08-06.** `Note` in `input.rs`, a `/note` route, `--trial`, and
`trials/<date>-<slug>.md` written whole on every note.
**The separation is structural, and tested anyway.** A note whose text is
a well-formed pointer fact (`down=action-solve&up=problem-1`) parses as a
note, is stored verbatim, and **the command channel refuses the same body
outright** — with the inverse asserted too, so the test is not vacuous.
**A plain `<form method="post">`**, so the comment box works with the
script disabled. The command channel needs JavaScript because a drag is
not a form submission; a comment is one, and making it depend on the
script would add a failure mode for nothing. The reply is **303 See
Other**, so a reload does not re-post the note.
**Verified over real HTTP**, not only in tests: two columns served, a note
posted (303), a hostile note stored as text (303), an empty note refused
(400), and the game did not advance.
**`esc()`'s first hostile input.** `<script>alert(1)</script>` renders
escaped **and still readable** — the test asserts the player's words
survive, because escaping that eats the text is its own defect.
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
## Task: surface them, or this pass has failed
```task
id: CB-WP-0027-T04
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
status: done
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
priority: high
state_hub_task_id: "df8afbef-6ff7-4aa2-9ff7-b3fa05f676cc"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
```
**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.
CB-WP-0027 T01-T04: the commentary track The meta view beside the table, and a note channel that provably cannot carry a move. T01 (ADR-0014). ADR-0007 D5 is SCOPED, NOT AMENDED, and the reason it was easy is that PointerFact::parse already refuses any unrecognised field -- a comment could not reach the command path even by accident. So /command carries pointer facts, /note carries text, and Note has no code path to GroundCommand. Comments live in trials/<date>-<slug>.md, not in ScenarioFile: a scenario is executed, replayed and hashed, and prose in it is data the runner must ignore, which is how a format rots. The state hash binds; round and step are for reading. And the retention question, decided before any comment was written: RAW NOTES NEVER LEAVE clay-borg. A note reaches ground-game only by being promoted to a register finding, by a human, with the wording chosen then -- "the DARVO sequence is infuriating" is useful signal and a bad way to open a message to the game's designer. T02. CSS grid, minmax(0,1fr) on both tracks -- load-bearing, because a grid child defaults to min-content width and without it the SVG table refuses to shrink and pushes the meta column off-screen, looking correct on the developer's monitor and broken everywhere else. Single-column fallback under 64rem. The running tally moved into the panel so it is visible WHILE PLAYING; it only appeared on the ending page before, and a score you see once the game is over informs nothing. T03. A plain <form method="post">, so the box works with the script disabled; the command channel needs JavaScript because a drag is not a form submission, a comment is one. 303 See Other so a reload does not re-post. esc()'s first hostile input: <script>alert(1)</script> renders escaped AND STILL READABLE -- escaping that eats the player's words is its own defect. Verified over real HTTP: note posted 303, hostile note stored as text, empty note refused 400, game did not advance. T04. tools/trials.py and make trials. THE REPORT'S DESIGN CHANGED BECAUSE I RAN IT: the first version called any note without a recording an orphan, so a live session reported every note as broken -- 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 / orphan, and only orphan is a target-0 number. The self-test exercises the REPORTING path, not just the parser, because design-baseline.py had a green self-test and an unexercised reporting path and that is where it rotted. And a latent Makefile defect surfaced: make trials did nothing, because trials is also a directory and 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. make all: exit 0. 49 render tests, 26 cb-play, loop-lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:37:46 +02:00
**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.
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
## Task: evidence
```task
id: CB-WP-0027-T05
CB-WP-0027 T05: evidence — and the one question this file cannot answer CB-EV-0025. T05 asks whether a comment actually got written during play, unprompted. It has not been, and that is the pass's real acceptance test. Everything built here is evidence the mechanism works; none of it is evidence it gets used. I exercised it 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. Stated plainly rather than answered optimistically, because CB-WP-0022 built a register whose first run found ten answers nobody had collected: this project's failures are about whether things get READ. ADR-0007 D5 turned out to be the easy part. PointerFact::parse already refuses any unrecognised field, so a comment could not reach the command path even by accident. That is strictness at a boundary paying off years early, and the clearest instance in this project of it being cheaper than vigilance across one. esc() held against its first hostile input, 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 in the output" while destroying the note. The reflow cost no coverage probe -- all 41 pre-existing render tests passed through a full restructure into two columns. CB-WP-0024 saw the opposite when a probe was tied to a rendering ("17 remaining"), and the contrast is the useful part: a probe that names a FACT survives a reflow, a probe that names a PRESENTATION does not. Running it changed the design for the third pass running, after CB-WP-0024's role column and CB-WP-0025's K=2 infeasibility. The pattern is consistent enough to state: the ADRs in this project are good at deciding and poor at predicting, and the loop's value comes disproportionately from the code loop rather than from steps 1-3. Chaos window 2 closes with zero overrides in twelve declarations, so its retirement condition is untestable -- final now rather than projected. The window's verdict should be that d8 made the mechanism unevaluable. Also recorded rather than dismissed: make all failed once at env-test and passed on re-run. A cb-play server and several cargo processes were running concurrently, which is the likely cause -- but "likely" is doing work in that sentence and I did not chase it. A gate that fails intermittently and is re-run until green is a gate being trained not to matter. Not built, and named: promoting a comment to a register note is still manual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:39:12 +02:00
status: done
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
priority: medium
state_hub_task_id: "c0beb2d6-f998-4c36-aafa-c9ebd10c5086"
CB-WP-0027: the commentary track — declare and register The maintainer's ask: game interface left, meta and commentary right, with comments bound to game state so a test player's perception, confusion and reasoning become reviewable signal. Declared M (chaos d8=7, no override, declaration 10 of window 2). It touches two canonical interfaces: ADR-0007 D5's input contract, which currently admits only raw pointer facts, and the recorded-session format, which is deny_unknown_fields -- CB-WP-0026 just proved that bites. FRAMED AS GameDesign §5's TRIAL PROTOCOL MADE ERGONOMIC, because that is what it is. The protocol already exists and nobody would use it as written: it asks the player to reconstruct their reasoning afterwards, from memory, in a sibling file. A comment typed while the position is on screen is contemporaneous, bound to something replayable, and captures the one class of signal no gate in this project can see -- confusion and frustration. The reason it is worth more than a comment box: GameDesign §3.1 makes a note unreportable until it has a reproduction, and three findings are stuck there now (F12, F14, F15). A comment bound to a recorded state, plus the recording, is the cheapest reproduction a play-derived observation can have. So the pass closes the register's input path for findings that come from playing rather than from building. The failure it must not reproduce is named up front: a pile of prose nobody reads is this project's signature defect -- the four-day unread message, the ten uncollected rulings. Storage is not the deliverable, surfacing is, and T04 carries it. T01 (ADR-0014) must decide where comments live, what the page may send (D5's transport widens from two ids to arbitrary user text -- amended or merely scoped?), what binds a comment to a position, and the retention question this project has not faced: these are the maintainer's own words about his own game, one will eventually be unflattering, and whether they travel to ground-game needs deciding before any are written. T03 notes that esc() is about to face its first hostile input -- until now it has escaped suit names. loop-lint: no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:18:35 +02:00
```
`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.
CB-WP-0027 T05: evidence — and the one question this file cannot answer CB-EV-0025. T05 asks whether a comment actually got written during play, unprompted. It has not been, and that is the pass's real acceptance test. Everything built here is evidence the mechanism works; none of it is evidence it gets used. I exercised it 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. Stated plainly rather than answered optimistically, because CB-WP-0022 built a register whose first run found ten answers nobody had collected: this project's failures are about whether things get READ. ADR-0007 D5 turned out to be the easy part. PointerFact::parse already refuses any unrecognised field, so a comment could not reach the command path even by accident. That is strictness at a boundary paying off years early, and the clearest instance in this project of it being cheaper than vigilance across one. esc() held against its first hostile input, 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 in the output" while destroying the note. The reflow cost no coverage probe -- all 41 pre-existing render tests passed through a full restructure into two columns. CB-WP-0024 saw the opposite when a probe was tied to a rendering ("17 remaining"), and the contrast is the useful part: a probe that names a FACT survives a reflow, a probe that names a PRESENTATION does not. Running it changed the design for the third pass running, after CB-WP-0024's role column and CB-WP-0025's K=2 infeasibility. The pattern is consistent enough to state: the ADRs in this project are good at deciding and poor at predicting, and the loop's value comes disproportionately from the code loop rather than from steps 1-3. Chaos window 2 closes with zero overrides in twelve declarations, so its retirement condition is untestable -- final now rather than projected. The window's verdict should be that d8 made the mechanism unevaluable. Also recorded rather than dismissed: make all failed once at env-test and passed on re-run. A cb-play server and several cargo processes were running concurrently, which is the likely cause -- but "likely" is doing work in that sentence and I did not chase it. A gate that fails intermittently and is re-run until green is a gate being trained not to matter. Not built, and named: promoting a comment to a register note is still manual. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 10:39:12 +02:00
**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.