clay-borg/decisions/ADR-0019-a-game-is-the-unit.md
tegwick edab11c0e4
Some checks failed
ci / check (push) Failing after 3s
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

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