CB-WP-0033: a game is the unit
make trials reported "positions unreachable: 4, target 0 — the recording
exists but the position moved". All four were false. A recording holds one
hash, the final state, and reachability asked whether the note's hash was
in that file — so a mid-game note could never match, and a post-game note
from any but the last game could not either. Instance 8 of the ADR-0018
family: vary only WHEN a note was written and the answer flips, with
nothing having moved.
The root cause was not the metric. play again reused state belonging to a
game: it overwrote the previous game's recording (data loss), never
cleared the journal (game 2's log opened with game 1's commands), and so a
note's command index pointed into a recording without those commands.
Fixing reachability alone would have gone green while a session still
destroyed its own evidence.
A note now binds by (game, after) — an index into the recording's own
commands list, which a reader can replay to. The hash keeps a job as the
integrity check at the end of a game, where it can actually fail. Game 1
keeps the path it was given, so GameDesign §5's documented invocation is
unchanged; later games get -2, -3 and nothing is overwritten. Legacy
5-column logs stay readable and are reported as legacy, never as orphans —
an unsubstantiated orphan claim is the defect being fixed.
All three fixes mutation-proven, including at the call site via a real
two-game session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:41:11 +02:00
|
|
|
---
|
|
|
|
|
id: CB-WP-0033
|
|
|
|
|
kind: product
|
|
|
|
|
title: "A game is the unit"
|
|
|
|
|
status: done
|
2026-08-07 17:49:06 +02:00
|
|
|
state_hub_workstream_id: "66c54551-38cd-465e-bb2f-9e1d83e14438"
|
CB-WP-0033: a game is the unit
make trials reported "positions unreachable: 4, target 0 — the recording
exists but the position moved". All four were false. A recording holds one
hash, the final state, and reachability asked whether the note's hash was
in that file — so a mid-game note could never match, and a post-game note
from any but the last game could not either. Instance 8 of the ADR-0018
family: vary only WHEN a note was written and the answer flips, with
nothing having moved.
The root cause was not the metric. play again reused state belonging to a
game: it overwrote the previous game's recording (data loss), never
cleared the journal (game 2's log opened with game 1's commands), and so a
note's command index pointed into a recording without those commands.
Fixing reachability alone would have gone green while a session still
destroyed its own evidence.
A note now binds by (game, after) — an index into the recording's own
commands list, which a reader can replay to. The hash keeps a job as the
integrity check at the end of a game, where it can actually fail. Game 1
keeps the path it was given, so GameDesign §5's documented invocation is
unchanged; later games get -2, -3 and nothing is overwritten. Legacy
5-column logs stay readable and are reported as legacy, never as orphans —
an unsubstantiated orphan claim is the defect being fixed.
All three fixes mutation-proven, including at the call site via a real
two-game session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:41:11 +02:00
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Purpose
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
structural tier M (changes an artifact contract -- the recording path --
|
|
|
|
|
and the definition of a reported metric)
|
|
|
|
|
chaos d8 = 7 → no override
|
|
|
|
|
declared tier M
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Declaration 4 of chaos window 3.**
|
|
|
|
|
|
|
|
|
|
## The report
|
|
|
|
|
|
|
|
|
|
Reviewing the maintainer's six trial notes, `make trials` said
|
|
|
|
|
**positions unreachable: 4, target 0 — "the recording exists but the
|
|
|
|
|
position moved."**
|
|
|
|
|
|
|
|
|
|
**All four were false.** A recording holds exactly one hash, the final
|
|
|
|
|
state; `reachability()` asked whether the note's hash was in that file. So
|
|
|
|
|
a mid-game note could never match, and a post-game note from any but the
|
|
|
|
|
last game could not either.
|
|
|
|
|
|
|
|
|
|
**Instance 8 of the ADR-0018 family**, with an exact sensitivity: vary
|
|
|
|
|
only *when* the note was written and the answer flips deterministically,
|
|
|
|
|
with nothing having moved.
|
|
|
|
|
|
|
|
|
|
## The root cause was not the metric
|
|
|
|
|
|
|
|
|
|
Three defects, one cause: **`play again` reused state that belongs to a
|
|
|
|
|
game.**
|
|
|
|
|
|
|
|
|
|
| reused | consequence |
|
|
|
|
|
|---|---|
|
|
|
|
|
| the recording path | **game 1's recording destroyed** — data loss |
|
|
|
|
|
| the journal (never cleared) | game 2's log opened with game 1's commands |
|
|
|
|
|
| the note's `after`, counted on that journal | indexed a recording without those commands |
|
|
|
|
|
|
|
|
|
|
**Fixing `reachability()` alone would have turned the number green while a
|
|
|
|
|
session still destroyed its own evidence.** [ADR-0019](../decisions/ADR-0019-a-game-is-the-unit.md).
|
|
|
|
|
|
|
|
|
|
## Task: a game is the unit
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
id: CB-WP-0033-T01
|
|
|
|
|
status: done
|
|
|
|
|
priority: high
|
2026-08-07 17:49:06 +02:00
|
|
|
state_hub_task_id: "b0b4b9f3-f99f-4b4d-8eed-ba2fa4f9ea98"
|
CB-WP-0033: a game is the unit
make trials reported "positions unreachable: 4, target 0 — the recording
exists but the position moved". All four were false. A recording holds one
hash, the final state, and reachability asked whether the note's hash was
in that file — so a mid-game note could never match, and a post-game note
from any but the last game could not either. Instance 8 of the ADR-0018
family: vary only WHEN a note was written and the answer flips, with
nothing having moved.
The root cause was not the metric. play again reused state belonging to a
game: it overwrote the previous game's recording (data loss), never
cleared the journal (game 2's log opened with game 1's commands), and so a
note's command index pointed into a recording without those commands.
Fixing reachability alone would have gone green while a session still
destroyed its own evidence.
A note now binds by (game, after) — an index into the recording's own
commands list, which a reader can replay to. The hash keeps a job as the
integrity check at the end of a game, where it can actually fail. Game 1
keeps the path it was given, so GameDesign §5's documented invocation is
unchanged; later games get -2, -3 and nothing is overwritten. Legacy
5-column logs stay readable and are reported as legacy, never as orphans —
an unsubstantiated orphan claim is the defect being fixed.
All three fixes mutation-proven, including at the call site via a real
two-game session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:41:11 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Controls:**
|
|
|
|
|
- **each fix fails on its own defect**, by mutation, or it is not tested;
|
|
|
|
|
- **the metric can go red** — a check reporting 0 that cannot report
|
|
|
|
|
anything else is decoration (ADR-0006 D3);
|
|
|
|
|
- **old logs stay readable and are not called orphans**;
|
|
|
|
|
- **the single-game invocation GameDesign §5 documents is unchanged.**
|
|
|
|
|
|
|
|
|
|
**Done 2026-08-07.** All three mutation-proven:
|
|
|
|
|
|
|
|
|
|
| mutation | what went red |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `next_game` stops clearing the journal | *"game 2 opened holding game 1's commands"* |
|
|
|
|
|
| notes not filtered by game | *"game 1's comment leaked into game 2's log"* |
|
|
|
|
|
| driver stops re-pathing the recording | *"game 2 was never recorded separately"* |
|
|
|
|
|
|
|
|
|
|
The third is the important one: it runs the **real two-game session**, so
|
|
|
|
|
it covers the call site rather than the helper. The unit test proves
|
|
|
|
|
`game_path` names files apart; only the integration test proves the driver
|
|
|
|
|
uses it, and the driver was where the data loss lived.
|
|
|
|
|
|
|
|
|
|
**Two defects were introduced and caught while writing this.**
|
|
|
|
|
|
|
|
|
|
- `cfg.record` was re-derived from the *previous game's* path, so game 3
|
|
|
|
|
would have landed in `x-2-3.yaml`. Fixed by keeping the caller's base
|
|
|
|
|
untouched, **and the compounding shape is now pinned by an assertion**
|
|
|
|
|
so the fix cannot silently rot.
|
|
|
|
|
- `Server::game()` was dead code, which `-D warnings` caught. Rather than
|
|
|
|
|
`#[allow]`, the driver now *asks the server* which game it is — removing
|
|
|
|
|
a second counter that could have disagreed with the one stamped on the
|
|
|
|
|
notes.
|
|
|
|
|
|
|
|
|
|
**The metric now reports honestly**: 0 unreachable, 6 legacy, and it says
|
|
|
|
|
what legacy means. `trials.py` gained six controls, including the two that
|
|
|
|
|
were previously **impossible to pass** — a mid-game note being reachable,
|
|
|
|
|
and a note from game 2 being judged against game 2's recording.
|
|
|
|
|
|
|
|
|
|
## Not done here
|
|
|
|
|
|
|
|
|
|
- **No hash per command in the recording** (ADR-0019 D3). A mid-game
|
|
|
|
|
position is confirmed by replaying to it, not by string search. The
|
|
|
|
|
trigger for revisiting is a reader who needs the position without a
|
|
|
|
|
replay.
|
|
|
|
|
- **The six existing notes stay legacy forever.** Their positions were
|
|
|
|
|
never checkable; nothing is gained by inventing bindings for them.
|