Skip to content

feat(frontend): export a classified address from the RAM Atlas into RAM Watch - #413

Merged
doublegate merged 2 commits into
mainfrom
feat/v2.3.9-atlas-watch-export
Aug 19, 2026
Merged

feat(frontend): export a classified address from the RAM Atlas into RAM Watch#413
doublegate merged 2 commits into
mainfrom
feat/v2.3.9-atlas-watch-export

Conversation

@doublegate

Copy link
Copy Markdown
Owner

v2.3.9 item C, the RAM Atlas half: a classified address can now leave the panel.

Why an export path at all

The atlas is cleared at every ROM transition, by design — it describes one observation window on one cartridge, and 2,048 labels standing across a ROM change would look like a map of a game they were never derived from. That correctness property has a cost: the address a user just verified by perturbing it disappears with everything else, and had to be transcribed by hand into the tool that keeps addresses.

RAM Watch is that tool, so it is the destination rather than a second list.

The three decisions worth arguing about

1. Two outcomes, not a bool. A second click on an address already watched did not grow the list. Reporting "added" is a false report of work done; reporting a failure is false in the other direction. SeedOutcome keeps them apart — the same refusal to collapse distinct answers that keeps Untested and Inert apart in the classifier.

2. The label carries the lens, or it over-claims. Liveness is relative to the observable: a byte is Live through work RAM and may be Inert through the screen. The watch list is precisely where an unqualified "LIVE" would outlive the panel that qualified it. Untested is spelled out rather than left blank — once the atlas is gone, an entry with no verdict and an entry that was never tested look identical — and an untested export names no lens at all, because nothing was observed through one.

3. Every address is exportable, including Inert and Untested. Restricting the button to verified-live addresses would be the paternalistic reading of the panel's honesty rule. The rule is that a claim carries its evidence, not that unverified addresses are unusable: an Untested sparse byte is a good thing to watch while forming a hypothesis, and Inert is documented here as not meaning unused.

Two smaller ones

  • The entry is byte-scoped and does not inherit the panel's size selector. The atlas's evidence is per byte; seeding a u16 would attach a byte's verdict to a second address nothing was observed about.
  • It arrives unfrozen. A frozen watch entry writes to the game; an export must not.

Where the dispatch lives, and why the status line exists

In the overlay, the only place holding both panels' state. It opens the destination window as part of the export — but Memory Compare is drawn earlier in the same pass, so the seeded row itself appears on the next frame. Without a line on the atlas's own status area, the user's only evidence would be a list they may not be looking at, and an export that silently succeeds would be indistinguishable from one that silently did nothing.

The button is also not gated on the netplay / TAS / hardcore predicate that gates Observe and Verify: it writes to another panel's list and never advances the emulator.

Verification

Six tests. Five mutations, each failing exactly its own test:

mutation test that caught it
drop the duplicate-address guard seeding_the_same_address_twice_adds_one_entry
inherit watch_size instead of Size::U8 a_seeded_entry_is_one_byte_and_unfrozen
drop the lens from a Live verdict an_exported_verdict_carries_its_lens
label an Untested address as LIVE an_unverified_export_says_so
clone the request instead of taking it a_watch_request_is_drained_by_the_first_taker

The sixth (clearing_discards_a_pending_export) rides the existing *self = Self::default(), so it has no independent mutation — stated rather than counted as coverage it does not have.

Gates: fmt, workspace clippy, debug-hooks / full, both wasm32 invocations, rustdoc, frontend suite at 528.

Frontend-only; no emulation-core file and no accuracy path is touched.

…AM Watch

v2.3.9 item C. The RAM Atlas classifies all 2 KiB of work RAM and can
verify one address by perturbing it, but until now a verified address
could not leave the panel. The atlas is cleared at every ROM transition
by design -- it describes one observation window on one cartridge -- so
"the address I just proved matters" had to be transcribed by hand into
the tool that keeps it.

RAM Watch is that tool, so it is the destination rather than a second
list. `MemoryComparePanelState::seed_watch` appends on another panel's
behalf and returns which of two things happened. Two outcomes rather
than a bool: a second click on an address already present did not grow
the list, so reporting "added" would be a false report of work done, and
reporting a failure would be false in the other direction. That is the
same refusal to collapse distinct answers that keeps `Untested` and
`Inert` apart in the classifier itself.

The seeded entry is byte-scoped and never inherits the panel's own size
selector. The atlas's evidence -- change count, direction, range,
distinct values -- is per byte, so seeding a u16 would attach a byte's
verdict to a second address nothing was ever observed about. Widening it
afterwards is a decision the user has made rather than one made for
them. It also arrives unfrozen, since a frozen entry writes to the game
and an export must not.

