A discard pile already exists -- solution_discard on GroundState, SolutionDiscarded removes from hand, and the page renders it. Building one would have been building a thing that is there. What was dragged are ACTION cards, which 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 points at something real. Measured live: Investigate draws correctly (2 -> 3 -> 4 cards), and Solve was then played three times on problem-1 with the hand unchanged at 4 and discard empty -- because problem-1 was face down and GR-A02's resolver silently continues. legal_commands offers Solve on every face-up problem without consulting the hand. Whether SOLVE should be selectable against a face-down problem or an unmatchable suit is a game-semantics question and INTENT defers those to ground-game. What is ours is that a provably-inert move is offered, accepted and never accounted for. Folded into T02 as the case the log must handle: a command that produced NO events is the one the player needs to see. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
222 lines
8.7 KiB
Markdown
222 lines
8.7 KiB
Markdown
---
|
|
id: CB-WP-0018
|
|
kind: product
|
|
title: "The browser is a client: game over, a log, and where a drop goes"
|
|
status: todo
|
|
---
|
|
|
|
# Purpose
|
|
|
|
```
|
|
structural tier M (changes how the loop constrains its own operation:
|
|
the chaos rate d4 → d8 and a second calibration
|
|
window, owed by CB-EV-0015 §5)
|
|
chaos d4 = 3 → no override
|
|
declared tier M
|
|
```
|
|
|
|
**Declaration 1 of the second chaos window** — opened by T04 below, at the
|
|
rate T04 sets. This declaration was rolled at the old d4, because the rate
|
|
changes when the decision lands, not retroactively.
|
|
|
|
## The defect: a finished game is 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 by driving a real game to completion over HTTP:
|
|
|
|
```
|
|
move 5 accepted → ok
|
|
GET / → URLError: [Errno 111] Connection refused
|
|
```
|
|
|
|
The game **ended normally** — 5 rounds, 30 commands — and everything it
|
|
produced went to the terminal:
|
|
|
|
```
|
|
coalition [P2] score 0
|
|
coalition [P3] score 0
|
|
winners
|
|
game over — 30 commands, hash f6c890a65271
|
|
```
|
|
|
|
The browser got nothing, because `next_choice` only accepts connections
|
|
*inside* a human decision point. When `play()` returns, `run_game` writes
|
|
the outcome to stdout and the process exits; the listener dies and the
|
|
page's post-`ok` reload is refused.
|
|
|
|
**The browser is a second-class client.** Every terminal outcome — the
|
|
result, the scores, the winners, and every error `run_game` can return —
|
|
is invisible to the only interface a player is actually using. A crash and
|
|
a win render identically: nothing.
|
|
|
|
This is the same defect class as CB-WP-0016's silent drop. The system
|
|
refuses to say what happened and the player is left to infer it.
|
|
|
|
## Task: serve the end of the game
|
|
|
|
```task
|
|
id: CB-WP-0018-T01
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
When `play()` returns — **`Ok` or `Err`** — the browser must be told, on
|
|
the page, in terms a player understands.
|
|
|
|
- **A win** shows the outcome the spectator projection already carries:
|
|
totals, threshold, coalitions, mastery, winners.
|
|
- **An error** shows that the game ended badly and why. `run_game`
|
|
currently returns `Err(String)` to a terminal nobody is reading.
|
|
- The final page must **not** auto-reload into a refused connection.
|
|
|
|
The listener has to outlive the game, which it does not today. Decide how
|
|
it ends — a timeout, an explicit close, or serving until interrupted — and
|
|
say why; a server that never exits is its own defect.
|
|
|
|
**Controls:** a test that plays a game to its end through the socket and
|
|
asserts the final GET returns a page naming the outcome, **not** a refused
|
|
connection. And the error path, driven by a game that fails, asserting the
|
|
reason reaches the page.
|
|
|
|
## The second report, and what it actually is
|
|
|
|
Reported: *"the cards I play by pulling them on a target will not be
|
|
removed, that is wrong i guess we will need a discard pile."*
|
|
|
|
**A discard pile already exists** — `solution_discard` on `GroundState`,
|
|
`SolutionDiscarded` removes the card from the hand and pushes it there,
|
|
and the page already renders `deck N remaining / discard …`. Building one
|
|
would have been building a thing that is there.
|
|
|
|
What the maintainer dragged were **action** cards. The five GROUND actions
|
|
are not cards and are correctly never consumed. Solution cards leave the
|
|
hand at **Resolve**, not at Select, because a selection is a face-down
|
|
commit.
|
|
|
|
But the report is pointing at something real. Measured over a live game:
|
|
|
|
| move | hand | discard |
|
|
|---|---|---|
|
|
| 1 · Investigate → problem-2 | 2 cards | none |
|
|
| 2 · Investigate → problem-3 | **3 cards** | none |
|
|
| 3 · Solve → problem-1 | 4 cards | none |
|
|
| 4 · Solve → problem-1 | **4 cards** | none |
|
|
| 5 · Solve → problem-1 | **4 cards** | 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, already claimed, denied, or when the seat holds no
|
|
card of the matching suit — and `legal_commands` offers Solve on every
|
|
face-up problem without consulting the hand, while the page offers it on
|
|
problem-1 which was still face-down.
|
|
|
|
**Whether that is a rule gap or intended is not this repo's call.** A
|
|
face-down commit you cannot fulfil is a plausible bluff in a commit/reveal
|
|
game with DARVO, and INTENT defers game semantics to `ground-game`. What
|
|
*is* this repo's call is that a move which provably does nothing is
|
|
offered, accepted, and never accounted for. That is the log's job (T02)
|
|
and the explanation's job (T03), and it is now the concrete case both are
|
|
measured against.
|
|
|
|
Raised for `ground-game`, not decided here: **should SOLVE be selectable
|
|
against a face-down problem, or against a suit the seat cannot match?**
|
|
|
|
## Task: a game log, and what to do next
|
|
|
|
```task
|
|
id: CB-WP-0018-T02
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Requested: *"a game log about the events that have been generated and a
|
|
hint of what the next move/options are."*
|
|
|
|
The events exist — `validate` produces them and `record::to_scenario`
|
|
already turns them into a scenario file. The page shows none of them, so a
|
|
player sees state change with no account of why.
|
|
|
|
- **The log** is the event stream, in the recorder's vocabulary. Do not
|
|
invent a fourth phrasing: `cb-play`'s `describe()` exists precisely so
|
|
*"what the player reads is what the scenario file will say"*, and the
|
|
HTML renderer currently uses `{c:?}` Debug instead. Reuse it or say why
|
|
it cannot be reused.
|
|
- **The hint** is what the seat may do now, which is `legal` — already in
|
|
hand, already rendered as cards by CB-WP-0017. The hint is the *step*
|
|
context that makes those cards make sense: what the round is waiting
|
|
for, and what happens when it stops waiting.
|
|
|
|
**Control:** the log must be derived from the events the aggregate
|
|
produced, not re-narrated from the state. A log that describes the state
|
|
after the fact is a second implementation of the rules and will drift.
|
|
|
|
**The case it must handle**, from the measurement above: three SOLVEs that
|
|
produced no events at all. A log built only from events would show nothing
|
|
for them and reproduce the silence. So the log must distinguish *"this
|
|
command produced these events"* from *"this command produced none"* —
|
|
the second is the one the player needs and the harder one to render.
|
|
|
|
## Task: say where a drop goes, and what it means
|
|
|
|
```task
|
|
id: CB-WP-0018-T03
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
Requested: *"an overlay arrow pointing to the droptarget element with an
|
|
explanation showing up besides it, when we drag on it."*
|
|
|
|
CB-WP-0017 marks every legal target. What it does not do is say what any
|
|
particular drop **means** — the player sees five outlined boxes and must
|
|
still guess which one does what.
|
|
|
|
So the explanation is the substance and the arrow is the delivery. Both
|
|
need the meaning to exist as data first, and **ADR-0010 Decision 1 binds
|
|
here**: the description of what `action-attack → seat-1` does must be
|
|
written by Rust into the page. The script may render it on hover; it may
|
|
not compose it.
|
|
|
|
**Controls:** every advertised target carries a description, and the
|
|
description a target shows is the one Rust wrote for *that* pair — a
|
|
mutation that shows a neighbouring pair's text must go red. The set
|
|
equality property from ADR-0010 D2 must still hold.
|
|
|
|
## Task: close the chaos change, and the evidence
|
|
|
|
```task
|
|
id: CB-WP-0018-T04
|
|
status: todo
|
|
priority: high
|
|
```
|
|
|
|
CB-EV-0015 §5 recommended and did not make the change, on the grounds that
|
|
it alters how the loop constrains its own operation and was therefore owed
|
|
a tier-M declaration. This is it.
|
|
|
|
**Make it or reject it**, in `specs/InnerLoop.md` and `gates.toml`:
|
|
|
|
- rate **d4 → d8**;
|
|
- a **second window of 12** declarations;
|
|
- new retirement condition: *retire if an override changes nothing twice
|
|
running*.
|
|
|
|
The argument to test rather than assume: both overrides were informative
|
|
*because they were rare*. If that is right the rate should fall; if the
|
|
real driver was that they landed on passes with something to find, the
|
|
rate is irrelevant and the recommendation is wrong. Say which, and note
|
|
that n=2 is thin evidence for a rate change — including the possibility
|
|
that the honest answer is to keep d4 for another window and decide with
|
|
four data points instead of two.
|
|
|
|
Then `evidence/CB-EV-0016-*.md`:
|
|
|
|
- **What the maintainer's three reports have cost and bought**, now that
|
|
there are three in a row and every one found something no test did.
|
|
- **Whether stage 1 closes.**
|
|
- **Quote CB-WP-0017's cost by re-running the instrument**, not from
|
|
memory — CB-EV-0015 §6 found that quoting a remembered figure defeats
|
|
the rule even when the boundary is right, and this is the first pass
|
|
that can apply that correction.
|