Provenance (tier M, structural S, chaos d4=4 -> OVERRIDE drawn M): the maintainer could drag after CB-WP-0016 but could not tell what was pickable, held, or droppable. Underneath that, the page was WRONG about which moves exist: 9 legal commands rendered as 5 cards each claiming all three target kinds, from a const string in the emitter. Investigate is legal on problems 2 and 3 but not 1; Solve on 1 but not 2 or 3. The live page now says 'Solve onto problem 1'. ADR-0010 restates control 5, which this work would otherwise have outgrown in silence: every game fact the page acts on must arrive from Rust as data; the script may read, match and render it, never compute, infer, filter or default one. The survey's real finding is that the permitted and forbidden designs are indistinguishable from outside, so the vocabulary grep is demoted to a cheap first line and two behavioural properties become the controls -- the highlighted set EQUALS the set Rust emitted, and anything the page marks legal must resolve. Both mutation-proven; the derive-legality mutation produces a plausible highlight (seat-0,1,2 where only seat-1 is legal) and is caught. Visible now: .pick resting shadow, .held on the grabbed element, .dropok on every legal target including BOTH drawings of a seat, and a ghost following the pointer. Nothing perceptual is verified and ADR-0010 D5 says so. The DOM stub now models classList/querySelectorAll/createElement and builds its node set from the real emitted page. Trap recorded: QuickJS fixes its stack limit at Context creation relative to that frame, so a helper returning a Context makes every later eval report 'SyntaxError: stack overflow'. CHAOS WINDOW CLOSED, 12 declarations, 2 overrides, one each way. Both changed the outcome, so the retirement condition is not met. Verdict: keep, and recommend d4 -> d8 with a second window of 12 -- that is a change to the loop's own constraints and is owed to the next declaration as tier-M work, not made here. CB-EV-0014 corrected: it quoted CB-WP-0015 at $15.14/136 and called it the first settled figure quoted. Now $22.70/166. The number had been read during CB-WP-0015 itself, so there are two defects -- the boundary, and quoting from memory instead of re-running the instrument. make all exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
CB-EV-0015 — legible interaction, and the chaos window's verdict
CB-WP-0017 T04. Measured 2026-08-02 at ca92db5+. Pass kind product,
tier M (structural S, chaos d4=4 → OVERRIDE, drawn M).
Declaration 12 of 12 — the chaos calibration window closes here.
1. The page was lying, and now it is not
The maintainer could drag after CB-WP-0016 but could not tell what was draggable, what was held, where it could go, or what any of it would do. Underneath that was something worse than a missing affordance: the page was wrong about which moves exist.
Seat P1, round 1, step Select — 9 legal commands, rendered as 5 cards each claiming all three target kinds. Before and after, from the live server:
| card | before | after |
|---|---|---|
| Investigate | "onto a seat, a problem, or the table" | onto problem 2, problem 3 |
| Solve | "onto a seat, a problem, or the table" | onto problem 1 |
| Attack | "onto a seat, a problem, or the table" | onto P2, P3 |
| Support | "onto a seat, a problem, or the table" | onto P2, P3 |
| Ground | "onto a seat, a problem, or the table" | onto the table |
Investigate is legal on problems 2 and 3 but not 1; Solve on 1 but not
2 or 3. The old text was a const in the emitter, printed whenever any
legal command used that action. legal carried the exact targets and the
renderer threw them away.
So drop-target highlighting is not decoration on a working table. It is the first time the page tells the truth about what is legal.
2. ADR-0010: the constraint that replaced "it does one thing"
ADR-0007 D5 said the page "reports raw pointer facts and nothing else"
and doc.rs said SCRIPT "does one thing". Everything asked for here
needs it to do more — hold a drag, follow the pointer, mark other
elements. Restating a control in order to permit what it forbade is how
controls die, so it was restated out loud:
Every game fact the page acts on must arrive from Rust as data. The script may read it, match it, and turn it into presentation. It may not compute, infer, filter, or default one.
The survey's real finding: the permitted design (Rust emits legality, the script matches it) and the forbidden one (the script derives legality from the DOM) are indistinguishable from the outside — both are "the page highlights some elements". A control that cannot separate them is not a control.
The old control was a grep of SCRIPT for game vocabulary. CB-WP-0014
already found that shape too weak once. It is now demoted to a cheap first
line, and the load-bearing control is stated over behaviour:
Property 1 — the highlighted set equals the set Rust emitted. The
mutation that makes the script derive targets by pattern-matching ids
produces a plausible highlight — seat-0, seat-1, seat-2 where only
seat-1 is legal — and the test catches it. That is Decision 1 actually
enforced rather than asserted.
Property 2 — a target the page marks legal must resolve. A page that
advertises a drop Rust then refuses is worse than no highlighting, because
it teaches the player something false. Mutation: adding table to
Investigate's targets → "the page advertises action-investigate -> table
but resolve refuses it".
3. What is visible now, and what is merely tested
| request | done | verified how |
|---|---|---|
| what can be picked up | .pick — shadow, lift on hover, grab cursor |
not verified. Whether it reads as pickable is a human check |
| what is held | .held — dimmed and scaled where it sat |
JS test: __heldKeys() is exactly the grabbed key |
| where it can go | .dropok on every legal target, both drawings of a seat |
JS test: set equality with data-targets |
| where it is going | a ghost following the pointer | JS test: ghost exists, and its left tracks clientX |
| what moves exist | the card names its real targets | live server, §1 |
A seat is marked on both of its drawings — card and graph node. That
is asserted rather than tolerated: highlighting only one would send the
player back to hunting for the one that works, which is the CB-WP-0016
defect in a new form. The first draft of that assertion was wrong about
it, expecting seat-1 and reading seat-1,seat-1.
Third mutation: dropping the pointercancel handler and the clear()
on pointerup leaves marks behind — "marks survived __up('seat-1')". A
stale highlight set is worse than none.
What this pass does not claim
No visual property is verified. Whether the shadow reads as "pick me up", whether the highlight is noticeable, whether the drag feels followable — none of it is reachable from here, and ADR-0010 D5 says so rather than letting the tests imply otherwise.
4. The harness had to get deeper, and cost an hour to a real trap
The DOM stub could not express any of this: it synthesized
{target:{id}} from whatever key the test passed. It now models
classList, querySelectorAll, createElement, and a body — and its
node set is built from the real emitted page via droppables(html),
not from a list the harness invented.
The trap, recorded because it will recur: QuickJS fixes its stack
limit at Context creation relative to the frame it was created in. A
helper that creates a context and returns it leaves every later eval
running in a shallower frame, the comparison underflows, and QuickJS
reports SyntaxError: stack overflow — for __all.length. The fix is
that the caller owns the Context and the helper only prepares it.
That is also why one test had silently skipped registration: each test
hand-rolled its setup. prepare() is now shared, so a test cannot set up
a different page than the harness runs.
5. The chaos calibration window: the verdict it has owed
gates.toml states the retirement condition in its own words:
"the window closes with no overridden tier producing a different outcome than the argued one — the evaluation this window exists to make possible"
Twelve declarations, two overrides, one in each direction. That is the minimum that makes an evaluation possible at all, and it arrived on the last declaration.
Override 1 — CB-WP-0011, structural L → S
The deleted survey would have opened on 2D toolkits. What the pass found instead, at tier S, was that the existing text renderer was showing 24 of 41 view fields (CB-EV-0009 §1) — a defect a toolkit survey would have walked straight past. Priced on the same subject: 0.099 $/response at S against 0.123 at L (CB-EV-0010 §5).
Different outcome than the argued one: yes. The argued tier would have bought a survey of alternatives; the rolled tier bought a defect in what already existed.
Override 2 — CB-WP-0017, structural S → M
The question the retirement condition asks: what did the extra ADR buy that a tier-S provenance paragraph would not have?
It bought the restatement of control 5. At tier S this pass would have widened what the JavaScript does — pointermove, class-toggling on other elements, a ghost node — under a one-paragraph commit note, and ADR-0007 D5's "reports raw pointer facts and nothing else" would have been silently outgrown rather than replaced. The forced document is what produced Decision 1's rule and, more importantly, the finding that the permitted and forbidden designs are indistinguishable from outside — which is what turned the vocabulary grep from the control into a cheap first line, and produced the two properties that are now the real controls.
Different outcome than the argued one: yes, and in the direction that is harder to see from inside — the extra process caught a control quietly expiring.
Verdict: keep, at a lower rate
Both overrides changed the outcome, so the retirement condition is not met. But the honest reading of the cost is that d4 fired twice in twelve and both were informative because they were rare; a mechanism that overrides a quarter of all declarations stops being a calibration and starts being the tier system.
Recommendation: keep the chaos roll, drop the rate from d4 to d8, and open a second window of 12 with a new retirement condition — retire if an override changes nothing twice running. This is a change to how the loop constrains its own operation and is therefore tier M work that this pass does not do: it is recorded here as the recommendation and owed to the next declaration, which must make it with its own tier and its own roll.
6. Cost
| pass | kind | responses | cost | $/response |
|---|---|---|---|---|
| CB-WP-0016 | product | 64 | $14.93 | 0.233 |
| CB-WP-0017 | product | provisional — not quoted |
Read from make status at the moment of writing, after CB-WP-0017's
declaration closed CB-WP-0016's window.
The self-quoting rule failed a second way, and CB-EV-0014 is corrected
CB-EV-0014 §5 quoted CB-WP-0015 at $15.14 / 136 and claimed it was "the first figure this project has quoted at a boundary that had actually settled." Both parts were wrong. The same tool now reports CB-WP-0015 at $22.70 / 166 — higher by 50%. CB-EV-0014 has been corrected in place.
$15.14/136 was read during CB-WP-0015 itself and carried forward into the next pass's write-up. So there are two defects, not one:
- the boundary is wrong — a window runs to the next pass's first commit (CB-EV-0013 §5);
- a figure read earlier in a session is an in-flight figure regardless of whose name is on it. Quoting from memory defeats the rule even when the boundary is right.
Five for five now, always low: every self-reported cost in this project has been an underestimate. The rule owed is therefore not just "quote two passes back" but "re-run the instrument at the moment you quote it" — and the second half is what this file did.
Meta budget: 0% [ok] over the trailing three, all product.
7. Open
- INTENT stage 1: the human check, again. Everything perceptual here
is unverified by construction.
cb-play --serve 0: can you see what is pickable, what you are holding, and where it may go? - Chaos: the d4→d8 recommendation and a second window. §5.
- The self-quoting rule still names the wrong boundary. CB-EV-0013 §5.
- AM-4b's scope defect (408,237 uncounted lines) and its proc-macro share.
python3as a toolchain dependency was never argued.- AM-4a cannot survive stage 2 — 1,741,979 against 161,000.
- ADR-0007 D3's acquisition rule unratified after deciding two dependency questions; ADR-0010 now rests on D5, which is also unratified.