Skip to content

Commit 30275b8

Browse files
authored
docs: backfill 3 ADRs, add DRAM-refresh implementation research (#34)
docs: ADR backfill (0007-0009) + v0.5.0/v0.6.0 ladder research and CI infrastructure. Documentation: docs/audit/ (SPC7110 boot-crash trail), Sharp S-RTC naming fixes, 3 new ADRs (versioning/release-process, ExLoROM decode-formula sourcing, ST018 detection+catch-up architecture), docs/DOCUMENTATION_INDEX.md, docs/benchmarks.md + a real Criterion headless-frame benchmark (3.27ms/frame baseline against the ~2ms target). v0.6.0 'Shippable': SHA-256 checksums on release archives; new security.yml CI gate (cargo audit + cargo deny), verified green end-to-end, suppressing 3 documented no-upgrade-path RUSTSEC advisories after explicit review. v0.5.0 'Fidelity' hardware-gotcha research: fixed a real doc/code drift (HDMA already fires at the hardware-correct dot 276, docs wrongly claimed scanline-boundary+deferred); $4203/$4206 multiply/divide overlap reclassified as an intentional non-goal (SNESdev errata: genuinely undefined hardware behavior); open-bus-via-HDMA-latch and the DMA/HDMA-collision crash quirk both researched to ground truth and correctly NOT force-landed after real regression risk was found (a prototype open-bus fix broke all 24 Super FX golden hashes) or no oracle existed to verify against; mid-scanline/HDMA-driven register timing — found and documented a genuine, previously-undocumented off-by-one-line compositor bug (not fixed this pass, hottest code path, no dedicated test ROM yet); hi-res color-math precision confirmed blocked on a real feature gap (no 512-wide hi-res output yet), not a precision tweak. CI green (audit/deny/lint), all Gemini/Copilot review threads resolved, human-reviewed and approved.
1 parent c6432ae commit 30275b8

25 files changed

Lines changed: 1369 additions & 50 deletions

‎.cargo/audit.toml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# cargo-audit configuration (the "Dependency Audit" CI job, `.github/workflows/security.yml`).
2+
#
3+
# Keep this ignore list IN SYNC with `deny.toml`'s `[advisories].ignore` -- the two tools scan
4+
# the same RUSTSEC database and gate the same workflow. Every entry's full justification lives in
5+
# deny.toml; this file only mirrors the IDs. Never suppress an advisory reachable from a runtime
6+
# code path. Each suppression below was independently verified against RustySNES's own
7+
# Cargo.lock (not copied from RustyNES's equivalent file, which happens to hit the identical
8+
# dependency chain via the same winit/egui/wgpu frontend stack) and explicitly approved by the
9+
# user (2026-07-08) after review.
10+
11+
[advisories]
12+
ignore = [
13+
# ttf-parser unmaintained (informational, not a vulnerability) -- transitive via winit's
14+
# Wayland client-side-decoration font stack (winit -> sctk-adwaita -> ab_glyph ->
15+
# owned_ttf_parser -> ttf-parser 0.25.1, already the latest release); no upgrade path exists
16+
# until winit's decoration stack moves off ab_glyph. Full rationale in deny.toml.
17+
"RUSTSEC-2026-0192",
18+
# quick-xml 0.39.4 (two real advisories) -- consumed ONLY by the wayland-scanner PROC-MACRO
19+
# parsing vendored Wayland protocol XML at COMPILE TIME (trusted input, never runtime/
20+
# untrusted data); no upgrade path until wayland-scanner (its own max version, 0.31.10) moves
21+
# off its ^0.39 quick-xml pin. Full rationale in deny.toml.
22+
"RUSTSEC-2026-0194",
23+
"RUSTSEC-2026-0195",
24+
]

‎.github/workflows/release.yml‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ jobs:
7777
Compress-Archive -Force -Path $stage -DestinationPath "$stage.zip"
7878
Add-Content -Path $env:GITHUB_ENV -Value "ASSET=$stage.zip"
7979
80+
# A detached SHA-256 checksum alongside each archive, so a downloader can verify the
81+
# binary they fetched matches what CI actually built without trusting GitHub's transport
82+
# alone. Neither `sha256sum` (GNU coreutils — absent on macOS) nor `shasum` (Perl-based —
83+
# not guaranteed present in Windows' Git-Bash `PATH`) is available on every runner this
84+
# matrix uses, so try both rather than assuming one.
85+
- name: Checksum
86+
shell: bash
87+
run: |
88+
set -euo pipefail
89+
if command -v sha256sum >/dev/null 2>&1; then
90+
sha256sum "$ASSET" > "$ASSET.sha256"
91+
else
92+
shasum -a 256 "$ASSET" > "$ASSET.sha256"
93+
fi
94+
8095
# Attach to the GitHub release matching this tag, creating a minimal one first if it
8196
# doesn't exist yet (the release ceremony normally authors the real release notes with
8297
# `gh release create --notes-file` around the same time the tag is pushed — this is a
@@ -102,4 +117,4 @@ jobs:
102117
exit 1
103118
}
104119
fi
105-
gh release upload "$TAG" "$ASSET" --repo "$REPO" --clobber
120+
gh release upload "$TAG" "$ASSET" "$ASSET.sha256" --repo "$REPO" --clobber

