Skip to content

chore(ci): Bump actions/upload-artifact from 4 to 6 - #4

Merged
doublegate merged 3 commits into
mainfrom
dependabot/github_actions/actions/upload-artifact-6
Dec 19, 2025
Merged

chore(ci): Bump actions/upload-artifact from 4 to 6#4
doublegate merged 3 commits into
mainfrom
dependabot/github_actions/actions/upload-artifact-6

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Dec 19, 2025

Copy link
Copy Markdown
Contributor

Bumps actions/upload-artifact from 4 to 6.

Release notes

Sourced from actions/upload-artifact's releases.

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: actions/upload-artifact@v5.0.0...v6.0.0

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

New Contributors

Full Changelog: actions/upload-artifact@v4...v5.0.0

v4.6.2

What's Changed

New Contributors

Full Changelog: actions/upload-artifact@v4...v4.6.2

v4.6.1

What's Changed

... (truncated)

Commits
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • 38d4c79 chore: rebuild dist
  • 7d27270 chore: add missing license cache files for @​actions/core, @​actions/io, and mi...
  • 5f643d3 chore: update license files for @​actions/artifact@​5.0.1 dependencies
  • 1df1684 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • b5b1a91 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot @github

dependabot Bot commented on behalf of github Dec 19, 2025

Copy link
Copy Markdown
Contributor Author

Labels

The following labels could not be found: dependencies, github-actions. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

@dependabot
dependabot Bot requested a review from doublegate as a code owner December 19, 2025 02:18
@doublegate
doublegate requested review from Copilot and removed request for doublegate December 19, 2025 05:11

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 updates the actions/upload-artifact action from version 4 to version 6 in the CI workflow, bringing support for Node.js 24 and updated dependencies.

  • Updates the upload-artifact action to leverage Node.js 24 runtime
  • Maintains compatibility with existing artifact upload configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@doublegate doublegate left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed / Approved - GitHub Version Bump(s) -- DG 12/19

@doublegate
doublegate merged commit 496eaa4 into main Dec 19, 2025
11 checks passed
@doublegate
doublegate deleted the dependabot/github_actions/actions/upload-artifact-6 branch December 19, 2025 06:08
doublegate added a commit that referenced this pull request Jun 13, 2026
…+ harvest (A-H)

Frontend-only (zero changes to any core crate; AccuracyCoin 100%/139 and the
60/60 + 52/52 commercial oracles stay byte-identical). The three requested
features plus five low-risk quality-of-life items harvested from the reference
emulators (tetanes/Mesen2/fceux survey).

A. Master volume — [audio] volume + muted; gain applied once per cpal callback
   at the lock-free ring consume point (QueueInner gain atomic); Audio-tab
   slider + mute, live-applied.
