RustyNES v2.6.23 — "Pulse" (the access does not increment, it pulses the load already there)
LatestAn expected failure that had stood RED for eight releases closes, and the cause was one sentence about what a $2007 access actually does. v2.7.0 "Shakedown" is still the session with the board — this is not it — but the bitstream this release cuts is the one intended for that bring-up, and it is the first .rbf to carry the fix below.
The emulation core is unchanged, so AccuracyCoin 144/144 and nestest 0-diff hold by construction, and were re-run anyway: both green. The sibling's co-simulation ladder went from 153 passed, 1 failed, 2 expected failures to 155 passed, 0 failed, 1 expected failure.
The CHR-during-rendering divergence closes, and the cause was v itself
RustyNES_MiSTer's chrram-live gate — the one that renders CHR-RAM written while rendering is enabled — had been RED for eight releases at 32,861 of 61,440 differing pixels and now reports "All 61440 pixels match". Its read-side sibling chrram-fetch went 21,941 → 18 diverging fetches. chr_wr asserts 4,389 times before and after, so it is the same stimulus that used to fail; nothing about the test changed, only what the RTL did with it.
Eleven prior variants had been measured correctly and recorded in rtl/ppu2c02.sv, and the conclusion drawn from them was wrong. They swept what the $2007 arm computes — increment composition, collision policy, write phase, write address, gate condition — and concluded "the v divergence is not in this logic," which closed the search and sent the next session away from the answer. Every one of those eleven measurements still stands. None of them swept what the arm computes it from, and that was the axis. A twelfth variant on any of the five axes could not have found it — the sweep was complete over the wrong space, which is the more expensive failure because it terminates with a confident negative.
On silicon a $2007 access during rendering does not perform an increment of its own: it pulses the rendering pipeline's existing load (NESdev's Visual 2C02 material — "the carry inputs to various sections of the address being set up for rendering, with the $2007 access triggering a 'load next value' signal"). This core instead computed a private inc_y(inc_x(v)) from the raw register, and because that assignment was textually last in its always_ff, it silently erased whatever the rendering pipeline had assigned on the same dot.
Three changes, each exposing the next:
v_pipeline— the value. One combinational block now holds every rendering-time load (coarse-X every eight dots, the dot-256 Y step,hori(v)=hori(t)at dot 257,vert(v)=vert(t)over dots 280-304 of the pre-render line), committed once, and the two$2007arms increment it rather than rawv. 21,941 → 2,646 diverging fetches.v_acc_addr— the address. The access also resolves against that same value. This is what stopped 33 nametable cells being written one coarse-X cell early. → 18 fetches.chr_wr_addrsourced fromv_acc_addrtoo. → 0 pixels.
How it was found is worth recording on its own: not by a twelfth variant, but by instrumenting the disputed quantity directly. A nametable fetch address is {2'b10, v[11:0]}, so the gate's own fetch records already contained v; the oracle's --ppu-state-trace gives its v per dot. Two traces that already existed, one comparison, and the answer was in the first diverging record — frame 3, scanline 261, dot 260:
dot 258 both agree cX=12 cY=2 NTx=1
dot 260 oracle cX= 1 cY=3 NTx=0 inc_y, then hori(v)=hori(t)
this core cX=13 cY=3 NTx=1 inc_y(inc_x(v)), copy DROPPED
No new instrumentation was needed, and none was written.
chr_wr_addr following the oracle is a maintainer decision, taken knowingly. The NESdev wiki declares that write address undefined, so this adopts the oracle's model for a behaviour no document settles — precisely the case ADR 0037 exists to govern, and the objection is kept in the RTL comment marked resolved-by-decision, because it governs the next case shaped like this one. The cost was accepted with it: a Quartus re-sweep, a re-cut bitstream, a re-run of every rung — all of which this release also carries out.
The 18 fetches that remain diverging (chrram-fetch stays run_xfail, graded both ways) are all garbage nametable fetches in the sprite window — dots 257-320, 18 of 18, all +1 coarse X, all frame 4 — whose data is discarded, which is why the picture is byte-perfect while the trace is not. Zero pattern-table divergences remain.
Golden reproducibility extends from 4 stems to 97 of 110
chrram-live becoming a plain run gate raised the stakes on a standing gap: a clone that could not regenerate ppuchrram054.* could not run the gate that proves this release's own headline fix. tb/golden-recipes.tsv now carries a recipe for 97 of the 110 committed golden manifests, generated from the manifests themselves by tb/gen_golden_recipes.py rather than transcribed — 110 hand-written caps is 110 silent chances to under-cover a golden, and a short trace just compares less than it claims to.
VERIFY=1 tb/fetch-goldens.sh regenerates into scratch and diffs against the committed goldens without writing to them. The result: 472 artifacts reproduce byte-for-byte against goldens whose recorded emulator spans rustynes 2.5.1 through 2.6.22 — fifteen releases. Framebuffers, fetch streams, RAM images, checkpoint streams and bus traces all survive that gap. That answers the question this project had been carrying open since the mixed-vintage finding: nothing needs re-cutting.
Every apparent difference the diff run surfaced across four passes turned out to be a defect in the harness, never in the oracle — a .irq.csv diagnostic mistaken for a missing artifact, a guessed --apu-trace cap the exporter correctly refused rather than silently under-cover, eleven blargg stems whose renamed ROMs were staged somewhere the battery never reads, and an AccuracyCoin run that sat on its title screen for 4,500 frames because --press-start went unread from the manifest — caught not by inspection but by accuracycoin_status refusing to score a vacuous run: "every one of the 144 SCORED entries is NotRun … a comparison against this proves nothing." With the flag restored, all three AccuracyCoin artifacts reproduce identically.
The ROM for each recipe is located by content (rom_sha256), never by the manifest's own rom field — across this corpus that field is relative, ../../../-relative, absolute under one developer's $HOME, and for the blargg stems points into /tmp/blargg-apu/, a directory the operating system is entitled to delete. Resolving by hash recovers the renames nothing else records: blargg01 is tests/roms/assorted/apu_01_len_ctr.nes.
Two corpus properties surfaced by the same diff run and deliberately not acted on: four apu.bin artifacts whose manifests disagree with the artifact's own record count are pure window-length mismatches — the shorter file is a byte-exact prefix of the longer at ratios of exactly 2.0000 and 2.5000, so the goldens are correct and only their manifests understate the run. And four ckpt.bin streams are recorded in their manifests (cpu-open-bus: ckpt_count=873) but were never committed, unnoticed for releases because no gate reads them.
The fitter re-swept on the new RTL, and the pin moved with a stated criterion change
rtl/ppu2c02.sv changed, so by RustyNES.qsf's own rule every previous seed-sweep table describes a design that no longer exists. Re-swept: five seeds, worst slack across all four corners, each from a clean database, same build date.
| seed | worst setup | worst hold | result |
|---|---|---|---|
| 1 | +0.247 ns | +0.115 ns | closes |
| 2 | +0.406 ns | +0.078 ns | closes |
| 3 | +0.257 ns | +0.098 ns | closes |
| 4 | +0.421 ns | +0.112 ns | closes — pinned |
| 5 | +0.251 ns | +0.078 ns | closes |
Hold binds at every seed. The established selection rule — maximise the binding margin — mechanically picks seed 1. The pin moved to seed 4 instead, on a maintainer decision to prefer best overall: within 3 picoseconds of seed 1 on hold while beating it by 174 picoseconds on setup. Both the reasoning and what would falsify it are written into RustyNES.qsf alongside the pin rather than left implicit: 3 ps is inside a run-to-run fitter variance this project has never measured, so seed 1's hold "advantage" is not established as a repeatable claim, while 174 ps of setup is far outside anything a re-fit would plausibly move. The falsifying experiment — re-fit one seed several times from a clean database and measure the spread — is named and marked not yet done.
Seed 5, the previous release's outright winner at +0.102 ns hold on the old RTL, is joint-worst on this one. It is the sharpest illustration yet of the rule the table exists to enforce: a seed sweep describes one RTL and nothing else, and carrying a pin forward across an RTL change is exactly the mistake the sweep prevents.
The resulting bitstream, RustyNES_20260920.rbf (md5 5186279b7c7742e498f2f6d4cfdd0abd, 4,100,132 bytes), was verified reproducible as a by-product: check_timing.py refused an earlier attach because merging the RTL change and pulling main had rewritten source files after the compile, so the build was re-run rather than the gate reasoned past. The re-run produced a byte-identical artifact — confirming both that the intervening change was inert and that a from-scratch compile of this design reproduces exactly.
The 31 legacy AccuracyCoin sub-test ROMs now say which test they run, and it is a measurement
Their (suite, test) indices had been unrecorded for four releases, under a correct note that rebuilding on guessed indices is worse than a stated gap. Both ways of un-guessing them turn out to be wrong, and confidently: a sub-test ROM embeds its own copy of the assembly, so its encoded index names a row of that build's table, and upstream has since reordered suites and inserted tests inside them. Resolving ppu-misc-stale-bg-shift-regs (encoded 18/2) against the current assembly gives $2002 flag timing; against the stale suite map it gives INC $4014. It is neither — the ROM writes $0483, Stale BG Shift Registers.
subtest_identify boots each ROM and reads which byte in $0400-$04FF it writes, which is the catalog's own key and needs no map at all. The control: RustyNES_MiSTer/tb/regress.sh independently registers 18 of these ROMs by an address read out of golden RAM in v2.6.4/v2.6.5, and all 18 come back identical. BUILD-PROVENANCE.tsv now carries all 33 rows, and accuracycoin_subtest_provenance.rs re-measures every one of them — seven mutations of the manifest and one of the corpus path, all caught.
The sweep that built this also found ppu-misc-2004-stress.nes does not run $2004 Stress Test — it writes $048E, which is $2007 Stress Test, the same entry ppu-misc-2007-stress.nes already covers. The corpus holds two ROMs for one entry and none for $2004 Stress Test ($048C) while appearing to hold one. Neither was a registered gate, so nothing relied on the name; the collision is now allow-listed with its effect stated, so a genuinely new one fails instead of quietly joining a category that already has members.
tests/roms/AccuracyCoin/README.md had this backwards: it said the result addresses are "not always the catalog's." Every one of the 33 ROMs writes the catalog address of some entry, exactly — what varies is whether that entry is the one the filename names. The page's --suite N --test M recipes are also relative to the upstream source of their day and several are now stale, which the page now states rather than reading as commands to re-run.
Also in this release
docs/agents/measurement-discipline.mdgrew from 11 to 14 findings (#536): a component measured through a defect upstream of it reads as negligible and the number is honest (the CHR write address was quantified at 715 of 32,861 pixels and parked on that basis, worthless untilvwas fixed); a complete sweep over the wrong space terminates with a confident negative; and a per-dot trace from two models is not comparable until a reference event with no modelling freedom calibrates it.- A version bump turned out to be a claim in sixteen places, not one number in
Cargo.toml.release_anchor_audit(14 doc anchors, including both release-line-continuity chains),cosim_manifest_audit(the workspace-excludedrustynes-cosimcrate's ownCargo.tomlandCargo.lock, unreachable by a root version bump because that exclusion is deliberate — trace features would otherwise unify into the default build), andlibretro_info_audit(the.infometadata RetroArch and the libretro buildbot actually read) are three disjoint checks with no overlap; this release needed all three to go green. - Fixing those anchors corrupted a file, twice, in two different ways, from the same underlying cause.
[^\n]*matcheddocs/STATUS.md's entire 32,134-character release-narrative line and replaced it wholesale, deleting thirteen releases of lineage behind a diff stat reading "1 changed, 1 added" — caught by the structural chain-continuity test, not by any content check.[^.]*— a character class excluding the literal byte.— stopped inside the next version literal inOVERVIEW.md's own chain (v2.6.21has a period in it too), splicing new text mid-word into syntactically-broken prose that still read as intact.agy(Antigravity review) caught the second one in its very first review round; it sat unfixed for three more rounds because a bot's archived rounds collapse by default and a repeat "Suggestion" doesn't visibly read as a previously-caught bug. Both are restored, with the new release prepended to each chain rather than overwriting into it.