feat(creator-tools): TAStudio force-greenzone + Lua API breadth (v2.1.10 B8/B9)#288
Merged
Conversation
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Pull request overview
This PR deepens the Creator Tools surface by adding TAStudio force-greenzone caching and expanding the Lua scripting API (new HUD primitive, more read domains, and new lifecycle events), including improved piccolo/wasm parity plus accompanying docs and example scripts.
Changes:
- TAStudio: add a bounded “Force GZ” mode that caches a save-state at every frame in a forced range (plus UI wiring and tests).
- Lua: add
emu.drawLine,memory:read_palette/memory:read_chr, and host-fired lifecycle events (reset,spriteZeroHit,codeBreak) with gating helpers. - picolo/wasm: add
drawLine, a read-paritymemorytable backed by an expanded per-frame snapshot, and a no-opaddEventCallbackfor portability; add/refresh docs + examples + changelog entry.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/scripts/palette_viewer.lua | New example demonstrating memory:read_palette and memory:read_chr with on-screen swatches/readout. |
| examples/scripts/lifecycle_events.lua | New example script for lifecycle events via emu.addEventCallback. |
| examples/scripts/hud_graph.lua | New example drawing a scrolling graph using emu.drawLine. |
| docs/scripting.md | Document drawLine, new memory:* reads, lifecycle events, and piccolo parity matrix + example library. |
| docs/frontend.md | Document TAStudio force-greenzone behavior, budget, and determinism. |
| crates/rustynes-script/src/types.rs | Add DrawCmd::Line to represent line overlay commands. |
| crates/rustynes-script/src/piccolo_backend.rs | Add emu.drawLine, memory table read parity, and addEventCallback no-op; expand snapshot to include PPU/OAM. |
| crates/rustynes-script/src/mlua_backend.rs | Add emu.drawLine, memory:read_palette / read_chr, and host-fired lifecycle events registration + firing. |
| crates/rustynes-script/src/lib.rs | Expose fire_* and needs_* lifecycle event APIs; add tests for drawLine, reads, and lifecycle events; embed new examples. |
| crates/rustynes-script/src/backend.rs | Extend VmBackend trait with defaulted lifecycle event hooks (fire_*, needs_*). |
| crates/rustynes-frontend/src/tastudio/mod.rs | Capture greenzone states at forced frames and add forced-range setter/getter + tests. |
| crates/rustynes-frontend/src/tastudio/greenzone.rs | Implement forced range tracking, pinning via anchors, and range normalization/clamping + tests. |
| crates/rustynes-frontend/src/debugger/tastudio_panel.rs | Add “Force GZ” UI toggle that emits TasRequest::SetForcedGreenzone. |
| crates/rustynes-frontend/src/app.rs | Handle new TAStudio request; fire Lua reset event; sample + fire spriteZeroHit; render DrawCmd::Line. |
| CHANGELOG.md | Add unreleased entry describing force-greenzone and Lua API breadth changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nts (B9) Extend the Lua scripting API breadth (v2.1.10 Creator Tools, B9): - DrawCmd::Line + emu.drawLine(x1,y1,x2,y2[,color]) — the fourth HUD primitive alongside text/rect/pixel, at full parity across the mlua and piccolo backends. Pure overlay, never write-gated. Rendered in the frontend via painter.line_segment (native + wasm overlay paths). - memory:read_palette(idx) + memory:read_chr(addr) — explicit palette and CHR PPU read domains, both via the side-effect-free ppu_bus_peek debug path (the *Debug variant by construction). - reset / spriteZeroHit / codeBreak host-fired lifecycle events, registrable via emu.addEventCallback and fired by the host through the new ScriptEngine::fire_reset / fire_sprite_zero_hit / fire_code_break pass-throughs (needs_*_event gates so the host fires only when armed). fire_reset wired at do_reset; fire_sprite_zero_hit sampled per-frame in the script pump via a non-destructive peek($2002) & 0x40. - piccolo parity uplift: drawLine, a read-parity memory table (CPU/PPU/palette/CHR/OAM from an extended per-frame Snapshot; poke keeps the deferred-but-lands gated contract), and an addEventCallback no-op so portable scripts don't error on wasm. Determinism unchanged: reads are debug-peeks, writes stay gated/deferred, the deterministic core is untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a force-greenzone capability to the TAStudio editor (v2.1.10 Creator Tools, B8): guarantee a cached save-state at EVERY frame in a user range, so scrubbing / rewinding anywhere inside it is instant (vs. the normal density-tiered keyframe skeleton). - Greenzone gains a bounded, self-tracked forced range: set_forced_range normalises + clamps the span to MAX_FORCED_GREENZONE_FRAMES (10,800 ~= 3 min at 60 fps), store() pins forced frames as non-evictable anchors, and shrinking/clearing the range releases ONLY the anchors force-gz added (never a marker/branch anchor). Documented memory budget. - TasEditor::set_forced_greenzone_range / forced_greenzone_range, and the seek + record capture loops now store at every forced frame (not just the keyframe stride). - A "Force GZ" toggle in the piano-roll header (TasRequest:: SetForcedGreenzone), dispatched under the emu lock. Force-greenzone is a pure caching optimisation: a seek into the forced range stays bit-identical to a linear replay (new editor test), so the determinism / TAS contract is unchanged. 5 greenzone unit tests + 1 editor bit-identity test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…NGELOG (B8/B9) - examples/scripts: hud_graph.lua (drawLine graph), palette_viewer.lua (read_palette/read_chr), lifecycle_events.lua (addEventCallback events); all embedded + covered by bundled_example_scripts_load_and_run. - docs/scripting.md: drawLine, memory:read_palette/read_chr (+ the *Debug side-effect-free contract), reset/spriteZeroHit/codeBreak events, the updated piccolo parity matrix, and an example-library table. - docs/frontend.md: the force-greenzone subsystem (API, mechanism, memory budget, determinism). - CHANGELOG [Unreleased]: comprehensive v2.1.10 Creator Tools entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app.rs: collapse the nested if in do_reset's reset-event fire (clippy collapsible_if under -D warnings); fire_reset still runs whenever armed. - greenzone.rs / tastudio/mod.rs: use plain code spans for MAX_FORCED_GREENZONE_FRAMES / DEFAULT_GREENZONE_BUDGET instead of intra-doc links into the private greenzone module (rustdoc private_intra_doc_links under -D warnings).
Greenzone::store's public doc linked the private forced_anchors field (rustdoc private_intra_doc_links under -D warnings).
a2f17f1 to
601225f
Compare
…or-ownership + full lifecycle example Address the four Copilot review threads on PR #288 (v2.1.10 "Creator Tools"). Two are real correctness bugs in the force-greenzone anchor bookkeeping; two are documentation-vs-behaviour consistency gaps in the lifecycle-events example. Greenzone force-anchor ownership (crates/rustynes-frontend/src/tastudio/greenzone.rs) - `clear_non_default_anchors` no longer disables Force-GZ. Previously it set `forced_range = None`, so any wholesale anchor rebuild the editor performs (marker shift, branch load, project load) would silently turn the user's Force-GZ toggle off — contradicting the "editor re-applies afterwards" doc contract and surprising the UI. The manager still drops every non-frame-0 anchor (including the ones force-greenzone pinned), but we now KEEP `forced_range` and re-derive its anchors: every forced frame that still has a cached state is re-pinned as a force-owned anchor, and forced frames not yet cached are re-pinned by `store` as the editor re-emulates across the range. The rebuild scan is bounded by the number of currently-cached frames, not the full (up to 10,800-frame) span. - `store` no longer claims force-ownership of a frame that is ALREADY an anchor for another reason (a marker / branch point). The prior code inserted every in-range forced frame into `forced_anchors` unconditionally, so a marker that happened to fall inside the forced range was recorded as force-owned; later shrinking or clearing the range (via `set_forced_range`) would then `remove_anchor` it and unpin a state the marker/branch mechanism still needs. Force-greenzone now piggy-backs on a pre-existing anchor's retention guarantee without claiming (and thus without being able to release) it — the added `!is_anchor` guard makes force-ownership exclusive to frames force-greenzone actually introduced. - Two regression tests lock the fixed behaviour: `clear_non_default_anchors_preserves_forced_range` (the Force-GZ range and its cached anchors survive an anchor rebuild, and a later disable still releases exactly them) and `forced_store_does_not_claim_a_preexisting_marker_anchor` (a marker anchor inside the forced range survives force release). The full greenzone suite is 12 green; the TAStudio bit-identity gates (`force_greenzone_caches_every_frame_and_stays_bit_identical`, `seek_is_bit_identical_to_linear_replay`) stay green — this is pure anchor bookkeeping, so the deterministic seek/replay contract is untouched. Lifecycle-events example (examples/scripts/lifecycle_events.lua) - The header claims the script covers the full `emu.addEventCallback` surface, but it only registered 6 of the 10 documented events. Register the missing four so the script matches its own description and the docs' "every event" goal: `endFrame` and `inputPolled` (quiet by default — they fire 60x/second), and `irq` and `stateSaved` (logged, like their `nmi` / `stateLoaded` peers). All ten names are validated against the `mlua_backend` event dispatch enum; the `bundled_example_scripts_load_and_run` test still loads and runs the script, and both backends stay green (mlua 51, piccolo 14). Determinism preserved: the changes live entirely in the frontend TAStudio bookkeeping and an example script — the `#![no_std]` core is untouched, so AccuracyCoin holds 141/141 (100%) and save-state / TAS replay stays byte-identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate
added a commit
that referenced
this pull request
Jul 12, 2026
…#295) Cuts the v2.1.10 release — the creator-tools-and-web-parity step of the v2.1.5 → v2.2.0 "deepen the existing project" run. Bumps the workspace version 2.1.9 → 2.1.10, renames CHANGELOG `[Unreleased]` → `[2.1.10]`, refreshes Cargo.lock, syncs the README badge / current-release paragraph / BibTeX, and authors `.github/release-notes/v2.1.10.md`. v2.1.10 (PRs #287, #288) deepens content-creation and closes the deferred browser gaps, on the byte-identical deterministic core (AccuracyCoin 141/141, visual_regression byte-identical, save-state / TAS / netplay replay bit-identical): - #288 — TAStudio greenzone / force-greenzone caching + Lua API breadth (full lifecycle callbacks, memory read-write with *Debug variants, HUD drawing, example library, wasm piccolo parity). Two review-surfaced greenzone anchor-bookkeeping bugs fixed with new regression tests. - #287 — browser RetroAchievements (wasm trampoline + deployable auth-proxy, ADR 0015; live login is a documented deploy-to-verify step) + Vs. DualSystem presentation in the libretro core. Vs. DualSystem wasm present honestly deferred. A real rustynes-libretro push_audio realloc bug fixed en route. No default-build behavior change. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Creator Tools half of RustyNES v2.1.10 "Creator Tools & Web Parity" — TAStudio depth (B8) + Lua API breadth (B9). All additive; the deterministic
#![no_std]core is untouched, so AccuracyCoin holds 141/141 (100%) and save-state / TAS replay stays byte-identical.B8 — TAStudio depth
Named markers and named branch save-slots already shipped (v1.6.0); this deepens the greenzone half with force-greenzone:
Greenzonetracks a normalised, span-clamped forced range plus the anchors it itself added;store()pins forced frames as non-evictable anchors; theseek/record_framecapture loops now store at every forced frame. Shrinking / clearing the range releases only force-greenzone's own anchors (marker / branch anchors untouched).MAX_FORCED_GREENZONE_FRAMES= 10,800 ≈ 3 min @ 60 fps; documented budget). Pure caching optimisation — a seek into the forced range is bit-identical to a linear replay (new editor test).B9 — Lua API breadth
emu.drawLine— the fourth HUD primitive (DrawCmd::Line), full mlua + piccolo parity, rendered viapainter.line_segment(native + wasm overlay).memory:read_palette/memory:read_chr— explicit palette ($3F00-$3F1F, 6-bit) and CHR ($0000-$1FFF, mapper-banked) read domains via the side-effect-free debug-peek path (the*Debugvariant by construction).reset/spriteZeroHit/codeBreakonemu.addEventCallback, host-fired via newScriptEngine::fire_reset/fire_sprite_zero_hit/fire_code_break(+needs_*_eventgates).resetwired atdo_reset;spriteZeroHitsampled per-frame in the script pump via a non-destructivepeek($2002) & 0x40.drawLine, a read-paritymemorytable (CPU/PPU/palette/CHR/OAM from an extended per-frame snapshot;pokekeeps the gated + deferred contract), and anaddEventCallbackno-op so portable scripts don't error on wasm. Remaining native-only carve-out (ADR 0012): per-access / per-interrupt replay callbacks + host-fired events.hud_graph.lua,palette_viewer.lua,lifecycle_events.lua, all compile-time embedded + exercised bybundled_example_scripts_load_and_run.Determinism
Reads are debug-peeks; writes stay gated / deferred exactly as before; greenzone force is a caching-only change. AccuracyCoin 141/141, the greenzone / editor bit-identity tests pass, and both
rustynes-scriptbackends' tests pass.Gates (all green)
cargo fmt --all --check;cargo clippy --workspace --all-targets -D warningscargo clippy -p rustynes-frontendscripting/scripting,hd-pack--lib --bins, default +wasm-canvas) +rustynes-script --features script-wasm(piccolo)RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps; markdownlint (pinned v0.39.0)cargo test -p rustynes-test-harness --features test-roms --test accuracycoin— 141/141rustynes-scripttests (mlua + piccolo) +rustynes-frontendtastudio / greenzone testsDocs
docs/scripting.md(drawLine, palette/CHR + the*Debugcontract, lifecycle events, piccolo parity matrix, example library),docs/frontend.md(force-greenzone subsystem),CHANGELOG.md[Unreleased].🤖 Generated with Claude Code