Skip to content

feat(creator-tools): TAStudio force-greenzone + Lua API breadth (v2.1.10 B8/B9)#288

Merged
doublegate merged 8 commits into
mainfrom
feat/v2.1.10-creator-tools
Jul 12, 2026
Merged

feat(creator-tools): TAStudio force-greenzone + Lua API breadth (v2.1.10 B8/B9)#288
doublegate merged 8 commits into
mainfrom
feat/v2.1.10-creator-tools

Conversation

@doublegate

Copy link
Copy Markdown
Owner

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:

  • A "Force GZ" toggle in the piano-roll header guarantees a cached save-state at every frame in a bounded range, so scrubbing / rewinding anywhere inside it is instant (vs. the density-tiered keyframe skeleton).
  • Greenzone tracks a normalised, span-clamped forced range plus the anchors it itself added; store() pins forced frames as non-evictable anchors; the seek / record_frame capture loops now store at every forced frame. Shrinking / clearing the range releases only force-greenzone's own anchors (marker / branch anchors untouched).
  • Bounded memory (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 via painter.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 *Debug variant by construction).
  • Lifecycle events reset / spriteZeroHit / codeBreak on emu.addEventCallback, host-fired via new ScriptEngine::fire_reset / fire_sprite_zero_hit / fire_code_break (+ needs_*_event gates). reset wired at do_reset; spriteZeroHit sampled per-frame in the script pump via a non-destructive peek($2002) & 0x40.
  • piccolo (wasm) parity upliftdrawLine, a read-parity memory table (CPU/PPU/palette/CHR/OAM from an extended per-frame snapshot; poke keeps the gated + deferred contract), and an addEventCallback no-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.
  • Example script libraryhud_graph.lua, palette_viewer.lua, lifecycle_events.lua, all compile-time embedded + exercised by bundled_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-script backends' tests pass.

Gates (all green)

  • cargo fmt --all --check; cargo clippy --workspace --all-targets -D warnings
  • cargo clippy -p rustynes-frontend scripting / scripting,hd-pack
  • wasm32 clippy (--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 accuracycoin141/141
  • rustynes-script tests (mlua + piccolo) + rustynes-frontend tastudio / greenzone tests

Docs

docs/scripting.md (drawLine, palette/CHR + the *Debug contract, lifecycle events, piccolo parity matrix, example library), docs/frontend.md (force-greenzone subsystem), CHANGELOG.md [Unreleased].

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 12, 2026 04:12
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

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 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-parity memory table backed by an expanded per-frame snapshot, and a no-op addEventCallback for 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.

Comment thread crates/rustynes-frontend/src/tastudio/greenzone.rs
Comment thread crates/rustynes-frontend/src/tastudio/greenzone.rs
Comment thread examples/scripts/lifecycle_events.lua
Comment thread examples/scripts/lifecycle_events.lua
doublegate and others added 7 commits July 12, 2026 09:22
…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).
@doublegate doublegate force-pushed the feat/v2.1.10-creator-tools branch from a2f17f1 to 601225f Compare July 12, 2026 13:26
…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 doublegate enabled auto-merge (squash) July 12, 2026 13:38
@doublegate doublegate merged commit 680d035 into main Jul 12, 2026
21 checks passed
@doublegate doublegate deleted the feat/v2.1.10-creator-tools branch July 12, 2026 13:53
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>
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