The exported label carries the verdict AND its lens. Liveness is
relative to the observable -- a byte is Live through work RAM and may be
Inert through the screen -- and the watch list is precisely where an
unqualified "LIVE" would outlive the panel that qualified it.
`Untested` is spelled out rather than left blank, because once the atlas
is gone an entry with no verdict and an entry that was never tested look
identical, and an untested export cites no lens at all: nothing was
observed through one, so naming one would dress a hypothesis as a
measurement.

The button is offered for every address, including `Inert` and
`Untested`. Restricting it to verified-live addresses would be the
paternalistic reading of the panel's honesty rule; the rule is that a
claim carries its evidence, not that unverified addresses are unusable.
It is also not gated on the netplay / TAS / hardcore predicate that
gates Observe and Verify, because it writes to another panel's list and
never advances the emulator.

The dispatch lives in the overlay, which is the only place holding both
panels' state, and it opens the destination window as part of the
export. Memory Compare is drawn earlier in the same pass, so the seeded
row appears on the next frame -- which is why the outcome is also
reported on the atlas's own status line, where the user is already
looking. An export that succeeds silently and one that silently does
nothing are otherwise indistinguishable, which is the defect shape this
release keeps finding.

Six tests, five of them mutation-checked: removing the duplicate guard,
inheriting the size selector, dropping the lens from a Live verdict,
labelling an untested address as Live, and cloning the request instead
of taking it each fail exactly their own test.
Copilot AI lite review requested due to automatic review settings August 19, 2026 18:44
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@doublegate, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7103e829-a092-4d6a-83aa-124bf430ae0a

📥 Commits

Reviewing files that changed from the base of the PR and between 76760d2 and f3877cb.

📒 Files selected for processing (4)
  • crates/rustynes-frontend/src/debugger/atlas_panel.rs
  • crates/rustynes-frontend/src/debugger/memory_compare_panel.rs
  • crates/rustynes-frontend/src/debugger/mod.rs
  • docs/ram-atlas.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an explicit export/hand-off path from the RAM Atlas panel to the existing RAM Watch list (in the Memory Compare panel), so a user can keep a vetted (or intentionally unvetted) address without manual transcription—even though the Atlas is correctly cleared on ROM transitions.

Changes:

  • Added an Atlas→RAM Watch “Send to RAM Watch” UI action that queues an export request from the Atlas panel.
  • Dispatched the queued export in DebuggerOverlay (the only layer that can access both panels’ state), seeding RAM Watch and opening the destination panel.
  • Introduced a SeedOutcome return type + seed_watch API and added focused unit tests around dedupe behavior, byte-scoping, and request draining/clearing.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
crates/rustynes-frontend/src/debugger/mod.rs Dispatches the Atlas export request into Memory Compare’s RAM Watch, opens the destination panel, and reports outcome back to the Atlas status line.
crates/rustynes-frontend/src/debugger/memory_compare_panel.rs Adds SeedOutcome + seed_watch API for cross-panel seeding, with tests ensuring dedupe and byte-scoped/unfrozen exports.
crates/rustynes-frontend/src/debugger/atlas_panel.rs Adds export button + queued request plumbing, label construction that preserves verdict/lens honesty, and tests for label semantics and request draining/clearing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Docs-as-spec: the panel gained a behaviour and its spec did not describe
it, which is the drift this project's contribution gate exists to stop.

Documents the four properties of the export as the honesty rule applied
to four different edges -- the label carries its lens, every address is
exportable, the entry is byte-scoped and unfrozen, and a duplicate is
reported as a duplicate -- each cross-referenced to the section of this
document that establishes the rule it applies.

Also narrows two "deliberately not implemented" entries rather than
deleting them. The remaining export paths are the Cheat panel, Lua and
RetroAchievements authoring, and the note now records the one way a
cheat export differs: a cheat is a WRITE, so it needs the locked-session
predicate the watch export correctly does without.

Per-game persistence stays deferred, with the reason stated instead of
implied. The obvious form is unsafe -- a restored verdict without its
evidence is a claim that cannot be checked, and this panel's whole
argument is that its output can be. A restored verdict WITH its evidence
is still a statement about the game state the observation ran in, which
the next session does not share. The export is the durable path in the
meantime, and RAM Watch already has .wch save and load.
@doublegate

Copy link
Copy Markdown
Owner Author

Added the spec update this PR should have carried from the start (docs/ram-atlas.md, f3877cbe) — the panel gained a behaviour and its document did not describe it, which is the drift the docs-as-spec gate exists to stop.

It also narrows two "deliberately not implemented" entries rather than deleting them, and records one thing worth having written down before the next export lands: a cheat export is a write, so it needs the locked-session predicate (netplay / TAS / hardcore) that the watch export correctly does without.

Per-game persistence stays deferred, now with the reason rather than the label: a restored verdict without its evidence is a claim that cannot be checked — which is this panel's whole argument in reverse — and a restored verdict with its evidence is still a statement about the game state the observation ran in, which the next session does not share. The export plus RAM Watch's existing .wch save/load is the durable path in the meantime.