B. Emulation-speed presets (25/50/75/100/150/200/300%) — EmuCore.speed scales
   effective_frame_duration at every pacer site (1.0 = bit-identical); forces
   wall-clock pacing at non-100% (display-sync can't do fractional rates);
   pitch-shifted glitch-free audio via a resampler base_ratio re-centering the
   DRC band on the speed. Emulation->Speed submenu + status readout; keys
   =/-/0 (SpeedUp/Down/Reset, verified unbound).
C. Save-state thumbnails — native Save-States manager (File->Save States...)
   surfacing the existing core THM thumbnail via save_state::slot_meta +
   Nes::extract_thumbnail; per-slot egui texture cache, invalidated on save and
   dropped on ROM change.
D. Controller hot-plug toast (gilrs Connected/Disconnected).
E. Deadzone slider in the Input settings tab (exposes the existing config).
F. Overscan crop toggle ([graphics] hide_overscan; blit-UV crop of the top/
   bottom 8 scanlines, no new pass) — View + Video tab.
G. Pause-screen dimming overlay (40% dim + centered PAUSED, over the viewport
   only).
H. Reset-to-Defaults per Settings section (two-click confirm, live re-apply).

Gates: fmt, workspace clippy, wasm-winit/wasm-canvas/no-default/RA clippy,
rustdoc, frontend tests (149), and the determinism re-verify (AccuracyCoin
139/139 + oracles 60/60 + 52/52) all green. New file save_states_ui.rs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jun 13, 2026
…h (I-K)

The remaining three ref-proj survey items, completing the full set.

I. Per-APU-channel mute toggles — the one core-touching item, done
   determinism-safe. rustynes-apu gains a `channel_mask: u8` (default
   CHANNEL_MASK_ALL = 0x3F) gated at the single mix site: with the default
   mask every channel passes through unchanged, so the mixer output is
   byte-identical (the oracle/tests never clear a bit) — NOT serialized into
   the save-state. Nes::set_apu_channel_mask/apu_channel_mask passthrough. Six
   checkboxes (Pulse1/Pulse2/Triangle/Noise/DMC/Mapper Audio) in the Settings
   Audio tab, pushed to the core under the emu lock, re-applied on ROM load +
   power-cycle, persisted to [audio]. +4 APU unit tests prove default == unmasked.
J. Screenshot-to-clipboard (native-only) — `arboard` behind the
   cfg(not(wasm32)) target table (never enters wasm); File -> "Copy Screenshot
   to Clipboard" reuses the take_screenshot grab, toasts on success/failure,
   never panics.
K. Live FPS/frame-time graph — a hand-rolled ui.painter() sparkline in the
   Performance panel (presented over produced, a 16.64 ms NTSC deadline line,
   auto-scaled) — no egui_plot dependency.

MSRV guard: `.cargo/config.toml` gains `[resolver] incompatible-rust-versions
= "fallback"` so a future `cargo update` can't pull `image` (arboard's
clipboard image dep) past the 1.86 MSRV; the committed Cargo.lock already holds
MSRV-safe pins (arboard 3.6.1, image 0.25.5) and is unchanged by the guard.

Gates: fmt, workspace clippy, wasm-winit/wasm-canvas/no-default/RA clippy,
rustdoc, frontend tests (150), and the determinism re-verify — AccuracyCoin
100% (139/139), oracles 60/60 + 52/52 byte-identical (independently re-run after
the core APU change), APU crate 127 tests. ppu/cpu/mappers untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jun 13, 2026
…harvest)

README + CHANGELOG [1.0.0] + the user-guide (controls/configuration/menus/
display-and-audio/troubleshooting) now reflect the shipped #4 batch: master
volume + mute, per-APU-channel mutes (channel_mask bitmask), emulation-speed
presets (25-300%, keys =/-/0), the thumbnail Save-States manager, overscan
crop, pause-dim, gamepad deadzone slider, controller hot-plug toasts,
screenshot-to-clipboard, per-section Reset-to-Defaults, and the Performance-
panel frame-time sparkline. Settings tabs labelled Display/Audio/Input/Advanced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jun 13, 2026
Records the bug-fix rounds 1-3 and the #4 feature batch (all user-smoke-tested)
+ the .cargo MSRV resolver guard, replacing the "compile-verified only" caveat
the shell originally shipped with.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate

Copy link
Copy Markdown
Owner

Adjudicated (post-hoc closeout): Copilot's review is an overview of a routine Dependabot bump — actions/upload-artifact v4→v6 (Node.js 24), CI workflow YAML only, no source / runtime / AccuracyCoin impact. Adopted + merged; no actionable review items outstanding.

doublegate added a commit that referenced this pull request Jul 11, 2026
…0 pal_apu_tests

Implement the PAL (2A07) APU frame-counter sequencer step positions, closing
five of the seven PAL timing residuals the salvaged screen-reading oracle
pinned. The blargg `pal_apu_tests` corpus now scores 8/10 under forced PAL
region (was 3/10), with the two remaining failures documented as a bounded,
fail-loud residual.

Mechanism
---------
The 2A03 (NTSC) and 2A07 (PAL) share the same six-step sequencer but divide the
CPU clock differently, so the identical quarter/half/IRQ events land at
different CPU-cycle counts. `FrameCounter` now carries a `pal: bool` selector,
derived from the console `Region` by `Apu::new` (true only for `Region::Pal`;
NTSC and Dendy — a PAL-clocked famiclone with an NTSC-period frame counter —
keep the NTSC positions). The sequencer step tables are:

