For agents, on top of README.md, which they read first.
Notes for agents
- Frame numbering is per-branch-relative to a bot lifetime. A branch's
log entry at local index
iwas recorded during the tick wherezbanks::Bot's own internal frame counter wasi- true by construction, because a recording always starts exactly when a freshBotdoes (window start, or a rebuilt branch).src/recorder.rsandapps/native's integration must preserve this: never start a recording or a branch mid-bot-lifetime, orsrc/rebuild.rs's goal-choice replay (keyed to tick order, not a stored frame number) silently mismatches with no error. - A non-root branch's own keyframe at its fork frame is not optional.
crate::tree::Tree::owner_ofresolves a frame exactly at a fork point to the CHILD, on the assumption that the child always has its own keyframe there (src/recorder.rs'sstore_fork_keyframe). Skip that call when creating a branch and seeking to its very first frame silently reads the wrong keyframe file and panics on a missing one instead. keyframes.rsbounds seek cost by tier, and pruning is only safe because of one invariant: a keyframe delta-references only a keyframe of its own tier or coarser, never a finer one. That is what letsprunedelete a whole aged-out dense or medium bucket without orphaning a survivor's reference. Changepreferred_referenceand pruning silently becomes a way to corrupt keyframes.prunealso never deletes a branch's first (anchor) keyframe, whatever its tier.- Keyframes are
Console::snapshot_fixed, neverConsole::snapshot. The keyframe codec (src/keyframe.rs) XORs at fixed offsets, and varint snapshots shift every field whenever one integer crosses a width boundary: 2 s deltas were 866 KB that way, 115 KB fixed-width. The recorder takes&Consoleand snapshots itself so a caller cannot hand it the wrong kind, andseek.rsrestores keyframes withrestore_fixedbut the header's frame-0 snapshot withrestore. Changing a keyframe's encoding means bumpingheader::FORMAT_VERSION: older recordings are then deleted at window start (storage::remove_other_formats), not read. - The compression is
ruzstd(pure-Rust zstd) over the XOR. If disk per hour needs to come down, the knobs arerecorder::RECORD_EVERY, the tier retention constants inkeyframes.rs, andstorage's cap - measure withapps/replay-probebefore and after. seek.rs,rebuild.rsandstorage.rswere built only afterapps/replay-probeproved the core deterministic - two independent 10,000-frame replay passes, byte-identical to a bot-driven recording and to each other (2026-09-22). All three assume that; if a future core upgrade needs re-proving it, rerun the probe before trusting any of them again - a diverged core makes every one of them silently produce a wrong machine, not a loud error.seek::entries_up_tois forrebuild.rs;seek_tois for scrubbing. They are not interchangeable:seek_tostarts from a keyframe ALREADY inside the target branch and only needs that branch's own tail;entries_up_tostarts from frame 0 and stitches every ancestor branch's contribution together, because a fresh bot has no keyframe to jump to - it must tick every logged frame from the beginning.- Never snapshot on every frame "to be safe". A snapshot is 1.3 MB;
recorder::Recorder::recordtakes the console and only snapshots on the frame a keyframe is due. - The design notes behind this crate (Doom, Quake, Source, BizHawk,
RetroArch; "determinism first") live in the owner's private notes, as
replay-timeline.md; source comments cite them by that name. They are not in this repository, so do not add links to them here.