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>
5.2 KiB
ADR-0019: a game is the unit, and play again was reusing what belongs to one
status: accepted date: 2026-08-07 decided by: agent, under the standing loop authorization tier: M (structural M — changes an artifact contract, the recording path, and the definition of a reported metric). chaos d8 = 7 → no override. Declaration 4 of chaos window 3. references: CB-WP-0033, ADR-0014 (the note channel), ADR-0018 (instance 8), GameDesign.md §5
Context
make trials reported positions unreachable: 4, target 0, with the
explanation "the recording exists but the position moved."
All four were false, and the explanation was false four times.
A recording carries exactly one hash — expect.state_hash, the final
state. reachability() asked whether a note's hash appeared in that file.
So the answer never depended on any position moving:
| note | outcome | why |
|---|---|---|
| written mid-game | always orphan | no intermediate hash is in the file to match |
| written after the last game | ok | it is the final hash |
| written after an earlier game | orphan | play again overwrote the recording |
Both Aug-7 sessions confirm it. In 2026-08-07-1612, two post-game notes
with identical round and step: game 1's is orphan, game 2's is ok,
purely because the file holds game 2.
Instance 8 of the family in ADR-0018, and the sensitivity is exact:
vary only when a note was written and reachability flips
deterministically, with nothing having moved. The computation
(hash in file) was right; the subject — a file with one hash, read as a
file of positions — was not.
D1 — the root cause is not the metric
Three defects, one cause: play again reuses state that belongs to a
game, not to a session.
| what | reused | consequence |
|---|---|---|
| the recording path | same file | game 1's recording is destroyed |
| the journal | never cleared | game 2's log shows game 1's commands |
| the note's position | counted against the shared journal | the index means nothing |
The metric was reporting the symptom, in the wrong words. Fixing only
reachability() would have made the number go green while a session still
destroyed its own evidence — the failure the register calls inert.
D2 — the recording path, and what stays unchanged
Game 1 keeps the path it was given. Games 2, 3 … get -2, -3 before the
extension.
The single-game session is byte-identical to today, which matters because GameDesign §5 documents exactly that invocation as the trial protocol, and this project has already shipped one gate that was written for a smaller world (CB-EV-0026 §4). A scheme that renamed every file would have made the spec wrong on the day it landed.
Nothing is overwritten. A recording is evidence; play again silently
deleting the previous game's evidence is a data-loss defect independent of
notes.
D3 — a note binds to a position by (game, after)
after is the number of commands played when the note was written, which
is exactly an index into the recording's own commands: list. That is a
binding a reader can act on: replay the first after commands and you
are standing where the player stood.
The hash keeps a job — an integrity check where it applies. When
after equals the command count, the note's hash must equal
expect.state_hash, and a mismatch is a real finding.
Why not record a hash per command instead? It would make every
position matchable by string search, and it changes the recording format —
which record.rs's round trip, edition-check and any consumer in
ground-game all read. Deferred, not refused: if a reader ever needs to
confirm a mid-game position without replaying, that is the trigger.
D4 — old logs stay readable, and are not called orphans
The trial log gains game and after. Existing 5-column rows are parsed
as legacy and reported as such.
They are not reported as orphans. Their positions were never checkable by anything this repo can now run, and an orphan claim we cannot substantiate is precisely the false explanation this ADR exists to delete. A row that is neither 5 nor 7 columns raises (CB-WP-0032).
Consequences
trials.py's headline metric changes meaning; the old number was not measuring reachability and no comparison to it is valid.- The journal is cleared per game, so the log shows this game.
- Notes from earlier games do not appear in the current game's log.
- A multi-game session leaves several recordings where it left one.
What was rejected
| rejected | why |
|---|---|
fixing reachability() alone |
green metric, session still destroys its own recordings |
| a hash per command in the recording | changes a format three consumers read, to save a replay |
renaming every recording to -1, -2 |
makes GameDesign §5's documented invocation wrong for the common case |
| calling legacy rows orphans | an unsubstantiated claim, which is the defect being fixed |
| clearing the notes per game | the trial log is the session's record; only the log view is per-game |