- PAL 4-step (mode 0): 8313 / 16627 / 24939 / 33252 / 33253 / 33254
  (quarter 8313/16627/24939/33253; half 16627/33253; IRQ 33252/33253/33254).
- PAL 5-step (mode 1): 8313 / 16627 / 24939 / 41565 / 41566
  (quarter 8313/16627/24939/41565; half 16627/41565; no IRQ).

These are the canonical Mesen2 `stepCyclesPal` values. The mode-0 terminal
three cycles replicate the NTSC IRQ-flag-visibility / `irq_line_active` split
verbatim at the PAL positions, preserving the AccuracyCoin Frame-Counter-IRQ
Tests I/J/K/L/M semantics for PAL.

NTSC byte-identity (sacred constraint)
--------------------------------------
The change is strictly region-gated: the `pal == false` branch runs the
original NTSC step tables unchanged, and the power-on / snapshot-restore
default is NTSC. The `tick` hot path was split into `clock_sequencer` +
`four_step` / `five_step` helpers (to stay within the clippy line budget) with
no behavioral change to the NTSC arms. Verified byte-identical:

- AccuracyCoin 141/141 (100.00%) — unchanged.
- `apu_test` 8/8, NTSC `blargg_apu_2005` 11/11, `nestest` 0-diff — unchanged.
- APU snapshot format is untouched: `pal` is derived, not persisted;
  `Apu::restore` re-derives it from the restored region.

Residual (honest)
-----------------
`10.len_halt_timing` and `11.len_reload_timing` still fail, but the PAL step
positions advanced them from `FAILED: #2` to `FAILED: #3` / `#4`. Their NTSC
builds pass (`blargg_apu_2005` 10 & 11), localizing the gap to a PAL-specific
length-counter halt/reload write-vs-half-frame-clock ordering detail adjacent
to the frame-counter step model. Left as fail-loud residual pins and recorded
in docs/accuracy-ledger.md; closing it is a separate deeper investigation.

Tests + docs
------------
Adds seven PAL frame-counter unit tests (step positions, IRQ position,
no-IRQ-at-NTSC-position, wrap, 5-step, NTSC-default). Promotes pal_apu 04-08
from residual pins to strict passes; keeps 10/11 as documented residuals.
Updates docs/apu-2a03.md, docs/accuracy-ledger.md, docs/STATUS.md,
docs/testing-strategy.md to the 8/10 state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 11, 2026
…0 pal_apu_tests

Implement the PAL (2A07) APU frame-counter sequencer step positions, closing
five of the seven PAL timing residuals the salvaged screen-reading oracle
pinned. The blargg `pal_apu_tests` corpus now scores 8/10 under forced PAL
region (was 3/10), with the two remaining failures documented as a bounded,
fail-loud residual.

Mechanism
---------
The 2A03 (NTSC) and 2A07 (PAL) share the same six-step sequencer but divide the
CPU clock differently, so the identical quarter/half/IRQ events land at
different CPU-cycle counts. `FrameCounter` now carries a `pal: bool` selector,
derived from the console `Region` by `Apu::new` (true only for `Region::Pal`;
NTSC and Dendy — a PAL-clocked famiclone with an NTSC-period frame counter —
keep the NTSC positions). The sequencer step tables are:

- PAL 4-step (mode 0): 8313 / 16627 / 24939 / 33252 / 33253 / 33254
  (quarter 8313/16627/24939/33253; half 16627/33253; IRQ 33252/33253/33254).
- PAL 5-step (mode 1): 8313 / 16627 / 24939 / 41565 / 41566
  (quarter 8313/16627/24939/41565; half 16627/41565; no IRQ).

These are the canonical Mesen2 `stepCyclesPal` values. The mode-0 terminal
three cycles replicate the NTSC IRQ-flag-visibility / `irq_line_active` split
verbatim at the PAL positions, preserving the AccuracyCoin Frame-Counter-IRQ
Tests I/J/K/L/M semantics for PAL.

