183 lines
8.3 KiB
Markdown
183 lines
8.3 KiB
Markdown
|
|
# CB-EV-0016 — the browser is a client, and window 1's verdict
|
|||
|
|
|
|||
|
|
CB-WP-0018 T04. Measured 2026-08-03 at `e8bb726`+. Pass kind `product`,
|
|||
|
|
tier **M** (structural M — changes the loop's own constraints; chaos d4=3,
|
|||
|
|
no override). **Declaration 1 of chaos window 2**, opened by this pass.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. A finished game was indistinguishable from a crash
|
|||
|
|
|
|||
|
|
Reported: *"after some time i get an empty page back. I guess the game
|
|||
|
|
crashes or ends but that is unclear as the ui disappears."*
|
|||
|
|
|
|||
|
|
Reproduced before touching anything, by driving a real game to completion
|
|||
|
|
over HTTP:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
move 5 accepted → ok
|
|||
|
|
GET / → [Errno 111] Connection refused
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The game had **ended normally** — 5 rounds, 30 commands — and its entire
|
|||
|
|
result went to a terminal nobody was reading. `next_choice` only accepts
|
|||
|
|
connections *inside* a human decision point, so when `play()` returned the
|
|||
|
|
listener died and the page's post-`ok` reload was refused.
|
|||
|
|
|
|||
|
|
**The browser was a second-class client.** The outcome, the scores, the
|
|||
|
|
winners, and every error `run_game` can return were invisible to the only
|
|||
|
|
interface a player uses. Now: an 8,998-byte page reading *"GROUND — game
|
|||
|
|
over … 30 commands, hash f6c890a65271"*, with the final table and the log.
|
|||
|
|
|
|||
|
|
It serves until the page posts `done` — the ending carries a *"close — I
|
|||
|
|
have read this"* control — with a 600 s linger, because a server that
|
|||
|
|
never exits is its own defect and a timeout would race a player reading
|
|||
|
|
the result.
|
|||
|
|
|
|||
|
|
## 2. The control had to be built twice, and the first was worthless
|
|||
|
|
|
|||
|
|
`the_end_of_the_game_reaches_the_browser` calls `serve_end` directly. It
|
|||
|
|
passes. **Deleting the call from `run_game` left it green.**
|
|||
|
|
|
|||
|
|
That is CB-EV-0012's finding recurring almost verbatim — *"every link was
|
|||
|
|
tested and the chain was not"* — and it survived one full round of
|
|||
|
|
mutation here before anyone noticed, because the mutation was run and the
|
|||
|
|
verdict was read as "no coverage gap" rather than "the test is in the
|
|||
|
|
wrong place".
|
|||
|
|
|
|||
|
|
`a_real_game_played_to_its_end_leaves_the_ending_on_screen` runs the real
|
|||
|
|
`play()` with a browser seat, drives a real game to its end over a real
|
|||
|
|
socket, and requires the last page to be the ending. Under the same
|
|||
|
|
mutation it goes red — and its failure message prints an **empty page**,
|
|||
|
|
which is precisely the symptom that was reported.
|
|||
|
|
|
|||
|
|
**A weak assertion of mine, caught by itself.** The first version grepped
|
|||
|
|
the ending page for `location.reload`. The page reuses `SCRIPT`, whose
|
|||
|
|
reload is guarded by `t.indexOf('ok') === 0`, and the ending endpoint
|
|||
|
|
answers `closed` — so the grep would have forced a second script into
|
|||
|
|
existence to satisfy a test rather than a requirement. That is the
|
|||
|
|
source-text control shape ADR-0010 D2 demoted three passes ago, reappearing
|
|||
|
|
in my own hands.
|
|||
|
|
|
|||
|
|
## 3. The card report, and what it actually was
|
|||
|
|
|
|||
|
|
Reported: *"the cards I play by pulling them on a target will not be
|
|||
|
|
removed … we will need a discard pile."*
|
|||
|
|
|
|||
|
|
**A discard pile already existed** — `solution_discard` on `GroundState`,
|
|||
|
|
`SolutionDiscarded` removing the card from the hand, and the page already
|
|||
|
|
rendering `deck N remaining / discard …`. Building one would have been
|
|||
|
|
building a thing that was there, and the only reason that did not happen is
|
|||
|
|
that the code was read before the work started.
|
|||
|
|
|
|||
|
|
What was being dragged are **action** cards. The five GROUND actions are
|
|||
|
|
not cards and are correctly never consumed; solution cards leave the hand
|
|||
|
|
at **Resolve**, because a selection is a face-down commit.
|
|||
|
|
|
|||
|
|
But the report pointed at something real. Measured live:
|
|||
|
|
|
|||
|
|
| move | hand | discard |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 1 · Investigate → problem-2 | 2 cards | none |
|
|||
|
|
| 2 · Investigate → problem-3 | **3 cards** | none |
|
|||
|
|
| 3–5 · Solve → problem-1 | **4 cards, unchanged** | none |
|
|||
|
|
|
|||
|
|
Investigate draws, correctly. **Solve was played three times and did
|
|||
|
|
nothing, three times, in silence** — GR-A02's resolver `continue`s when the
|
|||
|
|
problem is face-down, and `legal_commands` offers Solve on every face-up
|
|||
|
|
problem without consulting the hand.
|
|||
|
|
|
|||
|
|
**Raised for `ground-game`, not decided here** (INTENT defers game
|
|||
|
|
semantics): *should SOLVE be selectable against a face-down problem, or
|
|||
|
|
against a suit the seat cannot match?* A face-down commit you cannot
|
|||
|
|
fulfil is a plausible bluff in a commit/reveal game with DARVO, which is
|
|||
|
|
exactly why it is not this repo's call.
|
|||
|
|
|
|||
|
|
## 4. The log, and the limit of what it can honestly say
|
|||
|
|
|
|||
|
|
`bot::Journal` — `Applied { actor, command, events }` appended by the
|
|||
|
|
driver through the new `play_journaled`; `play` delegates with `None`, so
|
|||
|
|
nothing existing changed. `BotGame.events` is the same information but only
|
|||
|
|
after `play` returns, which is no use to a page rendered mid-game.
|
|||
|
|
|
|||
|
|
Phrased with `record::to_step`, so *what the player reads is what the
|
|||
|
|
scenario file will say*, and all 29 `GroundEvent` variants render in words
|
|||
|
|
rather than `{:?}`.
|
|||
|
|
|
|||
|
|
**A command that produced no events says `no effect`**, and the mutation
|
|||
|
|
removing that branch goes red.
|
|||
|
|
|
|||
|
|
**The honest limit:** the reported SOLVE case does *not* render as `no
|
|||
|
|
effect`, because the SOLVE resolves inside the system's `resolve` command,
|
|||
|
|
which does produce events for other seats. The player now sees the
|
|||
|
|
selection and sees no claim follow it — a large improvement on silence, but
|
|||
|
|
still an inference. Making it explicit would require the renderer to decide
|
|||
|
|
*why* a rule did nothing, which is a second implementation of the rules and
|
|||
|
|
is what this task's own control forbids. Left as an inference deliberately.
|
|||
|
|
|
|||
|
|
## 5. The explanation, and two mutations that a thin fixture defeated
|
|||
|
|
|
|||
|
|
Every advertised target now carries a sentence Rust wrote for **that pair**
|
|||
|
|
(`data-descs`, in step with `data-targets`), shown at the pointer while
|
|||
|
|
dragging over it. ADR-0010 D1 binds: the page renders it, never composes
|
|||
|
|
it.
|
|||
|
|
|
|||
|
|
Both mutations — showing a neighbouring pair's text, and letting targets
|
|||
|
|
and descriptions fall out of step — **initially survived**, because the
|
|||
|
|
test fixture's Attack card had exactly **one** target, where an off-by-one
|
|||
|
|
shift and a truncation are both no-ops.
|
|||
|
|
|
|||
|
|
That is CB-EV-0014's lesson again, one level in: *a stub too thin to
|
|||
|
|
express a failure is how the failure survives*. The fixture now offers two
|
|||
|
|
attack targets on purpose, and both mutations go red.
|
|||
|
|
|
|||
|
|
## 6. Window 1's verdict, and a rate change on n=2
|
|||
|
|
|
|||
|
|
Twelve declarations, two overrides, one each way, **both changed the
|
|||
|
|
outcome** — so window 1's retirement condition was not met and the
|
|||
|
|
mechanism is kept. The full table is in `InnerLoopReference.md` §Chaos
|
|||
|
|
roll.
|
|||
|
|
|
|||
|
|
**Rate dropped d4 → d8; window 2 opened at 12 declarations; new retirement
|
|||
|
|
condition: retire if an override changes nothing twice running.**
|
|||
|
|
|
|||
|
|
**The weakest part of this pass, stated plainly: it is a rate change argued
|
|||
|
|
from n=2.** The alternative — keep d4 for a second window and decide with
|
|||
|
|
four points — was live, and was rejected only because a quarter of all
|
|||
|
|
declarations is a large standing tax to pay for evidence. So window 2
|
|||
|
|
carries a falsifier: **if it produces no override at all, that is evidence
|
|||
|
|
the rate went too far**, not that the mechanism is healthy. A window that
|
|||
|
|
cannot fire cannot be evaluated, which is the exact failure d10 had.
|
|||
|
|
|
|||
|
|
## 7. Cost
|
|||
|
|
|
|||
|
|
| pass | kind | responses | cost | $/response |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| CB-WP-0016 | product | 64 | $14.93 | 0.233 |
|
|||
|
|
| **CB-WP-0017** | product | 40 | **$9.48** | 0.237 |
|
|||
|
|
| CB-WP-0018 | product | *provisional — not quoted* | | |
|
|||
|
|
|
|||
|
|
Read by **re-running `make status` at the moment of writing**, which is
|
|||
|
|
CB-EV-0015 §6's correction applied for the first time: quoting a figure
|
|||
|
|
remembered from earlier in a session defeats the rule even when the
|
|||
|
|
boundary is right. CB-WP-0017 was reported at $5.19/23 mid-flight and
|
|||
|
|
settled at **$9.48/40** — 83% higher. **Six for six, always low.**
|
|||
|
|
|
|||
|
|
**Meta budget 0% `[ok]`**, all three trailing passes product.
|
|||
|
|
|
|||
|
|
## 8. Open
|
|||
|
|
|
|||
|
|
- **INTENT stage 1: the human check.** Three runs, three defects no test
|
|||
|
|
could reach. Everything in this pass is verified by tests, mutation and a
|
|||
|
|
live socket; nothing perceptual is.
|
|||
|
|
- **For `ground-game`:** should SOLVE be selectable against a face-down
|
|||
|
|
problem or an unmatchable suit? §3.
|
|||
|
|
- **The self-quoting rule** now has both halves recorded but is still not
|
|||
|
|
written into the loop spec.
|
|||
|
|
- **AM-4b's scope defect (408,237 uncounted lines)** and its proc-macro
|
|||
|
|
share.
|
|||
|
|
- **`python3` as a toolchain dependency was never argued.**
|
|||
|
|
- **AM-4a cannot survive stage 2** — 1,741,979 against 161,000.
|
|||
|
|
- **ADR-0007 D3's acquisition rule** and **D5** remain unratified;
|
|||
|
|
ADR-0010 rests on the latter.
|