clay-borg/evidence/CB-EV-0023-the-table-you-can-watch.md
tegwick 8d58568013
Some checks failed
ci / check (push) Has been cancelled
CB-WP-0024: the table you can watch
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>
2026-08-05 17:32:48 +02:00

8.1 KiB

CB-EV-0023 — the table you can watch

CB-WP-0024 T05. Tier S (structural S — renders state the projection already carries; chaos d8=6 → no override). Declaration 7 of chaos window 2. Closed 2026-08-05.

Delivered: the ending control says what it does and the page acts on it; draw and discard drawn as stacks; each seat's play drawn as a card; a session tally across games. Five remarks from play, four addressed (remarks 2 and 3 are CB-WP-0025).


1. How many of the five remarks were already implemented

Three of five were data the projection already carried, rendered as text. That is the finding this pass is really about, and it was established by reading the code before writing the workplan rather than by building anything.

remark what was actually missing
follow the other players nothing in the model — selections project as Shown after Reveal and were already drawn, as sentences
draw and discard stacks nothing in the model — solution_deck_len and solution_discard were one line of text
show the scores nothing at all — personal, mastery, winners, total/threshold were already drawn
the ending button a real defect, two of them
a cumulative score genuinely absent

So the table's problem was legibility, not content. A renderer can show every field it is given and still be unreadable, and the coverage gate — which asserts every view field appears in the parsed document — passes either way. It is a completeness gate, and completeness was never the issue.

That is worth naming as a gap in the controls, not as a criticism of them. every_view_field_is_classified_in_the_emitted_document proves nothing is silently omitted. Nothing proves it is readable, and nothing could, cheaply. The maintainer playing the game is the instrument, which is what makes remarks like these worth more than their length suggests.

2. The defect that mattered was untestable, and that is why it survived

The ending control was labelled "close — I have read this" while hotseat.rs read it as stop the server, and acknowledging it changed nothing: the tab kept a full table and a play again pointing at a closed port.

The label was a wording bug. The second half survived for a structural reason:

jsrun's fetch stub returned {then: function () { return this; }}, which never invoked its callbacks. Every line of the script that reacts to what the server said was unreachable from every test in this project.

The ok → reload branch, the status text, and the branch that did not yet exist were all equally unexercised. A page that ignores the server was indistinguishable, under test, from one that acts on it.

CB-WP-0014 embedded a real JS engine specifically so the script could be executed rather than string-matched, and CB-WP-0016 found that a stub too thin to express a failure is how the failure survives. This is the same finding one layer deeper: the stub was thin at the reply, and the reply is where the session's ending lives.

The stub now delivers a real then-chain and gesture_with_reply reports which controls survive. The seal is mutation-proven — deleting the branch turns exactly one test red — and a negative control asserts ok: dealing does not seal, because a seal that fired on every reply would pass the first test and silently break play again.

One browser-versus-stub trap, avoided by writing it down

The first seal used setAttribute('data-drop', null). In the stub that stores a real null and getAttribute returns null; in a browser it writes the literal string "null", which is truthy, so the control would have stayed live while its own test called it sealed. removeAttribute is correct in both. The stub gained the method rather than the script gaining a workaround.

3. The reshuffle question: already ruled, already implemented

T02 required settling whether the deck-exhaustion shuffle happens before drawing it, and to raise a finding if it did not.

It does. games/ground/src/lib.rs:1419-1435 (draw_solution): deterministic reshuffle of the discard, seeded seed ^ round, carried in a DeckReshuffled event so replay never re-derives it; if both are empty the draw is skipped. That is the U4 default, which ground-game confirmed on 2026-08-03 — a ruled rule.

So nothing was raised, which is the correct outcome and the one the task was written to allow. The piles show the state in which the next draw triggers it, because that is derivable from the view; a claim that a reshuffle has happened is not, and is not made. The event already reads out in the log.

Two days earlier this question would have been raised as a finding. CB-WP-0026 applied the U4 ruling on 2026-08-05; this pass consumed it the same day. The register paying off inside 24 hours is not proof it works, but it is the first time the answer to "is this underdetermined?" was one lookup instead of a message.

4. What the controls caught

The coverage gate caught its own probe going stale. Replacing the 17 remaining text broke solution_deck_len's classification — the gate does not care how a field is rendered, only that its token appears, so changing the rendering changed the token. Fixed by putting the count in the pile's <title>: a stable probe, and what a screen reader announces. The on-canvas numeral would have been a weak probe, since a bare 17 could be anything on the page.

The compiler caught a misplaced doc comment that had silently taken Summary's #[derive(Debug)] with it.

Neither is interesting alone. Together they are the ordinary case for this project: the gates fire on rendering changes, and the cost of that is small enough that nobody is tempted to loosen them.

5. The cumulative-score question, and why it stayed a note

The task required deciding what "cumulative" means before summing anything. The answer is that GROUND defines one game and no series at all.

personal sums per seat; winners counts games won; group_success counts games the table cleared. They answer different questions, and a test asserts they can point at different seats — a 9/0/0 versus 0/1/1 series leads on summed score for one seat and on games won for the other.

Both are shown and both are named. Picking one would have made it the score by default, which is canonising a rule nobody wrote.

Registered F15, underdetermined, state note. It stays a note on purpose: the test demonstrates the two tallies can differ, which is arithmetic — not evidence that the ambiguity harms play. Under GameDesign §3.1 a note may not be reported to ground-game until an artifact exists.

This is the note tier doing exactly what ADR-0012 D6 built it for, on the first genuinely new question since it was written. The tier was argued as the way to admit findings from play that the engine cannot produce; its first real use is a question from building, which the ADR did not anticipate and which the tier handles unchanged.

6. Chaos window 2

Declaration 7 of 12. Structural S, d8 = 6, no override.

Ten declarations in, no 8 has been rolled. At d8 the expected count over twelve is 1.5, so this is unremarkable — but it means window 2 will likely close with zero overrides to evaluate, and its retirement condition (retire if an override changes nothing twice running) will be untestable. Window 1 closed with 2 overrides at d4 and both changed the outcome. The rate cut to d8 may have made the mechanism unevaluable, which is a real cost that CB-EV-0015's decision did not price, and the window's closing evidence should say so.

7. Cost

CB-WP-0023's cost, by re-running the instrument — the pass this one is required to quote. Reported by make cost; not inlined here as a literal (§Single source of fact), and see CB-EV-0019 §4 on the chain breaking beyond ~4 passes.

Open after this pass

  • Remarks 2 and 3how could we have won and difficulty — are CB-WP-0025, declared and ready, tier L.
  • F15 needs an artifact before the series question can go to ground-game.
  • Legibility has no gate (§1) and probably should not get one; the honest control is a person playing it.