NTSC byte-identity (sacred constraint)
--------------------------------------
The change is strictly region-gated: the `pal == false` branch runs the
original NTSC step tables unchanged, and the power-on / snapshot-restore
default is NTSC. The `tick` hot path was split into `clock_sequencer` +
`four_step` / `five_step` helpers (to stay within the clippy line budget) with
no behavioral change to the NTSC arms. Verified byte-identical:

- AccuracyCoin 141/141 (100.00%) — unchanged.
- `apu_test` 8/8, NTSC `blargg_apu_2005` 11/11, `nestest` 0-diff — unchanged.
- APU snapshot format is untouched: `pal` is derived, not persisted;
  `Apu::restore` re-derives it from the restored region.

Residual (honest)
-----------------
`10.len_halt_timing` and `11.len_reload_timing` still fail, but the PAL step
positions advanced them from `FAILED: #2` to `FAILED: #3` / `#4`. Their NTSC
builds pass (`blargg_apu_2005` 10 & 11), localizing the gap to a PAL-specific
length-counter halt/reload write-vs-half-frame-clock ordering detail adjacent
to the frame-counter step model. Left as fail-loud residual pins and recorded
in docs/accuracy-ledger.md; closing it is a separate deeper investigation.

Tests + docs
------------
Adds seven PAL frame-counter unit tests (step positions, IRQ position,
no-IRQ-at-NTSC-position, wrap, 5-step, NTSC-default). Promotes pal_apu 04-08
from residual pins to strict passes; keeps 10/11 as documented residuals.
Updates docs/apu-2a03.md, docs/accuracy-ledger.md, docs/STATUS.md,
docs/testing-strategy.md to the 8/10 state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 11, 2026
…pal_apu_tests)

Close the final two PAL APU accuracy residuals — blargg `pal_apu_tests`
`10.len_halt_timing` and `11.len_reload_timing` — by modelling the 2A03's
one-step deferral of length-counter halt changes and length reloads relative
to the frame sequencer's half-frame length clock. Extends PR #275 from 8/10 to
10/10 pal_apu_tests, with NTSC held byte-identical.