‎.github/workflows/security.yml‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Security
2+
3+
# Dependency-vulnerability scan (cargo-audit) + license/advisory/source policy (cargo-deny).
4+
#
5+
# Pure-documentation pushes are skipped: they cannot change Cargo.lock or any `.rs` code, so
6+
# there is nothing for these jobs to find. The weekly `schedule` cron still re-runs the full
7+
# suite, so a NEW advisory published against an unchanged dependency is still caught even when no
8+
# code is pushed.
9+
on:
10+
push:
11+
branches: [main]
12+
paths-ignore:
13+
- "**/*.md"
14+
- "docs/**"
15+
- "ref-docs/**"
16+
- "to-dos/**"
17+
- "LICENSE-*"
18+
- ".gitignore"
19+
pull_request:
20+
branches: [main]
21+
paths-ignore:
22+
- "**/*.md"
23+
- "docs/**"
24+
- "ref-docs/**"
25+
- "to-dos/**"
26+
- "LICENSE-*"
27+
- ".gitignore"
28+
schedule:
29+
- cron: "0 0 * * 1"
30+
workflow_dispatch:
31+
32+
concurrency:
33+
group: security-${{ github.workflow }}-${{ github.ref }}
34+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
35+
36+
# Least-privilege default: neither job below writes to the repo or uploads anything -- both only
37+
# read the checked-out tree and Cargo.lock.
38+
permissions:
39+
contents: read
40+
41+
jobs:
42+
audit:
43+
name: Dependency Audit
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@v7
47+
# Install the PREBUILT binary, never `cargo install` (build-from-source): the repo pins
48+
# rustc 1.96, but a from-source cargo-audit build can need a newer toolchain than that to
49+
# COMPILE. The prebuilt binary runs fine under any toolchain -- it only parses Cargo.lock,
50+
# it never compiles this project.
51+
- uses: taiki-e/install-action@v2
52+
with:
53+
tool: cargo-audit
54+
- run: cargo audit
55+
56+
deny:
57+
name: Cargo Deny Check
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v7
61+
# Same rationale as the audit job: the prebuilt binary avoids needing a newer toolchain
62+
# than the repo's pinned 1.96 just to build the checker itself. Policy lives in `deny.toml`.
63+
- uses: taiki-e/install-action@v2
64+
with:
65+
tool: cargo-deny
66+
- run: cargo deny check

‎.gitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ flamegraph.svg
135135

136136
# --- Documentation ---
137137
/docs/_site/
138-
/docs/audit/
139138
/ref-proj/
140139

141140
# --- Temporary files ---

‎CHANGELOG.md‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,105 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Added
1313

