diff --git a/.gitignore b/.gitignore index bfff5861..68458ce9 100644 --- a/.gitignore +++ b/.gitignore @@ -237,3 +237,35 @@ WARP.md # Antigravity PR reviewer: large-diff on-disk handoff (transient, removed by the script). /.agy-review-work/ + +# --- Frontend capture / telemetry output (`v1.25.0`, T-FP-B / T-FP-G1) --- +# These land in the user's data directory by default, but the emulator can be pointed anywhere and +# is routinely run straight out of a checkout — so a stray capture must not become a commit +# candidate. +# +# Each pattern's breadth was checked against what is actually COMMITTED rather than assumed. The +# extensions below have zero tracked files, so they are safe to match recursively: +**/*.y4m +**/*.wav +**/rustysnes-perf-*.csv + +# Screenshots are the exception, and the reason the rest of this block is not simply `*.png`. +# `screenshot::file_stem` writes `-NNNN.png`, defaulting to `rustysnes-NNNN` +# — but the repo tracks **712** PNGs, 30 of them named `rustysnes-*.png` (the branding banners), +# and `assets/RustySNES_Icon_Pack/transparent/icon/rustysnes-icon-transparent-1024.png` even +# matches a generic `*-NNNN.png`. So: +# - the default-title form is exact enough to match recursively (zero tracked collisions); +# - the ROM-titled form stays ROOT-ANCHORED, since a recursive version would ignore real assets. +**/rustysnes-[0-9][0-9][0-9][0-9].png +/*-[0-9][0-9][0-9][0-9].png + +# The recommended destination for captures — point the emulator here and nothing above has to be +# relied on at all. `**/` already covers the repo root, so no separate `/captures/` is needed. +**/captures/ + +# Shader presets and symbol maps a user drops in to try (inputs, never project files). Zero +# tracked files of any of these extensions, so recursive is safe. +**/*.slangp +**/*.cgp +**/*.sym +/presets/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 64b97516..365cf633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.25.0] "Workbench" - 2026-07-30 + +**The frontend catches up with the engine.** RustySNES's emulation core has been at the Mesen2/ares +bar for several releases while its egui shell lagged its own NES sibling; this release closes that +gap in ten reviewed increments (`T-FP-A` … `T-FP-G2`), taking the frontend from "boots games" to a +tool you can debug, profile, record, retime, and author a TAS with. + +Everything here is **additive and default-off or default-inert**: `video.stack` defaults to `Off`, +the preset chain is empty, the debugger needs `debug-hooks`, GPU timing needs `gpu-timing`, and the +per-voice mixer's unity gain is a bit-exact bypass in the DSP. An existing `config.toml` renders and +sounds byte-identically, and no save-state format changed (`FORMAT_VERSION` stays at 10). + +The five headline capabilities: + +- **A real debugger** — conditional breakpoints over an expression evaluator, an instruction trace, + a control-flow event log, a WRAM access heat map, symbol maps, a memory editor with freezes, an + OAM viewer, a cart memory map, and an inline 65C816 assembler that assembles by *searching the + real disassembler* rather than carrying a second opcode table. +- **A multi-pass shader stack** with `RetroArch`-shaped per-pass semantics, richer CRT and an NTSC + composite pass, `.slangp`/`.cgp` preset parsing, and a best-effort GLSL→WGSL bridge through naga — + every untranslatable pass named rather than silently black. Verified by **offscreen GPU goldens**, + which is new infrastructure: the previous shader tests only checked that WGSL parsed. +- **Observability and honest pacing** — p50/p95/p99/max distributions over emulation, present, GPU + and audio-buffer time, an opt-in CSV session log, and `PacingMode` turned from a setting nothing + read into one resolved against the monitor's real refresh and the surface's real present-mode caps, + with any declined request **reported** rather than silently downgraded. +- **An 8-voice audio mixer** (gain, mute, solo, VU meters) plus **compressed rewind** — XOR-delta and + RLE with periodic keyframes, no new dependency, bit-identical round-trip. +- **TAStudio** — a piano roll, a greenzone that reuses the rewind compression, and invalidation as + the *default* consequence of an edit. + +Plus the infrastructure the above plugs into: compile-time-checked localisation in five languages +wired through the whole menu bar and every Settings tab, a populated Help menu, and `Settings` split +into per-tab renderers. + +### A recurring defect this release was written to remove + +Five settings shipped in earlier versions that **round-tripped through `config.toml` while nothing +read them** — `gamepad`, `video.integer_scale`, `config.p2`, `audio.device`, and `PacingMode`. Each +looked functional and did nothing. All five are now live, and the pattern was explicitly hunted: +grep a setting's *readers*, not its definition. Two more instances were caught during review of this +very release — a shader preset path that persisted while nothing loaded it at startup, and an i18n +catalogue with sixty translated strings and eight consumers. + ### Fixed - **Soft-patching: a hostile BPS patch could panic instead of erroring.** Two arithmetic paths took diff --git a/Cargo.lock b/Cargo.lock index 16ed41b4..eee60b93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,7 +29,7 @@ dependencies = [ [[package]] name = "accuracysnes-gen" -version = "1.24.0" +version = "1.25.0" [[package]] name = "adler2" @@ -3825,7 +3825,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustysnes-android" -version = "1.24.0" +version = "1.25.0" dependencies = [ "android_logger", "bytemuck", @@ -3841,7 +3841,7 @@ dependencies = [ [[package]] name = "rustysnes-apu" -version = "1.24.0" +version = "1.25.0" dependencies = [ "bitflags 2.13.0", "rustysnes-savestate", @@ -3849,7 +3849,7 @@ dependencies = [ [[package]] name = "rustysnes-cart" -version = "1.24.0" +version = "1.25.0" dependencies = [ "bitflags 2.13.0", "rustysnes-savestate", @@ -3858,7 +3858,7 @@ dependencies = [ [[package]] name = "rustysnes-cheevos" -version = "1.24.0" +version = "1.25.0" dependencies = [ "cc", "ureq", @@ -3866,7 +3866,7 @@ dependencies = [ [[package]] name = "rustysnes-core" -version = "1.24.0" +version = "1.25.0" dependencies = [ "criterion", "rustysnes-apu", @@ -3881,7 +3881,7 @@ dependencies = [ [[package]] name = "rustysnes-cpu" -version = "1.24.0" +version = "1.25.0" dependencies = [ "bitflags 2.13.0", "rustysnes-savestate", @@ -3889,7 +3889,7 @@ dependencies = [ [[package]] name = "rustysnes-frontend" -version = "1.24.0" +version = "1.25.0" dependencies = [ "anstyle", "arboard", @@ -3931,11 +3931,11 @@ dependencies = [ [[package]] name = "rustysnes-gfx-shaders" -version = "1.24.0" +version = "1.25.0" [[package]] name = "rustysnes-ios" -version = "1.24.0" +version = "1.25.0" dependencies = [ "bytemuck", "log", @@ -3948,7 +3948,7 @@ dependencies = [ [[package]] name = "rustysnes-libretro" -version = "1.24.0" +version = "1.25.0" dependencies = [ "libc", "rust-libretro", @@ -3957,7 +3957,7 @@ dependencies = [ [[package]] name = "rustysnes-mobile" -version = "1.24.0" +version = "1.25.0" dependencies = [ "rustysnes-cart", "rustysnes-core", @@ -3967,14 +3967,14 @@ dependencies = [ [[package]] name = "rustysnes-monetization" -version = "1.24.0" +version = "1.25.0" dependencies = [ "uniffi", ] [[package]] name = "rustysnes-netplay" -version = "1.24.0" +version = "1.25.0" dependencies = [ "js-sys", "rustysnes-core", @@ -3986,7 +3986,7 @@ dependencies = [ [[package]] name = "rustysnes-ppu" -version = "1.24.0" +version = "1.25.0" dependencies = [ "bitflags 2.13.0", "rustysnes-cart", @@ -3996,14 +3996,14 @@ dependencies = [ [[package]] name = "rustysnes-savestate" -version = "1.24.0" +version = "1.25.0" dependencies = [ "thiserror 2.0.18", ] [[package]] name = "rustysnes-script" -version = "1.24.0" +version = "1.25.0" dependencies = [ "mlua", "rustysnes-core", @@ -4012,7 +4012,7 @@ dependencies = [ [[package]] name = "rustysnes-test-harness" -version = "1.24.0" +version = "1.25.0" dependencies = [ "insta", "rustysnes-apu", diff --git a/Cargo.toml b/Cargo.toml index 5ab569df..82133186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "1.24.0" +version = "1.25.0" edition = "2024" rust-version = "1.96" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index d998b528..30df6379 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