Root cause
----------
The length counter applied a `$4000`-bit-5 halt change and a `$4003` length
reload IMMEDIATELY, during the CPU's bus write. Because the CPU's bus access
runs before the APU's per-cycle `tick_with_external` (which fires the
half-frame length clock), a halt/reload write that landed on the exact cycle
of a half-frame clock was applied BEFORE that clock — the inverse of silicon,
which applies both AFTER. This surfaced only on PAL because the NTSC
`blargg_apu_2005` 10/11 ROMs report via a `$6000` protocol they have no
PRG-RAM for (a vacuous status==0), whereas the PAL suite decodes the on-screen
verdict. PAL reported `FAILED #3` (len_halt: "should be clocked when halted at
16629" — the write-on-clock-cycle case) and `FAILED #4` (len_reload: "reload
during clock when ctr = 0 should work").

Mechanism (mirrors TetaNES LengthCounter::reload + Mesen2 _newHaltValue)
-----------------------------------------------------------------------
`LengthCounter` gains three deferral fields:
  * new_halt       — latched by `set_halt` on a $4000/$4004/$4008/$400C write;
                     promoted to the effective `halt` in `reload`.
  * reload_val     — pending length load latched by `load` (deferred).
  * previous_count — snapshot of `count` at load time.
`reload()` applies a pending reload only if the post-clock `count` still equals
`previous_count` (a same-cycle half-frame clock did not decrement it because it
was already zero), then refreshes `halt = new_halt`. The APU calls `reload()`
on all four length channels once per CPU cycle in `tick_with_external`, AFTER
`handle_frame_events` (the half-frame clock) and BEFORE the mixer samples
channel outputs.

NTSC byte-identity (sacred)
---------------------------
On the common non-coincident write cycle the count is untouched between the
write and `reload`, so `count == previous_count` holds and the reload settles
in-cycle before the sample — identical to an immediate load; halt does not
affect `output()` directly, so its deferral is invisible except at a clock.
Only the write-lands-on-the-clock-cycle coincidence the ROMs probe differs.
Verified byte-identical: AccuracyCoin 141/141, blargg_apu_2005 11/11,
f2_accuracy_audit 6/6, apu_reset 4/4, apu_mixer / apu_test / volume_tests /
nestest / visual_regression all unchanged against their pre-change goldens.

The snapshot byte layout is unchanged: the deferral scratch fields are not
serialized (they resolve within the write cycle), and `read_length` seeds
`new_halt = halt` so a restore never spuriously clears a halted counter.

pal_apu_tests 10 & 11 promoted from the fail-loud `pal_apu_residual!` pins to
`pal_apu_pass!`; the now-unused macro is removed. Docs synced:
docs/apu-2a03.md (new §Length halt/reload ordering), docs/accuracy-ledger.md
(residual closed), docs/STATUS.md, docs/testing-strategy.md, CHANGELOG.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 12, 2026
…0 pal_apu_tests

Implement the PAL (2A07) APU frame-counter sequencer step positions, closing
five of the seven PAL timing residuals the salvaged screen-reading oracle
pinned. The blargg `pal_apu_tests` corpus now scores 8/10 under forced PAL
region (was 3/10), with the two remaining failures documented as a bounded,
fail-loud residual.

Mechanism
---------
The 2A03 (NTSC) and 2A07 (PAL) share the same six-step sequencer but divide the
CPU clock differently, so the identical quarter/half/IRQ events land at
different CPU-cycle counts. `FrameCounter` now carries a `pal: bool` selector,
derived from the console `Region` by `Apu::new` (true only for `Region::Pal`;
NTSC and Dendy — a PAL-clocked famiclone with an NTSC-period frame counter —
keep the NTSC positions). The sequencer step tables are:

- PAL 4-step (mode 0): 8313 / 16627 / 24939 / 33252 / 33253 / 33254
  (quarter 8313/16627/24939/33253; half 16627/33253; IRQ 33252/33253/33254).
- PAL 5-step (mode 1): 8313 / 16627 / 24939 / 41565 / 41566
  (quarter 8313/16627/24939/41565; half 16627/41565; no IRQ).

These are the canonical Mesen2 `stepCyclesPal` values. The mode-0 terminal
three cycles replicate the NTSC IRQ-flag-visibility / `irq_line_active` split
verbatim at the PAL positions, preserving the AccuracyCoin Frame-Counter-IRQ
Tests I/J/K/L/M semantics for PAL.

NTSC byte-identity (sacred constraint)
--------------------------------------
The change is strictly region-gated: the `pal == false` branch runs the
original NTSC step tables unchanged, and the power-on / snapshot-restore
default is NTSC. The `tick` hot path was split into `clock_sequencer` +
`four_step` / `five_step` helpers (to stay within the clippy line budget) with
no behavioral change to the NTSC arms. Verified byte-identical:

- AccuracyCoin 141/141 (100.00%) — unchanged.
- `apu_test` 8/8, NTSC `blargg_apu_2005` 11/11, `nestest` 0-diff — unchanged.
- APU snapshot format is untouched: `pal` is derived, not persisted;
  `Apu::restore` re-derives it from the restored region.

Residual (honest)
-----------------
`10.len_halt_timing` and `11.len_reload_timing` still fail, but the PAL step
positions advanced them from `FAILED: #2` to `FAILED: #3` / `#4`. Their NTSC
builds pass (`blargg_apu_2005` 10 & 11), localizing the gap to a PAL-specific
length-counter halt/reload write-vs-half-frame-clock ordering detail adjacent
to the frame-counter step model. Left as fail-loud residual pins and recorded
in docs/accuracy-ledger.md; closing it is a separate deeper investigation.

Tests + docs
------------
Adds seven PAL frame-counter unit tests (step positions, IRQ position,
no-IRQ-at-NTSC-position, wrap, 5-step, NTSC-default). Promotes pal_apu 04-08
from residual pins to strict passes; keeps 10/11 as documented residuals.
Updates docs/apu-2a03.md, docs/accuracy-ledger.md, docs/STATUS.md,
docs/testing-strategy.md to the 8/10 state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 12, 2026
…pal_apu_tests)