14+
- **Mid-scanline/HDMA-driven register timing + hi-res color-math precision: researched — `v0.5.0`
15+
"Fidelity" work.** Confirmed a genuine, previously-undocumented off-by-one-line compositor bug
16+
against ares' per-pixel reference model (`ppu/main.cpp`'s active-pixel rendering runs strictly
17+
*before* HDMA's per-line service point): RustySNES's end-of-line compositor applies a line `V`
18+
HDMA-driven register write to line `V` itself, when real hardware only ever observes it starting
19+
line `V+1` (the mechanism behind Air Strike Patrol's BG3 raster scroll and any similar
20+
HDMA-driven per-line effect). Corrected two related overclaims this same investigation surfaced
21+
(`docs/ppu.md`'s "single-split-per-line effects work" claim, and both that doc's and
22+
`crates/rustysnes-ppu/src/lib.rs`'s claim that the per-scanline compositor is unconditionally
23+
"bit-identical" to a per-dot renderer). Not fixed this pass — the change touches the hottest
24+
code path in the engine (every frame, all 29 currently-passing goldens) with no dedicated test
25+
ROM yet to verify a fix against; full mechanism and what a fix needs documented in
26+
`docs/ppu.md` §Mid-scanline/HDMA-driven register timing. Separately confirmed hi-res
27+
color-math precision (Bishoujo Janshi Suchie-Pai / Marvelous+SA-1) is blocked entirely on
28+
512-wide hi-res output not existing yet (ares' `DAC::run()` shows hi-res is a dual-half-pixel
29+
alternating-compositor-result trick, not a numeric-precision tweak) — a real feature gap, not
30+
this pass's scope; full mechanism in `docs/ppu.md` §Hi-res color-math precision. No `.rs` code
31+
changed beyond doc comments; full workspace + `--features test-roms` suites verified unaffected.
32+
33+
- **The "DMA/HDMA-collision crash quirk": researched and reclassified — `v0.5.0` "Fidelity"
34+
work.** The SNESdev errata page's DMA section bundles three distinct behaviors under this
35+
vague label: a version-1-5A22-only crash and a version-2-5A22-only silent-DMA-failure bug
36+
(both chip-revision defects compliant commercial ROMs are written to avoid, not reproduced as
37+
a crash by any mainstream reference emulator), plus a version-agnostic silent whole-frame HDMA
38+
failure that's well-defined but has no known commercial title or committed test ROM depending
39+
on it — no oracle exists to verify an implementation against, and the sibling open-bus
40+
investigation (below) already demonstrated this exact class of change carries real regression
41+
risk even when the documented mechanism is correct. A fourth item on the same errata list
42+
(A-bus address restrictions) turned out to already be correctly implemented, as is the general
43+
"HDMA preempts GP-DMA" priority ordering — the well-defined half of what "collision" could have
44+
meant was never actually a gap. Full citation and per-sub-case reasoning in `docs/scheduler.md`.
45+
46+
- **`security.yml` CI gate — `v0.6.0` "Shippable" work, pulled forward.** A new dedicated
47+
workflow runs `cargo audit` and `cargo deny check` on every `main`/PR push touching non-doc
48+
paths, plus a weekly schedule so a newly-published advisory against an unchanged dependency is
49+
still caught. Added `deny.toml`, built from RustySNES's own `cargo deny list` output (not
50+
copied from RustyNES's config) — independently confirms the same winit/egui/wgpu dependency
51+
chain trips the identical 3 RUSTSEC advisories RustyNES already documented (`ttf-parser`
52+
unmaintained via winit's Wayland decoration stack; `quick-xml`'s two advisories, reachable only
53+
through `wayland-scanner`'s compile-time XML parsing of trusted vendored protocol files, never
54+
runtime input). Suppressed in `deny.toml` + the new `.cargo/audit.toml` with the full
55+
rationale, after explicit review and approval.
56+
57+
- **Checksummed release assets (SHA-256) — `v0.6.0` "Shippable" work, pulled forward.**
58+
`.github/workflows/release.yml` gained a `Checksum` step that emits a detached `<archive>.sha256`
59+
alongside each platform's packaged binary archive, portable across the three runner shells
60+
(tries `sha256sum`, falls back to `shasum -a 256`, since GNU coreutils' `sha256sum` is absent on
61+
macOS runners and Perl's `shasum` isn't guaranteed on Windows' Git-Bash `PATH`); the upload step
62+
now attaches both files. Not yet exercised end-to-end against a real tag.
63+
64+
- **`docs/benchmarks.md` + a real Criterion benchmark — `v0.6.0` "Shippable" work, pulled
65+
forward.** The first-ever measured performance number on this codebase:
66+
`crates/rustysnes-core/benches/headless_frame.rs` (Criterion 0.7) measures headless full-frame
67+
throughput against a real committed test ROM (`tests/roms/undisbeliever/inidisp_hammer_0f00.sfc`,
68+
chosen for no coprocessor/DMA-heavy content so the measurement isolates the base
69+
CPU+PPU+scheduler cost). Result: **3.27 ms/frame** steady state, against `docs/performance.md`'s
70+
≤~2ms target — real-time headroom is fine (~5.1× at NTSC's 16.64ms/frame budget), but the
71+
target itself isn't met yet. Documented honestly as a baseline to measure future optimization
72+
against, not a claim of having hit the target.
73+
74+
- **`docs/DOCUMENTATION_INDEX.md` — `v0.6.0` "Shippable" work, pulled forward.** The full
75+
documentation map (subsystem specs, ADRs, testing strategy, `ref-docs`/`ref-proj`/`to-dos`
76+
cross-references, external hardware-reference links), matching RustyNES's own index and linked
77+
from the README.
78+
79+
- **`$4203`/`$4206` multiply/divide overlap: researched and correctly reclassified — `v0.5.0`
80+
"Fidelity" work, in progress.** The 65816 hardware-gotcha list named this as an open item;
81+
research against SNESdev's own Errata page shows starting a new multiply/divide while a
82+
previous one's 8-cycle latency hasn't elapsed produces genuinely **undefined** `RDMPY`/`RDDIV`
83+
output — no canonical corrupted value is documented anywhere to port, and fabricating one would
84+
violate the determinism contract's spirit (`docs/adr/0004`). `MulDiv`'s doc comment now cites
85+
the errata directly and explains why this stays a documented non-goal rather than an open gap.
86+
Added a regression test locking in the well-defined case real hardware *does* document (MPYA
87+
is a stable latch; a fresh `$4203` write alone starts another multiply against whatever it
88+
already holds, no `$4202` rewrite needed).
89+
90+
- **ADR backfill: 3 new ADRs, `v0.5.0` "Fidelity" / `v0.6.0` "Shippable" work, in progress.**
91+
`docs/adr/0007` (the versioning/release-process adoption itself — the named `v0.x.0` ladder,
92+
the tag-body-is-the-release-note convention), `docs/adr/0008` (why the ExLoROM decode formula
93+
is sourced from bsnes's runtime board database rather than extrapolated from LoROM or the
94+
header-detection heuristic), and `docs/adr/0009` (ST018's title-match detection method, kept
95+
consistent with the rest of the `$F`-nibble coprocessor family rather than reading the
96+
`$xFBF` byte other customs are known-unreliable against; and the `Board::coprocessor_tick`
97+
catch-up architecture chosen over the SA-1 second-CPU hooks, since ST018's ARM core is
98+
self-contained in `rustysnes-cart` unlike SA-1's second 65C816). Also adds implementation
99+
guidance for the still-unstarted DRAM-refresh hardware-gotcha fix to `docs/scheduler.md`,
100+
surfacing a real architectural tension this project's CPU-driven master clock has with real
101+
hardware's independent video-timing generator that needs resolving empirically (against the
102+
full golden-framebuffer suite) before that fix lands, not assumed safe up front.
103+
104+
- **`docs/audit/` — `v0.6.0` "Shippable" work, pulled forward.** A new decision-rationale /
105+
open-investigation directory (modeled on RustyNES's own `docs/audit/`), seeded with the full
106+
SPC7110 boot-crash trail: the `v0.4.0`-landed `bus_mirror` addressing fix (confirmed root
107+
cause #1) and the still-open gap (root cause #2, narrowed to two candidate hypotheses, not
108+
yet fixed) that keeps Far East of Eden Zero from booting to real content. Also fixed two
109+
remaining "Sharp RTC-4513" naming errors (`docs/cart.md`, `coproc::sharprtc`'s module/struct
110+
docs) — the standalone Sharp S-RTC has no established "4513" part number anywhere; that number
111+
belongs only to the different Epson chip paired with SPC7110.
112+
14113
- **`docs/STATUS.md`: an accuracy dashboard — `v0.5.0` "Fidelity" work, in progress.** RustySNES
15114
has no single monolithic oracle ROM the way RustyNES's AccuracyCoin does (an early skeleton for
16115
exactly that approach, `rustysnes-test-harness::accuracy_battery`, ticket T-04, was never

0 commit comments

Comments
 (0)