Skip to content

feat(tui,cpu): deep rewind via keyframes + :rewind-budget#412

Merged
nkane merged 1 commit into
mainfrom
feat/392-deep-rewind
Jun 3, 2026
Merged

feat(tui,cpu): deep rewind via keyframes + :rewind-budget#412
nkane merged 1 commit into
mainfrom
feat/392-deep-rewind

Conversation

@nkane
Copy link
Copy Markdown
Owner

@nkane nkane commented Jun 3, 2026

Final v1.3.0 Theme A sub-issue — deep reverse-step for long runs (#392).

Problem

The per-step snapshot ring is a fixed 256-entry FIFO (≤16 MiB), so reverse-step only reaches back 256 steps. The issue framed this as unbounded memory growth — it is not; the real limit is reach. Storing a delta for every one of millions of steps is infeasible, so deep rewind uses periodic full snapshots + forward replay.

What

  • cpu.KeyframeRing — budget-bounded ring of full-RAM snapshots. CPU.SnapshotFull captures all 256 pages; reuses the existing Restore.
  • One keyframe every keyframeInterval (4096) steps, captured during forward execution (incl. free-run).
  • :rewind N — reconstructs the state N steps back. Small jumps pop the fine ring exactly; deep jumps restore the nearest keyframe ≤ target and replay forward to the exact step (rewindToStepstepReplay under a replayingRewind guard).
  • :rewind-budget MB — resizes the ring (cap = budget / 64 KiB). Reach = cap × interval, shown in the status bar as deep:<reach>@<budget>.

Acceptance

Criterion Result
Rewind to any recent step within 100 ms ~1.3 ms incl. the 4096-step replay (BenchmarkDeepRewind), 75× margin
Ring under 256 MiB at default budget Default cap 128 MiB; memory is a ceiling, scales with run length

A note on the issue numbers

The scope as written is internally inconsistent: full 64 KiB keyframes every 1k steps cannot reach 10M under 256 MiB (that ceiling buys ~4M). I used interval 4096 instead — 256 MiB reaches ~16.7M while a deep rewind still replays ≤4096 instructions (sub-ms). A step-0 keyframe is seeded so sub-interval targets are reachable. Determinism caveat: forward replay assumes deterministic execution between keyframes; buffered keyboard input is snapshotted so it replays correctly. Delta-from-previous-keyframe compression (smaller keyframes → more reach per byte) is a clean future optimization.

Tests

  • cpu: SnapshotFull round-trip, KeyframeRing cap-from-budget / Nearest + eviction / Bytes / nil-safety.
  • tui: deep-rewind byte-exact reconstruction vs a RAM-mutating loop ROM, fast-path vs deep-path selection, fine-ring continuity after landing, :rewind-budget resize/clamp/reject, beyond-reach, reset, humanCount, latency benchmark.

Docs

README reverse-step section rewritten (the old "free-run does not snapshot" line was stale) + deep-rewind table; help modal entries; docs/context.md merged-PR note with the inconsistency finding.

Closes #392

The per-step snapshot ring only reaches back its capacity (256 steps).
Add keyframe-based deep rewind so users can jump back into the last
millions of steps (issue #392):

- cpu.KeyframeRing holds periodic full-RAM snapshots; CPU.SnapshotFull
  captures all 256 pages. One keyframe every 4096 steps (keyframeInterval).
- :rewind N reconstructs an arbitrary earlier step by restoring the
  nearest keyframe <= target and replaying forward to the exact step.
  Small jumps still pop the fine ring exactly.
- :rewind-budget MB caps keyframe memory (ring cap = budget / 64 KiB);
  reach = cap * interval, shown in the status bar as deep:<reach>@<budget>.
  Memory is a ceiling, not a reservation.

Interval 4096 (not the issue's 1k) so 256 MiB reaches ~16.7M steps while
a deep rewind replays <=4096 instructions — benchmarked ~1.3 ms incl.
replay vs the 100 ms acceptance. A step-0 keyframe is seeded on the first
step so sub-interval targets are reachable. StepCount tracks position;
`<` and reset keep it in sync. Forward replay assumes deterministic
execution between keyframes (buffered input is snapshotted).

No state-format change — StepCount and keyframes are ephemeral. cpu ring
logic is unit-tested apart from the TUI; deep-rewind exactness is verified
byte-for-byte against a RAM-mutating loop ROM.

Closes #392
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

TUI smoke renders

Rendered from 8bed38a by the smoke CI job. Replaced on every push to this PR.

chippy-source-scroll (#227)

chippy-source-scroll

chippy-syms (#226)

chippy-syms

chippy-bp-and-run (#225)

chippy-bp-and-run

chippy-console (#232)

chippy-console

@nkane nkane merged commit 00c68de into main Jun 3, 2026
13 checks passed
@nkane nkane deleted the feat/392-deep-rewind branch June 3, 2026 13:00
@nkane nkane mentioned this pull request Jun 3, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v1.3.0: reverse-step polish for long runs (>1M steps)

1 participant