- Build Status License: MIT OR Apache-2.0 Version Rust: 1.96
+ Build Status License: MIT OR Apache-2.0 Version Rust: 1.96
65C816 oracle: 0-diff SPC700 oracle: 0-diff GitHub Pages
Platform

@@ -623,9 +623,14 @@ Material-for-MkDocs documentation handbook at ## Current Release -RustySNES's current release is **v1.8.0 "Tracepoint"**. See [`docs/STATUS.md`](docs/STATUS.md) -and [`CHANGELOG.md`](CHANGELOG.md) for the full release history (`v0.1.0` through `v1.8.0`) and -per-release detail. +RustySNES's current release is **v1.25.0 "Workbench"** — the frontend release that brings the +egui shell up to the engine's level: a full debugger, a multi-pass shader stack with `.slangp` +support, performance distributions and hardware-resolved frame pacing, an 8-voice audio mixer with +compressed rewind, A/V capture, a virtual pad, and TAStudio. It is **additive throughout**: an +existing `config.toml` renders and sounds byte-identically, and no save-state format changed. + +See [`docs/STATUS.md`](docs/STATUS.md) and [`CHANGELOG.md`](CHANGELOG.md) for the full release +history (`v0.1.0` through `v1.25.0`) and per-release detail. - **Download:** the [GitHub Releases](https://github.com/doublegate/RustySNES/releases) page — desktop binaries for Linux, macOS (aarch64), and Windows. @@ -656,6 +661,16 @@ emulator, tracked in lockstep rather than a frozen snapshot native 3x default. - **`v1.8.0 "Tracepoint"`** — debugger depth II: a Memory Compare panel and an in-app Docs/glossary panel. +- **`v1.9.0 "Marionette"` - `v1.20.0 "Aperture"`** — scripting reach, HD packs, RetroAchievements, + shaders and accessibility, the Android/iOS mobile ladder, a PGO/BOLT pipeline, and a UI/UX-parity + pass — all with the accuracy dashboard unchanged. +- **`v1.21.0 "Touchstone"` - `v1.24.0 "Ensemble"`** — the accuracy rungs: the per-dot PPU compositor + becomes the sole renderer, the DSP-1 Mode-7 floor is fixed and then the NEC-DSP host interface + made pin-exact, and DSP-3 + ST011 complete the NEC coprocessor family. +- **`v1.25.0 "Workbench"`** — the frontend catches up with the engine, in ten reviewed increments + (`T-FP-A`-`T-FP-G2`): debugger, shader stack + `.slangp`/GLSL bridge, observability + real pacing, + audio mixer + compressed rewind, A/V capture + virtual pad + databases, and TAStudio. Five + settings that had round-tripped through `config.toml` while nothing read them are now live. The full roadmap lives in [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) (the phase spine) and [`to-dos/VERSION-PLAN.md`](to-dos/VERSION-PLAN.md) (the named release ladder, including the diff --git a/crates/rustysnes-android/Cargo.toml b/crates/rustysnes-android/Cargo.toml index a6964c7a..a278d27f 100644 --- a/crates/rustysnes-android/Cargo.toml +++ b/crates/rustysnes-android/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-android" description = "RustySNES: Android JNI presentation host (wgpu-on-Surface, no emulation logic)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-apu/Cargo.toml b/crates/rustysnes-apu/Cargo.toml index 2d066ed3..227c2c28 100644 --- a/crates/rustysnes-apu/Cargo.toml +++ b/crates/rustysnes-apu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-apu" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-cart/Cargo.toml b/crates/rustysnes-cart/Cargo.toml index c4ff33f6..56ed2088 100644 --- a/crates/rustysnes-cart/Cargo.toml +++ b/crates/rustysnes-cart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-cart" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-cheevos/Cargo.toml b/crates/rustysnes-cheevos/Cargo.toml index a5d9f8a7..e6a4c53e 100644 --- a/crates/rustysnes-cheevos/Cargo.toml +++ b/crates/rustysnes-cheevos/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-cheevos" description = "Native-only RetroAchievements (rcheevos) FFI wrapper for RustySNES" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-core/Cargo.toml b/crates/rustysnes-core/Cargo.toml index 16912f2a..3d8c530b 100644 --- a/crates/rustysnes-core/Cargo.toml +++ b/crates/rustysnes-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-core" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-cpu/Cargo.toml b/crates/rustysnes-cpu/Cargo.toml index 8e3b64eb..64ec39a5 100644 --- a/crates/rustysnes-cpu/Cargo.toml +++ b/crates/rustysnes-cpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-cpu" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-frontend/Cargo.toml b/crates/rustysnes-frontend/Cargo.toml index e48ba047..e7169392 100644 --- a/crates/rustysnes-frontend/Cargo.toml +++ b/crates/rustysnes-frontend/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-frontend" description = "RustySNES: cross-platform SNES emulator binary (winit + wgpu + cpal + egui)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-gfx-shaders/Cargo.toml b/crates/rustysnes-gfx-shaders/Cargo.toml index b799ddfe..9b79da6e 100644 --- a/crates/rustysnes-gfx-shaders/Cargo.toml +++ b/crates/rustysnes-gfx-shaders/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-gfx-shaders" description = "RustySNES: shared WGSL presentation-shader sources (blit + post-filters)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-ios/Cargo.toml b/crates/rustysnes-ios/Cargo.toml index b848d184..52982820 100644 --- a/crates/rustysnes-ios/Cargo.toml +++ b/crates/rustysnes-ios/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-ios" description = "RustySNES: iOS Metal presentation host (wgpu-on-CAMetalLayer, no emulation logic)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-mobile/Cargo.toml b/crates/rustysnes-mobile/Cargo.toml index cb15d9a8..5f587169 100644 --- a/crates/rustysnes-mobile/Cargo.toml +++ b/crates/rustysnes-mobile/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-mobile" description = "RustySNES: UniFFI mobile bridge (Kotlin/Swift bindings over the emulation core)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-monetization/Cargo.toml b/crates/rustysnes-monetization/Cargo.toml index cc9ea3fa..59bec76c 100644 --- a/crates/rustysnes-monetization/Cargo.toml +++ b/crates/rustysnes-monetization/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-monetization" description = "RustySNES: dormant entitlement/ad-pacing policy scaffold (mobile-only, never a dependency of the deterministic core)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-netplay/Cargo.toml b/crates/rustysnes-netplay/Cargo.toml index f6c92fc2..7416242b 100644 --- a/crates/rustysnes-netplay/Cargo.toml +++ b/crates/rustysnes-netplay/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-netplay" description = "RustySNES: GGPO-style rollback netplay (UDP native, WebRTC browser)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-ppu/Cargo.toml b/crates/rustysnes-ppu/Cargo.toml index bb5a759b..1c6fcad6 100644 --- a/crates/rustysnes-ppu/Cargo.toml +++ b/crates/rustysnes-ppu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-ppu" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-savestate/Cargo.toml b/crates/rustysnes-savestate/Cargo.toml index be49397c..19972bb4 100644 --- a/crates/rustysnes-savestate/Cargo.toml +++ b/crates/rustysnes-savestate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-savestate" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-script/Cargo.toml b/crates/rustysnes-script/Cargo.toml index 9f1b4453..d4377459 100644 --- a/crates/rustysnes-script/Cargo.toml +++ b/crates/rustysnes-script/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustysnes-script" description = "RustySNES: sandboxed Lua 5.4 scripting (memory read/write + a per-frame callback)" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rustysnes-test-harness/Cargo.toml b/crates/rustysnes-test-harness/Cargo.toml index a1735b74..55819855 100644 --- a/crates/rustysnes-test-harness/Cargo.toml +++ b/crates/rustysnes-test-harness/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustysnes-test-harness" -version = "1.24.0" +version = "1.25.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/docs/STATUS.md b/docs/STATUS.md index a7939a32..5735771a 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -3,7 +3,7 @@ This file is authoritative for per-suite pass counts, the board / coprocessor matrix, and version policy. Everything else defers to it. -**Current release:** `v1.24.0 "Ensemble"` (`v1.23.0 "Cadence"`, `v1.22.0 "Horizon"`, `v1.21.0 "Touchstone"`, `v0.1.0 "Foundation"`, +**Current release:** `v1.25.0 "Workbench"` (`v1.24.0 "Ensemble"`, `v1.23.0 "Cadence"`, `v1.22.0 "Horizon"`, `v1.21.0 "Touchstone"`, `v0.1.0 "Foundation"`, `v0.2.0 "Persistence"`, `v0.3.0 "Continuum"`, `v0.4.0 "Completion"`, `v0.5.0 "Fidelity"`, `v0.6.0 "Shippable"`, `v0.7.0 "Resolution"`, `v0.8.0 "Community"`, `v0.9.0 "Threshold"`, `v1.0.0 "Zenith"`, `v1.0.1 "Aftertouch"`, `v1.1.0 "Latchkey"`, `v1.2.0 "Phosphor"`, @@ -41,6 +41,24 @@ It is a model-consistency + response-latency change, **not** a rendering change chips are byte-identical to the `v1.22.0` baseline — and it structurally retires the "stop at first `RQM=set`" hazard class. The DSP sub-clock phase is serialized, so save-state `FORMAT_VERSION` bumps `9 → 10` (older blobs loud-fail per `docs/adr/0006`). +**`v1.25.0 "Workbench"` is a frontend release — it changes no emulation behaviour at all.** The +engine has been at the accuracy bar for several versions while the egui shell lagged its own NES +sibling; this closes that gap in ten reviewed increments (`T-FP-A`-`T-FP-G2`): a real debugger +(expression-conditional breakpoints, instruction trace, control-flow event log, WRAM access heat +map, symbol maps, memory editor with freezes, OAM viewer, cart map, inline 65C816 assembler), a +multi-pass shader stack with `.slangp` parsing and a best-effort GLSL→WGSL naga bridge (verified by +**offscreen GPU goldens** — new infrastructure; the prior shader tests only checked that WGSL +parsed), p50/p95/p99/max performance distributions plus a CSV session log, hardware-resolved frame +pacing, an 8-voice audio mixer, XOR-delta+RLE rewind compression, A/V capture, a virtual pad, and +TAStudio. Everything is additive and **default-off or default-inert** — `video.stack` defaults to +`Off`, the debugger needs `debug-hooks`, GPU timing needs `gpu-timing`, and a unity per-voice gain is +a bit-exact bypass in the DSP — so an existing `config.toml` renders and sounds byte-identically. +**No save-state format change** (`FORMAT_VERSION` stays at 10), and the accuracy dashboard, +per-suite pass counts, and coprocessor tier matrix below are unchanged. Two engine-side fixes ride +along, both found by frontend work and both invisible to rendering: `Cpu::interrupts_taken` +bookkeeping so a debugger can tell a vectored step from an executed one, and a `delta::Chain` +eviction defect (a keyframe evicted from the front orphaned every delta that referenced it) that +affected the **rewind buffer** as much as the new greenzone. See `CHANGELOG.md` for full per-release detail. `v1.0.0` closes the production-cut gate: `Board: Send` (unblocking `emu-thread` to compile/test/lint clean for the first time, though it stays off-by-default pending full feature parity — see `docs/frontend.md`), the five diff --git a/scripts/agy-review.sh b/scripts/agy-review.sh index 17ec42ac..4a300629 100755 --- a/scripts/agy-review.sh +++ b/scripts/agy-review.sh @@ -574,13 +574,30 @@ else # `--arg`/`--argjson` rather than shell interpolation into the filter: the marker is an HTML # comment today, but a quote or a backslash in it would otherwise break the jq program itself # rather than simply not matching. - gh api "repos/${REPO}/issues/${PR}/comments" --paginate \ - --arg marker "$MARKER" --argjson new_id "$new_comment_id" \ - --jq '.[] | select(.user.type == "Bot" and .user.login == "github-actions[bot]") | select(.body | contains($marker)) | select(.id != $new_id) | .id' 2>/dev/null \ - | while read -r cid; do - [ -n "$cid" ] || continue - if ! gh api -X DELETE "repos/${REPO}/issues/comments/${cid}" >/dev/null 2>&1; then - log "warning: could not delete prior review comment ${cid}; a duplicate may result" - fi - done + # + # Those are JQ flags, so the JSON is fetched raw and piped into a real `jq` — `gh api` has no + # `--arg`/`--argjson` of its own and rejects them. Handing them to `gh api --jq` made it exit + # non-zero on every run; with the old `2>/dev/null` swallowing the message and `set -o pipefail` + # in force, the script then died *after* posting, so the stale comments were never deleted and + # the job went red for a reason nothing printed. stderr is kept this time for exactly that + # reason. (`--paginate` without `--jq` emits one JSON array per page; `jq` reads that stream + # fine, applying `.[]` to each.) + stale_ids="$( + gh api "repos/${REPO}/issues/${PR}/comments" --paginate \ + | jq -r --arg marker "$MARKER" --argjson new_id "$new_comment_id" \ + '.[] | select(.user.type == "Bot" and .user.login == "github-actions[bot]") | select(.body | contains($marker)) | select(.id != $new_id) | .id' + )" || { + log "warning: could not list prior review comments; leaving them in place" + stale_ids="" + } + while read -r cid; do + [ -n "$cid" ] || continue + if ! gh api -X DELETE "repos/${REPO}/issues/comments/${cid}" >/dev/null 2>&1; then + log "warning: could not delete prior review comment ${cid}; a duplicate may result" + fi + done <<< "$stale_ids" fi + +# The delete loop above is the last real work; end on a defined status so a stray non-zero from +# it can never be mistaken for "the review failed" once the comment is already published. +exit 0 diff --git a/to-dos/VERSION-PLAN.md b/to-dos/VERSION-PLAN.md index 5885e65b..9fea66f0 100644 --- a/to-dos/VERSION-PLAN.md +++ b/to-dos/VERSION-PLAN.md @@ -1500,3 +1500,58 @@ board renders byte-identically; no save-state format change. `docs/cart.md`, `docs/STATUS.md`. `cargo test --workspace` green; fmt, clippy (default + per-feature), no_std, doc all green. This completes the NEC DSP family: DSP-1/2/3/4 + ST010/011, six chips, one engine. + +### `v1.25.0 "Workbench"` — the frontend catches up with the engine — **RELEASED 2026-07-30** + +Ten reviewed increments (`T-FP-A` … `T-FP-G2`, one PR each) taking the egui shell from "boots games" +to a tool you can debug, profile, record, retime, and author a TAS with. **No emulation behaviour +changes**: additive and default-off or default-inert throughout, an existing `config.toml` renders +and sounds byte-identically, and save-state `FORMAT_VERSION` stays at 10. + +- **`T-FP-A` Infrastructure.** Compile-time-checked localisation (`crate::i18n`) in English, Spanish, + French, German, and Japanese — each locale is an array indexed by a `Msg` enum, so **adding a + string without translating it fails the build**, where a runtime map would surface a blank label + nobody notices. Wired through the entire menu bar and all four Settings tabs. Plus `crate::perf`'s + distribution primitives, a populated Help menu, and `render_settings` split into per-tab renderers. +- **`T-FP-B` Observability + pacing.** p50/p95/p99/max over produce/present/GPU/audio-occupancy, + sparklines, an opt-in CSV session log, an optional `gpu-timing` feature over `wgpu` timestamp + queries (reporting *unavailable* rather than a fabricated number where the adapter lacks it), and + `PacingMode` made real — resolved against the monitor's reported refresh and the surface's actual + present-mode caps, with a request the hardware cannot honour **reported as declined**. +- **`T-FP-C1`/`C2` The debugger.** Core `debug-hooks` instruction trace, control-flow event log, and + WRAM access heat map on `watchpoint.rs`'s zero-cost-when-off contract; then memory editor with + freezes, OAM viewer, cart map, an expression evaluator driving conditional breakpoints, symbol + maps, trace/callstack/event viewers, and an inline 65C816 assembler that **searches the real + disassembler** instead of carrying a second opcode table — correct by construction against the + decoder, and a new opcode becomes assemblable for free. +- **`T-FP-D`/`E` The shader stack.** An ordered multi-pass chain with `RetroArch`-shaped per-pass + keys, generic `#pragma parameter` sliders generated *from the pass*, richer CRT and an RGB-domain + NTSC pass, then `.slangp`/`.cgp` parsing and a best-effort GLSL→WGSL bridge through naga where + every untranslatable pass is **named** rather than silently black. Verified by **offscreen wgpu + goldens** — new infrastructure, and the enabler: the prior shader tests only checked that WGSL + parsed, which a shader that compiles and renders the wrong thing passes. +- **`T-FP-F` Audio + rewind.** Additive per-voice gains and output taps at the existing per-voice-mute + site (unity is a **bit-exact bypass**), an 8-voice mixer panel, XOR-delta + RLE rewind compression + with periodic keyframes and **no new dependency**, and `save_state_into` removing run-ahead's + per-frame allocation. +- **`T-FP-G1`/`G2` Media + TAStudio.** Raw `.y4m` + `.wav` capture with an exact-rational frame rate + and drift **reported, not corrected**; a virtual pad sharing one geometry source with its renderer; + CRC32-keyed game and Game Genie databases; input macros; and TAStudio — piano roll, greenzone + reusing the rewind compression, invalidation as the default consequence of an edit. + +**The defect class this release was written to remove.** Five settings shipped earlier that +round-tripped through `config.toml` while nothing read them (`gamepad`, `video.integer_scale`, +`config.p2`, `audio.device`, `PacingMode`) are now live; two more of the same shape were caught +during review of this release itself. The rule that came out of it: **grep a setting's readers, not +its definition.** + +**Two engine-side fixes ride along**, both found by frontend work, both invisible to rendering: +`Cpu::interrupts_taken` bookkeeping so a debugger can distinguish a vectored step from an executed +one (without it, an NMI arriving after a `JSR` was logged as a second `Call`), and a `delta::Chain` +eviction defect where a keyframe evicted from the front orphaned every delta that referenced it — +which affected the **rewind buffer** as much as the new greenzone. + +**Verification.** `cargo test --workspace` 804 passing / 1 ignored; per-feature frontend clippy +across all eight feature sets (the `--features full` set excludes `emu-thread`, so default+full is +not enough); `rustysnes-apu`/`-core`/`-cpu` clippy; `no_std` core builds for `thumbv7em-none-eabihf`; +fmt and `RUSTDOCFLAGS=-D warnings cargo doc` clean; the AccuracySNES battery unchanged.