Close the final two PAL APU accuracy residuals — blargg `pal_apu_tests`
`10.len_halt_timing` and `11.len_reload_timing` — by modelling the 2A03's
one-step deferral of length-counter halt changes and length reloads relative
to the frame sequencer's half-frame length clock. Extends PR #275 from 8/10 to
10/10 pal_apu_tests, with NTSC held byte-identical.

Root cause
----------
The length counter applied a `$4000`-bit-5 halt change and a `$4003` length
reload IMMEDIATELY, during the CPU's bus write. Because the CPU's bus access
runs before the APU's per-cycle `tick_with_external` (which fires the
half-frame length clock), a halt/reload write that landed on the exact cycle
of a half-frame clock was applied BEFORE that clock — the inverse of silicon,
which applies both AFTER. This surfaced only on PAL because the NTSC
`blargg_apu_2005` 10/11 ROMs report via a `$6000` protocol they have no
PRG-RAM for (a vacuous status==0), whereas the PAL suite decodes the on-screen
verdict. PAL reported `FAILED #3` (len_halt: "should be clocked when halted at
16629" — the write-on-clock-cycle case) and `FAILED #4` (len_reload: "reload
during clock when ctr = 0 should work").

Mechanism (mirrors TetaNES LengthCounter::reload + Mesen2 _newHaltValue)
-----------------------------------------------------------------------
`LengthCounter` gains three deferral fields:
  * new_halt       — latched by `set_halt` on a $4000/$4004/$4008/$400C write;
                     promoted to the effective `halt` in `reload`.
  * reload_val     — pending length load latched by `load` (deferred).
  * previous_count — snapshot of `count` at load time.
`reload()` applies a pending reload only if the post-clock `count` still equals
`previous_count` (a same-cycle half-frame clock did not decrement it because it
was already zero), then refreshes `halt = new_halt`. The APU calls `reload()`
on all four length channels once per CPU cycle in `tick_with_external`, AFTER
`handle_frame_events` (the half-frame clock) and BEFORE the mixer samples
channel outputs.

NTSC byte-identity (sacred)
---------------------------
On the common non-coincident write cycle the count is untouched between the
write and `reload`, so `count == previous_count` holds and the reload settles
in-cycle before the sample — identical to an immediate load; halt does not
affect `output()` directly, so its deferral is invisible except at a clock.
Only the write-lands-on-the-clock-cycle coincidence the ROMs probe differs.
Verified byte-identical: AccuracyCoin 141/141, blargg_apu_2005 11/11,
f2_accuracy_audit 6/6, apu_reset 4/4, apu_mixer / apu_test / volume_tests /
nestest / visual_regression all unchanged against their pre-change goldens.

The snapshot byte layout is unchanged: the deferral scratch fields are not
serialized (they resolve within the write cycle), and `read_length` seeds
`new_halt = halt` so a restore never spuriously clears a halted counter.

pal_apu_tests 10 & 11 promoted from the fail-loud `pal_apu_residual!` pins to
`pal_apu_pass!`; the now-unused macro is removed. Docs synced:
docs/apu-2a03.md (new §Length halt/reload ordering), docs/accuracy-ledger.md
(residual closed), docs/STATUS.md, docs/testing-strategy.md, CHANGELOG.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 12, 2026
…oracle (8/10, v2.1.5) (#275)

* wip(pal-apu): salvage orphaned-agent PAL APU oracle work

WIP snapshot of the v2.1.5 PAL APU oracle workstream, committed to protect the
work after the spawning background agent was orphaned on a session exit. Not yet
gate-verified; to be assessed and completed before PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(apu): model PAL frame-counter step positions (region-gated); 8/10 pal_apu_tests

Implement the PAL (2A07) APU frame-counter sequencer step positions, closing
five of the seven PAL timing residuals the salvaged screen-reading oracle
pinned. The blargg `pal_apu_tests` corpus now scores 8/10 under forced PAL
region (was 3/10), with the two remaining failures documented as a bounded,
fail-loud residual.

Mechanism
---------
The 2A03 (NTSC) and 2A07 (PAL) share the same six-step sequencer but divide the
CPU clock differently, so the identical quarter/half/IRQ events land at
different CPU-cycle counts. `FrameCounter` now carries a `pal: bool` selector,
derived from the console `Region` by `Apu::new` (true only for `Region::Pal`;
NTSC and Dendy — a PAL-clocked famiclone with an NTSC-period frame counter —
keep the NTSC positions). The sequencer step tables are:

- PAL 4-step (mode 0): 8313 / 16627 / 24939 / 33252 / 33253 / 33254
  (quarter 8313/16627/24939/33253; half 16627/33253; IRQ 33252/33253/33254).
- PAL 5-step (mode 1): 8313 / 16627 / 24939 / 41565 / 41566
  (quarter 8313/16627/24939/41565; half 16627/41565; no IRQ).

These are the canonical Mesen2 `stepCyclesPal` values. The mode-0 terminal
three cycles replicate the NTSC IRQ-flag-visibility / `irq_line_active` split
verbatim at the PAL positions, preserving the AccuracyCoin Frame-Counter-IRQ
Tests I/J/K/L/M semantics for PAL.

NTSC byte-identity (sacred constraint)
--------------------------------------
The change is strictly region-gated: the `pal == false` branch runs the
original NTSC step tables unchanged, and the power-on / snapshot-restore
default is NTSC. The `tick` hot path was split into `clock_sequencer` +
`four_step` / `five_step` helpers (to stay within the clippy line budget) with
no behavioral change to the NTSC arms. Verified byte-identical:

- AccuracyCoin 141/141 (100.00%) — unchanged.
- `apu_test` 8/8, NTSC `blargg_apu_2005` 11/11, `nestest` 0-diff — unchanged.
- APU snapshot format is untouched: `pal` is derived, not persisted;
  `Apu::restore` re-derives it from the restored region.

Residual (honest)
-----------------
`10.len_halt_timing` and `11.len_reload_timing` still fail, but the PAL step
positions advanced them from `FAILED: #2` to `FAILED: #3` / `#4`. Their NTSC
builds pass (`blargg_apu_2005` 10 & 11), localizing the gap to a PAL-specific
length-counter halt/reload write-vs-half-frame-clock ordering detail adjacent
to the frame-counter step model. Left as fail-loud residual pins and recorded
in docs/accuracy-ledger.md; closing it is a separate deeper investigation.

Tests + docs
------------
Adds seven PAL frame-counter unit tests (step positions, IRQ position,
no-IRQ-at-NTSC-position, wrap, 5-step, NTSC-default). Promotes pal_apu 04-08
from residual pins to strict passes; keeps 10/11 as documented residuals.
Updates docs/apu-2a03.md, docs/accuracy-ledger.md, docs/STATUS.md,
docs/testing-strategy.md to the 8/10 state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(changelog): rewrite the v2.1.5 PAL APU entry to the 8/10 implemented state

The salvaged WIP entry described the pre-implementation state (screen-reading
oracle wired, 3/10 honest pass, PAL frame counter unmodeled). Rewrite it to
record the delivered work: the region-gated PAL (2A07) frame-counter step
positions, the resulting 8/10 pass count (04-08 flipped to PASS), the preserved
NTSC byte-identity (AccuracyCoin 141/141, apu_test 8/8, blargg_apu_2005 11/11,
nestest 0-diff), and the two documented length halt/reload timing residuals
(10/11) pinned fail-loud.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(apu): defer length halt/reload ordering; close PAL 10/11 (10/10 pal_apu_tests)

Close the final two PAL APU accuracy residuals — blargg `pal_apu_tests`
`10.len_halt_timing` and `11.len_reload_timing` — by modelling the 2A03's
one-step deferral of length-counter halt changes and length reloads relative
to the frame sequencer's half-frame length clock. Extends PR #275 from 8/10 to
10/10 pal_apu_tests, with NTSC held byte-identical.

Root cause
----------
The length counter applied a `$4000`-bit-5 halt change and a `$4003` length
reload IMMEDIATELY, during the CPU's bus write. Because the CPU's bus access
runs before the APU's per-cycle `tick_with_external` (which fires the
half-frame length clock), a halt/reload write that landed on the exact cycle
of a half-frame clock was applied BEFORE that clock — the inverse of silicon,
which applies both AFTER. This surfaced only on PAL because the NTSC
`blargg_apu_2005` 10/11 ROMs report via a `$6000` protocol they have no
PRG-RAM for (a vacuous status==0), whereas the PAL suite decodes the on-screen
verdict. PAL reported `FAILED #3` (len_halt: "should be clocked when halted at
16629" — the write-on-clock-cycle case) and `FAILED #4` (len_reload: "reload
during clock when ctr = 0 should work").

Mechanism (mirrors TetaNES LengthCounter::reload + Mesen2 _newHaltValue)
-----------------------------------------------------------------------
`LengthCounter` gains three deferral fields:
  * new_halt       — latched by `set_halt` on a $4000/$4004/$4008/$400C write;
                     promoted to the effective `halt` in `reload`.
  * reload_val     — pending length load latched by `load` (deferred).
  * previous_count — snapshot of `count` at load time.
`reload()` applies a pending reload only if the post-clock `count` still equals
`previous_count` (a same-cycle half-frame clock did not decrement it because it
was already zero), then refreshes `halt = new_halt`. The APU calls `reload()`
on all four length channels once per CPU cycle in `tick_with_external`, AFTER
`handle_frame_events` (the half-frame clock) and BEFORE the mixer samples
channel outputs.

NTSC byte-identity (sacred)
---------------------------
On the common non-coincident write cycle the count is untouched between the
write and `reload`, so `count == previous_count` holds and the reload settles
in-cycle before the sample — identical to an immediate load; halt does not
affect `output()` directly, so its deferral is invisible except at a clock.
Only the write-lands-on-the-clock-cycle coincidence the ROMs probe differs.
Verified byte-identical: AccuracyCoin 141/141, blargg_apu_2005 11/11,
f2_accuracy_audit 6/6, apu_reset 4/4, apu_mixer / apu_test / volume_tests /
nestest / visual_regression all unchanged against their pre-change goldens.

The snapshot byte layout is unchanged: the deferral scratch fields are not
serialized (they resolve within the write cycle), and `read_length` seeds
`new_halt = halt` so a restore never spuriously clears a halted counter.

pal_apu_tests 10 & 11 promoted from the fail-loud `pal_apu_residual!` pins to
`pal_apu_pass!`; the now-unused macro is removed. Docs synced:
docs/apu-2a03.md (new §Length halt/reload ordering), docs/accuracy-ledger.md
(residual closed), docs/STATUS.md, docs/testing-strategy.md, CHANGELOG.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(test-harness): neutral pal_apu_pass message + reuse screen decode buffer

Address two PR #275 bot-review threads (both behavior-preserving):

- **Copilot (pal_apu_tests.rs):** the `pal_apu_pass!` macro's failure message
  called every ROM a "region-independent APU check", but the macro also covers
  the PAL frame-counter-timing checks (04-08) and the length halt/reload
  checks (10-11), for which that claim is false. Reworded to a neutral
  "PAL APU {name}: expected on-screen PASSED but ROM reported {verdict}" and
  updated the macro doc to enumerate all three categories it now covers.

- **Gemini (nes_runner.rs `decode_screen_text`):** the decoder allocated a
  fresh `String` per frame and a per-row intermediate `String` (~60 allocs per
  frame, ~100k+ per multi-hundred-frame run). Refactored to
  `decode_screen_text_into(nes, &mut String)`: `run_nes_screen` now decodes
  every frame into ONE reused buffer (`clear`ed each frame, moved into the
  result at the end). Trailing-space trimming and blank-row dropping are done
  in place by tracking each row's start offset + last non-space byte and
  `truncate`-ing back to it (every glyph is ASCII `0x20..=0x7e`, so one byte
  per char and the offsets are always on a char boundary) — no per-row scratch
  String. Decoded text is byte-for-byte identical; verified pal_apu_tests still
  10/10 with unchanged on-screen verdicts and frame counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

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