@github-actions

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Adds a button to the RAM Atlas panel that exports a classified address and its verification status to the Memory Compare RAM Watch list.

Blocking issues

None found.

Suggestions

  • crates/rustynes-frontend/src/debugger/mod.rs: The success message "added to RAM Watch (Memory Compare)" could simply be "added to RAM Watch", as the tooltip on the button itself already specifies that it opens Memory Compare.

Nitpicks

  • crates/rustynes-frontend/src/debugger/atlas_panel.rs (line 507): In watch_label, verdict creates an intermediate String allocation. You could format the entire string in one pass to avoid this, though the performance impact in a button click handler is trivial.
  • crates/rustynes-frontend/src/debugger/atlas_panel.rs (line 492): The UI button click unconditionally overwrites state.watch_requested. Since a user can only physically click one button per frame in egui, this is safe, but using a Vec for requests would defensively handle any future multi-select actions.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

@doublegate

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@doublegate

Copy link
Copy Markdown
Owner Author

Antigravity review addressed — no blocking issues; all three points declined, with reasons.

Suggestion — shorten the status to "added to RAM Watch". The tooltip and the status line are read at different moments. The tooltip is gone by the time the status appears, and the status is the only thing on screen naming where the entry went — which matters here more than usual, because Memory Compare is drawn earlier in the overlay's pass, so the seeded row itself does not appear until the next frame. Naming the destination panel is the difference between "it worked, look here" and "it worked, somewhere".

Nitpick — the intermediate String in watch_label. Correctly flagged as trivially cheap, and I would rather keep the match producing one value per arm than fold three format strings into a single expression. The allocation is per button click; the readability is per reader.

Nitpick — a Vec for requests instead of Option. Declined as speculative. A single Option is not a limitation to work around, it is the invariant: one click produces one export, and take_watch_request drains it so a click cannot seed twice. A Vec would introduce an unbounded queue with no producer that can fill it, and the next reader would have to work out whether multiple pending exports are possible (they are not). If multi-select ever lands, the type changes with it and the tests move with the type.

For the record

CodeRabbit did not review this PR. It does not auto-review this repository (under 10 stars), and the manual @coderabbitai review I posted hit a per-developer rate limit. #416 records both, along with the consequence that gating a merge on "zero pending checks" never terminates here.

@doublegate
doublegate merged commit f205394 into main Aug 19, 2026
27 checks passed
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@doublegate Thanks for the detailed rationale.

The status text must name RAM Watch because the seeded entry is not visible until the next overlay frame.

Option correctly represents the current one-click, one-request invariant. A queue is not needed without multi-select.

The watch_label allocation is outside the emulator hot path. The explicit match is reasonable.

The record about the rate-limited manual review is clear. No CodeRabbit review findings were produced for this pull request.


✏️ Learnings added
Learnt from: doublegate
URL: https://github.com/doublegate/RustyNES/pull/413

Timestamp: 2026-08-19T19:58:27.211Z
Learning: In `doublegate/RustyNES`, RAM Atlas export status text must explicitly name RAM Watch. Memory Compare is drawn earlier in the overlay pass, so a newly seeded RAM Watch entry is not visible until the next frame. The RAM Atlas status line is the immediate confirmation of both success and destination.

Learnt from: doublegate
URL: https://github.com/doublegate/RustyNES/pull/413

Timestamp: 2026-08-19T19:58:27.211Z
Learning: In `doublegate/RustyNES`, RAM Atlas stores a pending RAM Watch export as a single `Option` request. One click produces one request, and `take_watch_request` drains it so that the same click cannot seed twice. Do not replace this with a `Vec` unless a future multi-select feature introduces multiple producers.

Learnt from: doublegate
URL: https://github.com/doublegate/RustyNES/pull/413

Timestamp: 2026-08-19T19:58:27.211Z
Learning: In `doublegate/RustyNES`, a future RAM Atlas cheat export is a write and must use the locked-session predicate for netplay, TAS, and RetroAchievements hardcore. RAM Watch export does not require that gate because it only adds an unfrozen entry to another panel and does not advance or mutate the live `Nes`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: doublegate
Repo: doublegate/RustyNES PR: 392
File: crates/rustynes-frontend/src/debugger/mod.rs:2181-2203
Timestamp: 2026-08-17T16:10:04.466Z
Learning: In `doublegate/RustyNES`, RAM Atlas actions must use the already-computed `writes_locked || hardcore_blocked` session gate that `emu.write` and debugger writeback use. Publish this value to `DebuggerOverlay` with `DebuggerOverlay::set_writes_locked` on both native and wasm paths. Do not re-derive the gate in the panel, because netplay, TAS recording/playback, and RetroAchievements hardcore must prevent RAM Atlas observation and verification from advancing or mutating the live `Nes`.

You are interacting with an AI system.

@doublegate
doublegate deleted the feat/v2.3.9-atlas-watch-export branch August 19, 2026 19:58
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.

2 participants