CB-WP-0024: the table you can watch
Some checks failed
ci / check (push) Has been cancelled

Four of the maintainer's five playtest remarks. Three of the five turned
out to be data the projection already carried, rendered as text -- the
table's problem was legibility, not content, and the coverage gate passes
either way because it proves nothing is OMITTED, not that anything is
readable. That gap is named in the evidence rather than closed: the honest
control is a person playing it.

T01. The ending control was two defects wearing one button. The label said
"close -- I have read this" while hotseat.rs reads `done` as STOP THE
SERVER, and acknowledging it changed nothing -- the tab kept a full table
and a `play again` pointing at a closed port. Now labelled by its effect,
and the page seals itself on the `closed` reply: removeAttribute on every
control's data-drop, so they stop being droppable by the same rule that
made them droppable. removeAttribute rather than setAttribute(_, null),
which writes the truthy string "null" in a browser.

The reason it survived is structural. jsrun's fetch stub returned
{then: function(){return this}} and never invoked its callbacks, so every
line of the script reacting to the server was unreachable from every test
in this project -- a page that ignores the server was indistinguishable
from one that acts on it. Same finding as CB-WP-0016's "a stub too thin to
express a failure is how the failure survives", one layer deeper, at the
reply. The stub now delivers a real then-chain; gesture_with_reply reports
surviving controls; the seal is mutation-proven and a negative control
asserts `ok: dealing` does NOT seal.

T02. Draw and discard as offset stacks with counts. The shuffle question
the task required settling: it already works, at
games/ground/src/lib.rs:1419-1435, implementing the U4 default that
ground-game confirmed 2026-08-03. Nothing raised. The piles show the state
before it fires, which is derivable from the view; a claim that a
reshuffle HAS happened is not, and is not made. CB-WP-0026 applied that
ruling the same day this consumed it -- first time answering "is this
underdetermined?" was one lookup instead of a message.

T03. Each seat's play drawn as a card, sentence kept beside it. The
face-down back is a const with no parameters: SelectionView::Hidden
carries nothing, so there is no data path into the back to add later. The
leak test copies view.rs's own shape -- identical backs across two
different hidden situations, THEN assert a revealed play does show,
because without the second half the first passes for a renderer that draws
nothing.

T04. MatchTally lives in `play`, beside the listener and the seed. What
"cumulative" means was decided before anything was summed, and the answer
is that GROUND defines one game and no series: summed personal score and
games-won answer different questions, and a test asserts they can point at
different seats. Both shown, both labelled. Registered F15 as a NOTE --
the test shows the tallies can differ, which is arithmetic, not evidence
the ambiguity harms play, so GameDesign §3.1 bars reporting it. First use
of the note tier since D6 wrote it, and it came from building rather than
from play.

make all: exit 0. 41 render tests, 26 cb-play tests, loop-lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-05 17:32:48 +02:00
parent 1edb10d5a3
commit 8d58568013
7 changed files with 644 additions and 14 deletions

View file

@ -2,7 +2,7 @@
id: CB-WP-0024
kind: product
title: "The table you can watch: the piles, the other seats' moves, the score that carries"
status: active
status: done
state_hub_workstream_id: "5f17b6f9-cd4b-4c31-a8af-712313149cf2"
---
@ -163,7 +163,7 @@ when the deck is actually out.
```task
id: CB-WP-0024-T03
status: todo
status: done
priority: high
state_hub_task_id: "7b0b08d9-cf0d-40f4-8697-049bdb20085d"
```
@ -189,11 +189,28 @@ face-down as one identical back.
`view.rs`'s own test already uses; copy it);
- `make sim` still passes: 26 scenarios encode what the table looks like.
**Done 2026-08-05.** Each seat's play is drawn as a card in its own area,
with the sentence kept beside it — the log is the record and a player
reading back needs the same vocabulary.
**The face-down back is a `const` with no parameters.** `SelectionView::
Hidden` carries nothing, so the renderer is structurally unable to leak
what it was not given; making the back a constant means there is no data
path into it to add later. The risk this guards is real but is the
renderer's, not the model's: a tint keyed on suit or a shape keyed on
action would leak even though `view.rs` handed over nothing.
The leak test copies `view.rs`'s own shape — assert two different hidden
situations render an identical back, **then** assert the same renderer
does show a revealed play, because without the second half the first
passes for a renderer that draws nothing at all. Both are
mutation-proven: deleting the call turns both red.
## Task: the score that carries across games
```task
id: CB-WP-0024-T04
status: todo
status: done
priority: medium
state_hub_task_id: "4f312267-2b82-4cf9-bbb1-7f287dc9e766"
```
@ -219,11 +236,36 @@ raise the question rather than silently canonising a choice.
- the tally survives the seed advance and resets only on a new process;
- the per-game outcome block is unchanged, and a test says so.
**Done 2026-08-05.** `MatchTally` lives in `play`, beside the listener and
the seed — the other two things that survive `play again`. Shown on the
ending page above the log, and **absent for a first game**: one game is not
a series, and a cumulative panel restating the outcome directly above it
is noise.
**"Cumulative" was decided before anything was summed, and the answer is
that the rules do not decide it.** `OutcomeView` offers `personal` (per
seat), `group_success` (per table) and `winners`; summing the first and
counting the third answer different questions, and GROUND defines one game
and no series at all. **Both are shown and both are named** rather than one
being picked and becoming *the* score by default. A test asserts they can
point at different seats — 9/0/0 against 0/1/1 leads on summed score for
one seat and on games won for the other.
Registered as **F15, kind `underdetermined`, state `note`** in
`FindingRegister.md`. It stays a note deliberately: the test shows the two
tallies *can differ*, which is arithmetic, not evidence that the ambiguity
harms play — so under GameDesign §3.1 it may not be reported to
ground-game yet. **This is the note tier doing the job D6 built it for**,
on the first new question since it was written.
A crashed game contributes nothing: the error path passes an empty tally,
so a game that ended without an outcome is never counted as played.
## Task: evidence
```task
id: CB-WP-0024-T05
status: todo
status: done
priority: high
state_hub_task_id: "db176595-e00d-467c-ba54-0714cfb7ba5c"
```
@ -238,3 +280,30 @@ state_hub_task_id: "db176595-e00d-467c-ba54-0714cfb7ba5c"
- **What the cumulative-score question turned out to be**, and whether it
went to `ground-game`.
- **Quote CB-WP-0023's cost by re-running the instrument.**
**Done 2026-08-05.**
[CB-EV-0023](../evidence/CB-EV-0023-the-table-you-can-watch.md).
- **Three of five remarks were already implemented** — data the projection
carried, drawn as text. The table's problem was legibility, not content,
and **the coverage gate passes either way** because it proves nothing is
omitted, not that anything is readable. That gap is named, not fixed:
the honest control is a person playing it.
- **The defect that mattered was untestable.** `jsrun`'s fetch stub never
invoked its callbacks, so every line of the script reacting to the
server was unreachable from every test in the project. Same finding as
CB-WP-0016's *a stub too thin to express a failure is how the failure
survives*, one layer deeper — at the reply, which is where the session's
ending lives.
- **The reshuffle was already ruled and already implemented** (U4,
confirmed 2026-08-03), so nothing was raised. CB-WP-0026 applied that
ruling the same day this pass consumed it — the first time answering
*"is this underdetermined?"* was one lookup instead of a message.
- **F15 stayed a note on purpose.** The note tier's first use since D6
wrote it, and it came from *building* rather than from play, which the
ADR did not anticipate and which the tier handled unchanged.
- **Chaos window 2 has rolled no 8 in ten declarations**, so it will
likely close with no override to evaluate and its retirement condition
untestable. The d4 → d8 cut may have made the mechanism unevaluable —
a cost CB-EV-0015 did not price, and one the window's closing evidence
should state.