From c986411ff784a64b358bd0297b0a09bb2eabc92d Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 18:45:30 -0400
Subject: [PATCH 01/44] fix(mappers)!: mapper 45 CHR-RAM is unbanked
(T-GA23C-CHRRAM)
Famicom Yarou Vol.1 7-in-1 (mapper 45, 256 KiB PRG, CHR-RAM) booted to
noise in every release that staged it. The v3.0.0 trace showed why: the
menu writes all 8 KiB of pattern data through $2007 while every MMC3 CHR
register is 0, then draws with R0-R5 = 0, 2, 4, 5, 6, 7. Under MMC3 CHR
banking that upload lands in 1 KiB banks 0-1 only, and the outer CHR-OR
can move it further, so banks 2-7 are never written and the screen shows
whatever the RAM powered on with.
The document. The mapper 45 page says nothing about CHR-RAM, which is why
v3.0.0 left the ticket open ("fix only from a document or a hardware
measurement"). The answer is on the page of a GA23C variant: NES 2.0
mapper 372 is "INES Mapper 045 but with one bit of outer bank register #2
working as a CHR-ROM/RAM switch", and its bit table reads "Select CHR-ROM
(0)/CHR-RAM (1, unbanked)". That is the family's own CHR-RAM wiring, and
it is exactly what the trace needs. (Mapper 356, a second GA23C variant,
gives its CHR-RAM as a plain 8 KiB.) A NESdev forum post on MMC3 clones
names the two wirings boards use -- RAM A10-A12 on PPU A10-A12
("unbanked") or on the MMC3's CHR A10-A12 ("banked") -- and mapper 45
carries no submapper to choose between them, so the family document
decides.
The change: in Mmc3Board::chr_target, a Board::M45 with chr_is_ram returns
Chr::Rom(addr & 0x1FFF), the whole-image CHR-RAM indexed by the PPU
address. The existing write path already stores Chr::Rom offsets into
CHR-RAM when chr_is_ram, so reads and writes agree. CHR-ROM boards take
the unchanged arm below it.
Verification:
- m45_chr_ram_is_unbanked (new) replays the traced sequence: CHR
registers 0, 8 KiB written, R0-R5 = 0,2,4,5,6,7, outer registers set to
CHR-OR $10 / CHR-AND $7, and every byte read back. It FAILED before the
fix ($0000 read 7: register 0 banked it elsewhere) and passes after;
removing the new arm is that same failure, so the mutant is caught.
- rustynes-mappers: 954 passed, 0 failed.
- external_coverage on the five local mapper 45 dumps: only Vol.1 moved.
Its new frame is the "7 IN 1" title scene (trees, birds, sun, a girl in
green); the old committed screenshot was noise. Snapshot re-blessed and
screenshot replaced; all five then pass. The other four carry CHR-ROM
and cannot reach the new arm.
EMULATION_EPOCH 1 -> 2. ADR 0045's mechanical trigger is a moved
snapshot, and Vol.1's moved: a v3.0.0 movie of it would replay on
different pattern data, and a v3.0.0/v3.0.1 netplay pair would desync.
Both are now refused, naming both epochs. Everything that tests the epoch
is relative to the constant (movie.rs, bk2_interop.rs), so core and
netplay pass unchanged (251 + 101 + the rest, 0 failed). The constant's
doc now carries a table of which release moved it and why.
Docs in the same change: docs/mappers.md row 45, the ROADMAP ticket
(FIXED v3.0.1 with the source), CHANGELOG [Unreleased].
Not established: no hardware has been measured. The fix rests on the
372 page describing the family plus a trace that requires it; a mapper 45
board wired "banked" would need a submapper the format does not have.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
CHANGELOG.md | 15 +++++++
crates/rustynes-core/src/hardware_options.rs | 9 +++-
crates/rustynes-mappers/src/mmc3_boards.rs | 5 +++
.../rustynes-mappers/src/mmc3_boards_tests.rs | 39 ++++++++++++++++++
..._GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap | 5 +--
docs/mappers.md | 2 +-
.../Famicom Yarou Vol.1 7-in-1 (Unl).png | Bin 3816 -> 3110 bytes
to-dos/ROADMAP.md | 15 ++++++-
8 files changed, 82 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d46e210..7485c312 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,21 @@ cycle-accurate core later replaced.
## [Unreleased]
+### Fixed
+
+- **Mapper 45 (GA23C) CHR-RAM is unbanked (T-GA23C-CHRRAM).** *Famicom Yarou
+ Vol.1 7-in-1* now draws its menu instead of noise. Its CHR-RAM is addressed
+ straight from PPU A10-A12, so the MMC3 CHR banks and the outer CHR registers
+ no longer apply to it. The mapper 45 page says nothing about CHR-RAM; the
+ GA23C variant with a ROM/RAM switch (mapper 372) documents its RAM as
+ unbanked. The four mapper 45 carts with CHR-ROM are unchanged.
+
+### Changed
+
+- **`EMULATION_EPOCH` is 2.** The mapper 45 fix changes what *Famicom Yarou
+ Vol.1* produces, so movies recorded and netplay peers running v3.0.0 are
+ refused, naming both epochs (ADR 0045).
+
## [3.0.0] - 2026-10-06 - "Cornerstone" (the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core)
The MAJOR release the v2.9.x line prepared for (ADR 0043). v2.9.8 and v2.9.9
diff --git a/crates/rustynes-core/src/hardware_options.rs b/crates/rustynes-core/src/hardware_options.rs
index e4c75fc3..e5244efb 100644
--- a/crates/rustynes-core/src/hardware_options.rs
+++ b/crates/rustynes-core/src/hardware_options.rs
@@ -87,10 +87,15 @@ const MAX_GENIE_CODES: usize = u8::MAX as usize;
/// from) do not bump it. A release that moved goldens without raising it
/// breaks the promise this constant exists to keep.
///
-/// It is 1 at v3.0.0, the first release to carry it. Earlier builds have no
+/// It was 1 at v3.0.0, the first release to carry it. Earlier builds have no
/// epoch, and are refused by the movie format (5) and the protocol (6)
/// instead.
-pub const EMULATION_EPOCH: u32 = 1;
+///
+/// | epoch | release | what moved |
+/// | --- | --- | --- |
+/// | 1 | v3.0.0 | the MMC3 background-at-`$1000` A12 rule (T-MMC3-BG-A12) |
+/// | 2 | v3.0.1 | mapper 45 CHR-RAM unbanked (T-GA23C-CHRRAM; *Famicom Yarou Vol.1*) |
+pub const EMULATION_EPOCH: u32 = 2;
/// Every host-settable option that changes what the emulated console does.
///
diff --git a/crates/rustynes-mappers/src/mmc3_boards.rs b/crates/rustynes-mappers/src/mmc3_boards.rs
index 397b90bd..36030f96 100644
--- a/crates/rustynes-mappers/src/mmc3_boards.rs
+++ b/crates/rustynes-mappers/src/mmc3_boards.rs
@@ -469,6 +469,11 @@ impl Mmc3Board {
rom((raw & 0xFF) | (a18 << 8))
}
Board::M37 => rom((raw & 0x7F) | (((r(0) >> 2) & 1) << 7)),
+ // T-GA23C-CHRRAM: CHR-RAM is addressed straight from PPU
+ // A10-A12, bypassing every CHR bank. The mapper 45 page is
+ // silent on CHR-RAM; mapper 372's page, the GA23C with a
+ // ROM/RAM switch, documents its RAM as "unbanked".
+ Board::M45 if self.chr_is_ram => Chr::Rom(usize::from(addr & 0x1FFF)),
Board::M45 => {
let c = r(2) & 0x0F;
let mask = if c >= 7 { 0xFF >> (15 - c) } else { 0 };
diff --git a/crates/rustynes-mappers/src/mmc3_boards_tests.rs b/crates/rustynes-mappers/src/mmc3_boards_tests.rs
index fd87f72e..e8349ba2 100644
--- a/crates/rustynes-mappers/src/mmc3_boards_tests.rs
+++ b/crates/rustynes-mappers/src/mmc3_boards_tests.rs
@@ -255,6 +255,45 @@ fn m45_power_on_and_reset_pass_every_mmc3_chr_bit() {
check(&mut m, "$6001");
}
+/// T-GA23C-CHRRAM. A GA23C board with CHR-RAM addresses it straight from PPU
+/// A10-A12: the MMC3's CHR banks and the outer CHR registers do not reach it.
+/// The mapper 45 page is silent on CHR-RAM; the GA23C variant that switches
+/// to CHR-RAM, mapper 372 (`NES_2_0_Mapper_372.md`), documents that RAM as
+/// "unbanked". *Famicom Yarou Vol.1 7-in-1* depends on it: its menu uploads
+/// 8 KiB through `$2007` with every CHR register 0, then draws with R0-R5 =
+/// 0, 2, 4, 5, 6, 7. That sequence is replayed here; under MMC3 banking the
+/// upload lands in banks 0-1 only and banks 2-7 read back as zero.
+#[test]
+fn m45_chr_ram_is_unbanked() {
+ let mut m = Mmc3Board::new(
+ Board::M45,
+ prg_image(32),
+ Box::default(),
+ Mirroring::Vertical,
+ 0,
+ 0,
+ )
+ .expect("valid sizes");
+ for r in 0..6 {
+ mmc3_reg(&mut m, r, 0);
+ }
+ for addr in 0..0x2000u16 {
+ m.ppu_write(addr, (addr >> 10) as u8 ^ addr as u8);
+ }
+ for (r, v) in [0, 2, 4, 5, 6, 7].into_iter().enumerate() {
+ mmc3_reg(&mut m, r as u8, v);
+ }
+ // Outer registers that would move a banked CHR elsewhere change nothing.
+ m45_outer(&mut m, [0x10, 0x00, 0x07, 0x00]);
+ for addr in 0..0x2000u16 {
+ assert_eq!(
+ m.ppu_read(addr),
+ (addr >> 10) as u8 ^ addr as u8,
+ "CHR-RAM byte ${addr:04X}"
+ );
+ }
+}
+
#[test]
fn m45_dip_switch_reads_on_d0() {
let mut m = board(Board::M45, 16, 8);
diff --git a/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_045_GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap b/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_045_GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap
index 935bbaaf..21cdbf6c 100644
--- a/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_045_GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap
+++ b/crates/rustynes-test-harness/tests/snapshots/external_coverage__mapper_045_GA23C_Famicom_Yarou_Vol_1_7_in_1_Unl.snap
@@ -1,6 +1,5 @@
---
source: crates/rustynes-test-harness/tests/external_coverage.rs
-assertion_line: 681
expression: text
---
rom=mapper-045-GA23C/Famicom Yarou Vol.1 7-in-1 (Unl) [!].nes
@@ -10,5 +9,5 @@ fb_bytes=245760
cycles=33949775
audio_samples=836505
audio_fnv1a64=d046673ae808f5c4
-checkpoint f900 fb_fnv1a64=2474ee6337073864
-checkpoint f1100 fb_fnv1a64=2474ee6337073864
+checkpoint f900 fb_fnv1a64=5cab1ca79e100154
+checkpoint f1100 fb_fnv1a64=5cab1ca79e100154
diff --git a/docs/mappers.md b/docs/mappers.md
index 4f881e9e..55eafc8d 100644
--- a/docs/mappers.md
+++ b/docs/mappers.md
@@ -716,7 +716,7 @@ the page gives only Disch's notes, or masks marked "probably".
|------|-----|--------------|------|-----|-------|
| 12 | 0 | Gouder SL-5020B (`INES_Mapper_012`) | Curated | MMC3A (the alternate / NEC behaviour) | `$4100` mask `$E100`: CHR A18 per pattern table (bit 0 for PPU A12=0, bit 4 for A12=1), outside the ASIC so unaffected by `$8000` bit 7. The *Dragon Ball Z 5* language bit read at the same address returns 0 on D0: the page says every known copy is hard-wired to Chinese but not which level that is, so this is an assumption. Submapper 1 (the Magic Card 4M extraction) is a different device and is not supported. |
| 37 | — | SMB + Tetris + NWC (`INES_Mapper_037`) | Curated | MMC3 | The 74HC161 at `$6000-$7FFF`, written only while the MMC3's `$A001` allows a PRG-RAM write; PRG A16 = Q0·Q1 + Q2·M16, A17 = CHR A17 = Q2 (the page's NAND equations). Write-only (open bus). The CIC reset clears it (`Mapper::reset`). |
-| 45 | — | GA23C (`INES_Mapper_045`) | Curated | MMC3 | Four outer registers written in turn at `$6000` (mask `$F001`): CHR-OR, PRG-OR, CHR-AND + high bits, PRG-AND (inverted) + lock. `$6001` resets and unlocks, as does a soft reset. `$5000-$5FFF` reads the menu DIP switch on D0. WRAM only when a NES 2.0 header declares it. The page gives no power-on or reset value for the outer registers. Power-on, soft reset and `$6001` set `[$00, $00, $0F, $00]`, so CHR-AND passes every MMC3 CHR bit (T-GA23C-POWERON, maintainer 2026-10-05). The evidence: two *Famicom Yarou* menus (54 and Vol.5) draw with CHR banks 0-7 before their first outer-register write, which needs CHR-AND at `$A` or more. *Famicom Yarou Vol.1* (CHR-RAM) still shows noise: its menu uploads all 8 KiB of pattern data with every CHR register at 0, as if the RAM were unbanked (T-GA23C-CHRRAM, open). |
+| 45 | — | GA23C (`INES_Mapper_045`) | Curated | MMC3 | Four outer registers written in turn at `$6000` (mask `$F001`): CHR-OR, PRG-OR, CHR-AND + high bits, PRG-AND (inverted) + lock. `$6001` resets and unlocks, as does a soft reset. `$5000-$5FFF` reads the menu DIP switch on D0. WRAM only when a NES 2.0 header declares it. The page gives no power-on or reset value for the outer registers. Power-on, soft reset and `$6001` set `[$00, $00, $0F, $00]`, so CHR-AND passes every MMC3 CHR bit (T-GA23C-POWERON, maintainer 2026-10-05). The evidence: two *Famicom Yarou* menus (54 and Vol.5) draw with CHR banks 0-7 before their first outer-register write, which needs CHR-AND at `$A` or more. CHR-RAM is unbanked: addressed straight from PPU A10-A12, past the MMC3 banks and the outer CHR registers (T-GA23C-CHRRAM, fixed v3.0.1). The page is silent on CHR-RAM; mapper 372, the GA23C with a ROM/RAM switch, documents its RAM as unbanked, and *Famicom Yarou Vol.1* uploads all 8 KiB with every CHR register at 0 and then draws with R0-R5 = 0, 2, 4, 5, 6, 7. |
| 47 | — | Spike V'Ball + NWC (`INES_Mapper_047`) | BestEffort | MMC3 | One block bit in the PRG-RAM window, gated like mapper 37. The page is Disch's notes only. The Kasheng *2-in-1 (Mortal Kombat 6, Samurai Spirits)* is often labelled 47 (512 KiB CHR, `$6000` written with `$C0`/`$60` while WRAM is disabled); it is NES 2.0 mapper 291 (`NES_2_0_Mapper_291`), which this project does not implement. |
| 74 | — | Waixing 43-393 (`INES_Mapper_074`) | Curated | MMC3 | CHR banks 8 and 9 are 2 KiB of CHR-RAM. 8 KiB work RAM. A dump that writes `$A001` with bit 5 set (`$EC`/`$ED`), `$5FF3`, or MMC3 registers 8-11 is a Waixing FS005 re-release, mapper 176 submapper 2 (`INES_Mapper_176`), not this board. |
| 83 | 0/1/2 | Cony / Yoko (`INES_Mapper_083`) | Curated | 16-bit M2, up or down | Three PRG modes, `$6000` ROM (subs 0/1) or 32 KiB banked WRAM (sub 2), 1 KiB / 2 KiB / outer-banked CHR. On iNES the submapper follows the page's CHR-size heuristic. The DIP and scratch-RAM masks are "probably" on the page and are decoded inside `$5000-$5FFF` only. |
diff --git a/screenshots/external/mapper-045-GA23C/Famicom Yarou Vol.1 7-in-1 (Unl).png b/screenshots/external/mapper-045-GA23C/Famicom Yarou Vol.1 7-in-1 (Unl).png
index 9fdb27207af364cbca3524c4235f4bf1a5a4bde6..8f89fdecd6e3e4d1ac081d6c7c0a8fba96a8d234 100644
GIT binary patch
literal 3110
zcmds4X;c#08a<#QPKl*f=9s0HW3TIQGtf-QSt||AIV%+^EUgSuv`o#RZm(OKl~`UH
z9Y{npMI2tCS)yrB4xnC#3Wv-D424HO->>)ct@Zw#b=La!+TULL+uvH}Tn!G~r=@P7
z4gk>d@%B6b07P*G-{3ICIbZy;8vu=2AJ07@Y4>JF+|HOcZhRf*^D5pYwiPFV5uuj+
z%pS{6y?r@>7S5MXkFS%!T5_N&ExD1sYp@Q8OxR1v58kS*>{->*tzKG>J_!okw4Ch4
z`1$!Du!T7@0)W&EfH-AfS_nY44#;$X0t6a>b~td~00D6G|M#LD(b;{(O5w4fu7}@+&0X08rx6;oq4Nw>^lEKs!qRARrq9L$72Qb}z0DNu~TEK-GTqKQ-C)
z!phH;elpP%WEwR*l(S6sboba=zVE
z+$@y~GScTzda=sDA0ZlVOzuR^U5XpHH1^Aok`F5GAI%L^B+%+R{p?pLETmTvMa`DC
zyaOu>oFbcyTmUSx-LbzRG)&0!J$V4Y@`=fq2@?&%e)xp3Ns%LnrP6teesR4xZ0j-i
zR_gF%do&!B(*^@vaZ@XllN~rJ3}_C@Hl0%HSsJ9^Os1_)xQ#B3!ma$-;
z@m7uav*9zb2nq+W!3*pw&2{S>mP-46@x0%cR*cd>lZQ_FrDc}N7vyiw$C5|OKcgPSLBM3iiq=}O@({!LW*9%ni_IwvT2If*W3IVoHgJ-It(`nXRqx0w(Hw1tYDO+m^1=DVef1|bM+}zlG?if-Ef2}W!N;KM=2pb%KRi4s*v7)8q1e50gFi2rs%%!K)R
zwGWNPNZ#|}vV*VTu^!r9+HRr^f|l!0U-K-<@9SYTZSl{>l7E*bRNL0s%bLdVS+59`
zZJG982NwM|Il(gRD}_7`hnaq1@cxqM_ftpR;V0IMrxxO$POtvG_0X;zH!+aNEK0t$
zO)XNpT!9!iZWf%j(M#KI=sO3!!b;L{2#tIieYR#Kggcb5>bXdE8kS1xyluZzxGF0g
ziOQ#nX3OJf(D;CEO^xh(xBH0#c3(S4!~8EttcNg9ZgXcsohHpC~u5QyvfqKR~GT+zsiYRSeF{3bD-W
z9Nb_CJ~4gkP&i)TouJ-AAr-_ZPjE*`v6iN#MACCX1i}t9ImEmi78}ed>oRA5PTFXA
za)-{tDAqPa5`K@NuOHEUT%`@eO7gaW=h#7CDjCTVPBtVl(}^kx*!=_Y$Y<0u*E!ty
zTk_Bu-?nGl+`pC*0>4h91&M70=`{=Im){?3L^0SeghqokEcy2xYlB@>qq+BhqsDPR
zuaAoXG(M_(*oTi>*g1^ZA(hZY!_UYvoH~%Br*hJ5EdwPqW=?@Z(a{Wp;KLCbk~D}>>~k)vSaJ|p5KMR3)k93VEX;s
zBDUAkc_USfdAPGxT=pVcN|gRnRzh8SO3mWY^gVeclXU`QGdoI`Q@%R-F$21HcN3%`
zds}1B6iHC(M(XivGY#bBMRLZ9oMatnBPTL6X$rE!GwksvS4hVUg88Zl{c!B9-RC{(
zdir>F^$Nr9aFsrfdGSE|x-Bz4>8Do*nq>H8`?pn2HAdJ7yx>N*>*l}N9R18Ent*h`
zMh_D(A7N0N%ex;Dh9{pL`dK(}0)<+6CFZeCyzUla^H&&yB+F*e$pflZ1+TW%9HqfIe(e|rBDAX87
zmCmJFdQFtnhk2XTaYky>eFf`+ba@{f{e`)czOGj<_FbDms8!2H75vFl2|+{bk?P&+
zG1Uw>rqr*v6iwx$glA&PAH0{w1o6#Ir~|hdk?PZsqo<pVslJ0@RlL1{u28vnDRyG8XQ)e^_2q8X;hc%aO{oK?I8iBI-YHrf3i~4?
z#A&ngoaOeFAP7SAkho2+SiwuGmJ~aQ8eTC=-?=g7G0>i{A7rTMt@7w@H5SFi_ti0G
zx=YUOaTdAx8|`4G!~}(XU~L%vIN#biTd^{RLjf~$?gcnNxs;NUc>eAzANkzAx>UEG
zkNUVl#`__2z7a+O=J4`Y1hoR?oCvH0SD9GaDMo9a1$UC?oJ?Pd}lA
z__t#u)Z=}>W|aIHzzeq4g&b|MV*Kb$NHj_A+?E-UxWJ5$+|rVaB@8xn5;Cy8irgId
z)H9@0c+y@dmu1Bb;q31^P-BOeeOhK(7&uITyIpd
zSQm7Xzr@$kYm8nIq*VDXaHtYula9*pX>f6V*s7R;s#>1?VS!y8*8C6JZ~k_AeT8QA
z3YQH9-P2s??J~@JU2r-js?s>0b0!xm~v$0}v|*6tVbO4cYXf4t@TNxBvBD)nc&+x(GN|jsw#-}nNNo#^v??06zNr!M=N?eI6|qY
zW`*^Yiwj=HF=4$Cm$!ky2X%=&gEg2NDy`KR$T*)qxof5H!#Uxs(i%4?ya4+&c9{9|
txMGx}x>5iPI}{)=Q}AD2*=4D#CXJ~UOJSF<*!=ni_;>|+R(r%=`ZwlGiTwZo
literal 3816
zcmc&%YgALm7M?>8P$a-AQwplC6uCsC}3R#F8OA1H_xt4J__ASC4A3oQt?
zsKrVQq6Oq3Qhb4u1fc{f4=I>bpaz#qL2>#TLwWMUNJ
zw@=P}9_a5d<-I8Y0Ed9}YqtP^z#&lD*}(Vcx7AMpz?TQCT^*d5JItv_|4``iW65@5
z!xZA7aNJ48obY*afpcc78Jk{q{e1YWEA}5Q&v5wUSl9)tKB>(q=f{D$KZPB&spQF)
zq{setu%g&oe4?Vj@7`}0(=k?KNdv=q|t0v6QT%FYpXCtbG
z{%jk^q;u|1SK0WguCqQ!Z_h+%NEbsOo(58}o$KsCC+SM^a9>jEwWBhc{IZ+WHFGT%9I
zS%VJ)D4CqoM%yfNbQN8#gR@7u&K!MWBhs6lJ;QB&Uk-uV
z{NK^Mwm7E+24cDOU$Pf^kpzGl)<0wRn!uY?n+c*xj^p7lQg<>4<<8{joBz=RFSbyS
zHa{(9PdNUh4m_0P>g)oCI9aLjNIh$3+>q4W_MN*^yKKjhg>Lhy(6s=%zOzWM#glB!
z&CyvOqC>9%OyW9$U|G{E~fho!z5CRL)
z-r{oeCDCRK*{|MFCyiPC#V_^-Vc&+^6psChS8NGW)|Kw>Gm#>er@ds0#oWEwITXoQ
z(~8z*^DB9)PkX#6Mt$@|253MHzL$^W9{7YKIx=RQT_Gl=#7^Z0oauTa-Bf;(_g+6P
zr>KoG$tYJ!jJ21Pl)4CoK0hHq6&{LBWBu|?8+elJK&$wce{Uu$dfe09HmtXDbjiZc
zpJdIglPfu=&1$9D)zrV4Z9~oMEB%ECiM5|6c^qdDn7dH=>r;u03;i=CD4R<~>+4>+Uv|T9l
zpu5%>b#k_;r^cw+C};DIkN-e9pv>Btn8>8E}K0H-}YLwNUMp-pc!JZIOsuD~O
z>bPt}ou~SZu~W=877E2&Aw}J0&mMivy`p~oa6k%6#6}XQ?bMFny9dij5XozIQ@(gq
z%&?Jx%Slb{+D7fb-%!fG!Jks@W9Nrhon=U*r#AfupbPHBG*dt@I*M5!Az1wK?3!kOzmEQ*Pr6(GtGGXl&su6WJ*UmljN6MT_|lH1x6*r{Q}~@UD>wP5
zS?x=<=wRq0R8-o%`L
zGaJy;)n2F^?*qSgl?Nl#fVrDEdw3ahrktpqA2fo=9EXSf^ze5m0~w*^w=!dCd3P3H7R4|
zUaB7+kjQfVFa+m??ZnlT=lrs38}ZaS+jZse&a;pvq_;!kkV+ILlARAjj%Wn~2SoBw
zZtFKAg|vC9(i24NEFojhF;-^6WA2_w43Lj5PCBltN=`Qnmbu${VMHi^GfC#eG{fK}
zH(M$mBMR`SdFvC?xQGKRf>RHVuG%o(4z1mO!`%>vlHfTPa07F!$XK_*`0a|9G`fQ&
z3obPoL_vF|rsENTFYQUD@$?IcOZlh+cIv4
za!cxo0#z;L;WIfwtKlyuSw$t&*i&R+Ei()@i0B+hK`K6vZYKi`Pm#O!tC|&&@zY|&
z!}Jkezc2eMDVuB@oXkGUZJ|(n<*ZYy-AYaHRf6&H$d!Dpix-e)aTt(C>CjD6V7%HJ^Ha-gBAJ_#a&~__*mhP
z>E|%R`0B&rjc-z&RLU0q`D<5?r43i!mK+(Mm>Nbx4-V@WkP=J1!Ra>o^{jZCJiRVR
z1wTfrU&@W||8_E;KA!sP6jI_sk0P0Atd}I=PjE=PM)T#bKv%lfuuJUuDP_HC8uYJ8
zEsJNzoK8Qqnr|GfDO2^VJo}g*Z?b%>C?gz@UxlaMw2Bnix0nMxpRKaZW7y>FuQv?+&
z7WjeZ`kO-w{IG3z(Oa-!f0Ccx-s$9NwMXH_Q&VFB2woJ?tkK#*QEpM
z_$dV=GqQ0--nP%8UFL0T3E0mixLxW&3smYI5yKzIpE;$0)ObIvsUN6c;7SbjGB6%@
z$s24Xu`d#-3#wTA6~mPcT|!SXbkJTKyo&T6lk;t#t+!;;59>jr7LhuV45h+bGH}0y
zHsqM+qGB@_?(Q}wXn4C2dY{e>I`QKk_E)NiP~gKMQU_7vk^7!-
zYn>*m3^L5bh@y-kkhmK(eP!ZFXKPXhp(*x7wPk;zA|BaS4vj))
z>dPNB6f)L)aRgV`Z@onvf40T;f+5A+rJ?v?tF8}?HH>$<`qBa)m}7wY>W^mcrpn^1izahh^V
zocSAE<7b2DoqK_J!PmGPt73FCI4(Z(M+Gdn86tFKrfPZ`*r19)!3Qvkj|PW`>EbLR
z)n3>eIE8YyJ%;tXBz#yJxY-l3{QJ+LEC9i_
q7zdEIZvcP=XYl{$f#CU5Gg7oT<6Hw}D8=$eHsI61wYS%Vo%k2?`+O+?
diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md
index 6e800740..76d3a0da 100644
--- a/to-dos/ROADMAP.md
+++ b/to-dos/ROADMAP.md
@@ -1205,7 +1205,18 @@ So two dumps from the same series assume CHR-AND passes at least 3 bits
(`$A` or more) before any write. That rules out the all-zero power-on value,
but does not pin a value. The maintainer chose `$F`.
-## T-GA23C-CHRRAM — *Famicom Yarou Vol.1*'s CHR-RAM is uploaded unbanked (found v3.0.0)
+## T-GA23C-CHRRAM — *Famicom Yarou Vol.1*'s CHR-RAM is uploaded unbanked (found v3.0.0, FIXED v3.0.1)
+
+**Fixed in v3.0.1, from a document.** NESdev's mapper 372 page describes that
+board as "INES Mapper 045 but with one bit of outer bank register #2 working
+as a CHR-ROM/RAM switch" and documents the RAM side as "CHR-RAM (1,
+unbanked)". That is the GA23C family's own CHR-RAM wiring, and it is what the
+trace below requires. A mapper 45 board with CHR-RAM now addresses it
+straight from PPU A10-A12 (`mmc3_boards.rs`, `chr_target`). Pinned by
+`m45_chr_ram_is_unbanked`, which replays the traced upload-then-bank sequence
+and fails without the fix. Only *Vol.1* moved among the five local mapper 45
+dumps (the other four have CHR-ROM); its menu now draws, and
+`EMULATION_EPOCH` rose to 2 with it. The original analysis follows.
*Famicom Yarou Vol.1 7-in-1* (mapper 45, 256 KiB PRG, CHR-RAM) shows noise,
both before and after T-GA23C-POWERON. Its committed screenshot was noise in
@@ -1220,7 +1231,7 @@ v2.9.8 too, and that review accepted it as running. Traced on 2026-10-05:
The program behaves as if this cart's CHR-RAM is addressed straight from PPU
A10-A12, bypassing the MMC3's CHR banks. The mapper 45 page does not mention
CHR-RAM at all. Open: fix only from a document or a hardware measurement, as
-for T-GA23C-POWERON.
+for T-GA23C-POWERON. (The document turned out to be the mapper 372 page.)
## T-SPECTATOR-HISTORY — the spectator's input history grows without bound (found v2.9.9, FIXED v3.0.0)
From 4aae8c1343513aff238897e99de9219f6606754b Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:08:36 -0400
Subject: [PATCH 02/44] build!: Rust 1.99 for the workspace, 1.96 for the
libretro buildbot; every dependency at its newest
v3.0.1's dependency and toolchain refresh (the maintainer's request: every
version bump, PR #584 included, except the parts that must stay on 1.96).
THE TOOLCHAIN SPLIT. rust-toolchain.toml moves 1.96.0 -> 1.99.0 (newest
stable, 2026-10-01). 1.100 is the current beta and 1.101 nightly, so 1.99 is
the newest that is released. The pin had been held at 1.96 since 2026-09-28
for one reason: libretro's build image injects `-Car=...` into every Apple job,
and `-C ar` became a hard error in Rust 1.97. That reason still stands, so the
buildbot alone stays on 1.96.0:
- .gitlab-ci.yml `.core-defs` sets RUSTUP_TOOLCHAIN: "1.96.0". The variable
outranks rust-toolchain.toml, and every job's `rustup target add` became
`rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target
${RUST_TARGET}`. The shared libretro templates set neither RUSTUP_TOOLCHAIN
nor rustflags (checked in rust-apple/linux-x64/windows-x64/android-jni/
webos .yml).
- The seven crates that core builds (cpu, ppu, apu, mappers, core, gamedb,
libretro) keep rust-version = "1.96"; every other crate is 1.99.
- CI's libretro-cross job reads the version out of .gitlab-ci.yml with a
table-scoped, fail-closed awk (the same discipline as rust-setup's
resolver) and builds on it, so .github/ still carries no toolchain literal
and the 1.96 floor is checked on every PR rather than found on the buildbot.
That check has already paid for itself. clippy 1.99 rewrote
`for b in self.ram.iter_mut()` (a Box<[u8; 2048]>) to `for b in &mut self.ram`,
which only 1.97+ accepts (`&mut Box<[T; N]>` is not IntoIterator on 1.96);
clippy did not honour the crate's rust-version, and only the 1.96 build
caught it. Fixed as `&mut *self.ram`, with the reason at the site.
The RetroAchievements question (whether newer Rust eases the C bridge):
1.99 stabilises `extern "C"` variadic DEFINITIONS (VaList). None of the
rcheevos functions RustyNES calls is variadic, and the one C file,
static_asserts.c, exists only to report C sizeof values for an ABI test. So
nothing there gets simpler; recorded rather than invented.
CLIPPY 1.99. 28 findings in the first pass grew to about 70 once each crate
compiled (a failing crate hides everything downstream; `--keep-going` was
needed to see them). All are exact rewrites: `chunks_exact(N)` ->
`as_chunks::()` (stable since 1.88, so fine on 1.96 too), `fill`,
`unwrap_or`, byte-string literals, a redundant #[must_use], an array
destructure, and 34 `assert!(x.is_empty())` -> `assert_eq!(x, [] as [T; 0])`
(clippy::assert_is_empty), applied from clippy's own suggestions and made
crate-absolute where it emitted module-relative paths. `cargo clippy --fix`
compiles one feature set at a time, so every CI combination was re-run after.
DEPENDENCIES. Cargo: `cargo update` in both workspaces (23 lock changes; the
cosim lock only libc). The only crates not at their newest are held upstream:
getrandom 0.2 and 0.3 by piccolo 0.3.3 (its newest release) via rand 0.8 and
ahash, and generic-array 0.14.7 by crypto-common 0.1.7's `=0.14.7`. The
MSRV-aware resolver held nothing back (with and without it, `cargo update`
chose identical versions).
- GitHub Actions: taiki-e/install-action 2.87.21 -> 2.87.26 (this supersedes
Dependabot's PR #584, which proposed 2.87.22); dtolnay/rust-toolchain at
its current v1 (7e38f4b4, adds install retries). Every other action was
already on its newest major.
- macOS: macos-14 -> macos-15 in ci.yml and release.yml. The 14 image is in
deprecation brownouts since 2026-10-05 and unsupported from 2026-11-02
(actions/runner-images 13518), still aarch64.
- Android: cargo-ndk 3 -> 4 (our flags -t/-o/--platform are unchanged in
4.x), NDK r29 -> r30 installed by sdkmanager in each job (the runner image
ships r29; the version is one workflow env value), Gradle run on Temurin
17 -> 25 (bytecode stays JVM 17, AGP 9.4's floor), org.json 20250517 ->
20260814. AGP, Gradle, Kotlin, the Compose BOM and every AndroidX library
were already newest.
- Web: wasm-opt pinned at version_133. It was not pinned at all, so trunk
0.21.14 used its built-in version_123, and web.yml's header claimed both
tools were pinned; corrected. MkDocs on Python 3.14.
- Docker: the signaling image on rust:1.99-trixie (it said 1.86, two years
stale; the build was right only because rustup reads the copied
rust-toolchain.toml anyway), runtime debian:trixie-slim, raproxy on
python:3.14-slim.
- pre-commit: ruff 0.16.10.
Verified, on this tree:
- cargo fmt (workspace + cosim) clean; all 18 CI clippy combinations rc=0
on 1.99, plus scripts/ios-host-typecheck.sh; rustdoc -D warnings; no_std
thumbv7em build.
- On 1.96.0: `cargo check --release -p rustynes-libretro` and the no_std
chip-stack build.
- cargo test --workspace + the cosim crate on 1.99: 2,926 passed, 0 failed.
- Android: the CI cross-build command (cargo-ndk 4.1.2, NDK r30) and
`gradlew assembleFossDebug testFossDebugUnitTest` on JDK 27, newer than
CI's 25 (Gradle 9.8 supports both).
- Web: `trunk build --release` downloaded and ran wasm-opt version_133.
- Docs: `mkdocs build --strict` on Python 3.14.7.
- Docker: both images build.
Not verified locally: the libretro buildbot itself (no pipeline runs before
main moves; the 1.96 build of its crates is the PR-time stand-in), the iOS
Xcode build, and the GitHub-hosted macos-15 and Windows legs (CI runs them).
Not done, with the reason: Xcode stays on the newest Xcode_26* the runner
image has (Xcode 27 shipped 2026-09-14 but is not on macos-26 yet);
targetSdk stays 36 (the Play mandate; raising it is a behaviour change);
markdownlint-cli is already newest; Quartus stays 17.0.2 (MiSTer's flow).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.cargo/config.toml | 7 +-
.github/actions/rust-setup/action.yml | 2 +-
.github/workflows/android.yml | 60 +++++--
.github/workflows/ci.yml | 59 +++++--
.github/workflows/ios.yml | 2 +-
.github/workflows/pgo.yml | 2 +-
.github/workflows/release.yml | 5 +-
.github/workflows/security.yml | 4 +-
.github/workflows/toolchain-canary.yml | 11 +-
.github/workflows/web.yml | 6 +-
.gitlab-ci.yml | 34 ++--
.pre-commit-config.yaml | 2 +-
AGENTS.md | 2 +-
ARCHITECTURE.md | 2 +-
CHANGELOG.md | 20 +++
CONTRIBUTING.md | 4 +-
Cargo.lock | 166 ++++++++++--------
Cargo.toml | 6 +-
README.md | 4 +-
SUPPORT.md | 4 +-
android/app/build.gradle.kts | 2 +-
crates/rustynes-apu/Cargo.toml | 3 +-
crates/rustynes-apu/src/blip.rs | 2 +-
crates/rustynes-apu/src/lib.rs | 2 +-
crates/rustynes-cheevos/Cargo.toml | 2 +-
crates/rustynes-cheevos/src/lib.rs | 4 +-
crates/rustynes-core/Cargo.toml | 3 +-
.../rustynes-core/benches/snapshot_restore.rs | 2 +-
crates/rustynes-core/src/bus.rs | 5 +-
crates/rustynes-core/src/cpu_boot_trace.rs | 2 +-
crates/rustynes-core/src/hardware_options.rs | 2 +-
crates/rustynes-core/src/lib.rs | 2 +-
crates/rustynes-core/src/movie.rs | 8 +-
crates/rustynes-core/src/movie_interop.rs | 2 +-
crates/rustynes-core/src/nes.rs | 4 +-
crates/rustynes-cosim/Cargo.lock | 4 +-
crates/rustynes-cosim/Cargo.toml | 2 +-
crates/rustynes-cosim/src/checkpoint.rs | 10 +-
crates/rustynes-cosim/src/lib.rs | 2 +-
crates/rustynes-cpu/Cargo.toml | 3 +-
crates/rustynes-cpu/src/lib.rs | 2 +-
crates/rustynes-frontend/src/about_fx.rs | 4 +-
crates/rustynes-frontend/src/audio.rs | 2 +-
crates/rustynes-frontend/src/cheats.rs | 10 +-
crates/rustynes-frontend/src/cli.rs | 2 +-
crates/rustynes-frontend/src/config.rs | 2 +-
.../src/debugger/access_counter.rs | 2 +-
.../src/debugger/atlas_panel.rs | 6 +-
.../src/debugger/badge_cache.rs | 8 +-
.../src/debugger/callstack.rs | 4 +-
.../src/debugger/cpu_panel.rs | 4 +-
.../src/debugger/divergence_panel.rs | 2 +-
.../src/debugger/latency_panel.rs | 2 +-
.../src/debugger/memory_compare_panel.rs | 2 +-
.../src/debugger/memory_panel.rs | 2 +-
.../src/debugger/tastudio_panel.rs | 5 +-
crates/rustynes-frontend/src/genie_db.rs | 7 +-
crates/rustynes-frontend/src/genie_encode.rs | 2 +-
crates/rustynes-frontend/src/help_tui.rs | 2 +-
crates/rustynes-frontend/src/i18n.rs | 6 +-
crates/rustynes-frontend/src/icon.rs | 4 +-
crates/rustynes-frontend/src/input_macros.rs | 5 +-
crates/rustynes-frontend/src/movie_srt.rs | 4 +-
crates/rustynes-frontend/src/patch.rs | 2 +-
crates/rustynes-frontend/src/perf.rs | 2 +-
crates/rustynes-frontend/src/shader_pass.rs | 20 ++-
crates/rustynes-frontend/src/wasm_io.rs | 3 +-
crates/rustynes-frontend/web/Trunk.toml | 5 +
crates/rustynes-gamedb/Cargo.toml | 3 +-
crates/rustynes-gfx-shaders/src/crt_stack.rs | 4 +-
crates/rustynes-hdpack/src/hdpack.rs | 10 +-
crates/rustynes-ios/src/audio_ring.rs | 2 +-
crates/rustynes-libretro/Cargo.toml | 3 +-
crates/rustynes-libretro/src/abi_tests.rs | 6 +-
crates/rustynes-libretro/src/lib.rs | 4 +-
crates/rustynes-mappers/Cargo.toml | 3 +-
crates/rustynes-mappers/src/bmc_simple.rs | 8 +-
crates/rustynes-mappers/src/header.rs | 2 +-
.../rustynes-mappers/src/homebrew_boards.rs | 2 +-
crates/rustynes-mappers/src/lib.rs | 2 +-
crates/rustynes-mappers/src/mmc3_boards.rs | 2 +-
.../tests/battery_sram_exposed.rs | 2 +-
crates/rustynes-mobile/src/lib.rs | 12 +-
crates/rustynes-netplay/src/relay.rs | 8 +-
crates/rustynes-netplay/src/signaling.rs | 2 +-
crates/rustynes-netplay/src/transport.rs | 8 +-
crates/rustynes-ppu/Cargo.toml | 3 +-
crates/rustynes-ppu/src/lib.rs | 2 +-
crates/rustynes-ppu/src/state_trace.rs | 2 +-
crates/rustynes-probe/src/atlas.rs | 2 +-
crates/rustynes-script/Cargo.toml | 2 +-
crates/rustynes-script/src/lib.rs | 4 +-
crates/rustynes-script/src/mlua_backend.rs | 2 +-
.../src/bin/fds_smoke.rs | 4 +-
.../src/bin/fds_swap_repro.rs | 4 +-
.../src/bin/fds_trace.rs | 2 +-
.../src/bin/repro_smb3.rs | 12 +-
.../src/bin/smb3_dma_trace.rs | 4 +-
.../src/bin/vs_dual_trace.rs | 4 +-
.../src/bin/zapper_light_probe.rs | 7 +-
crates/rustynes-test-harness/src/coverage.rs | 4 +-
crates/rustynes-test-harness/src/lib.rs | 2 +-
.../tests/contribution_checklist_audit.rs | 2 +-
.../tests/holy_mapperel.rs | 4 +-
.../tests/mister_source_map_audit.rs | 6 +-
.../tests/release_notes_render_audit.rs | 35 +++-
.../tests/release_state_prose_audit.rs | 32 +++-
.../tests/vs_dualsystem.rs | 4 +-
.../tests/vs_system_rgb.rs | 4 +-
deploy/Dockerfile | 12 +-
deploy/Dockerfile.raproxy | 2 +-
docs/agents/ci-and-release.md | 2 +-
docs/agents/libretro.md | 1 +
docs/build-and-tooling.md | 8 +-
docs/dev/BUILD.md | 11 +-
docs/dev/STYLE_GUIDE.md | 2 +-
docs/testing-strategy.md | 2 +-
docs/user-guide/getting-started.md | 2 +-
ios/README.md | 2 +-
rust-toolchain.toml | 20 ++-
120 files changed, 560 insertions(+), 323 deletions(-)
diff --git a/.cargo/config.toml b/.cargo/config.toml
index 13013b58..7f34c53b 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -21,8 +21,9 @@
# handled by the `getrandom` `js`-feature shim in the frontend's wasm32 deps.)
rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=getrandom_backend=\"wasm_js\""]
-# MSRV-aware (v1.3.0 bumped 1.86 -> 1.96) dependency resolution. The workspace pins
-# rust-version = "1.96"; `fallback` makes `cargo update` prefer dependency
+# MSRV-aware (v1.3.0 bumped 1.86 -> 1.96, v3.0.1 -> 1.99) dependency resolution.
+# The workspace pins rust-version = "1.99" and the seven libretro-path crates
+# "1.96"; `fallback` makes `cargo update` prefer dependency
# versions compatible with that MSRV over the newest available. Without it, a
# future `cargo update` could pull a past-MSRV crate (e.g. `image` >= 0.26, a
# transitive dep of `arboard`'s clipboard image support, which needs a newer
@@ -54,7 +55,7 @@ full-build = "build --release -p rustynes-frontend --features full"
# self-heal instead of failing the job. This is repo-wide, so it covers EVERY
# cargo invocation: ci.yml, android.yml's `:app:cargoNdkBuild`, pgo.yml, web.yml,
# release.yml, and local builds. (Index fetches already use the sparse protocol
-# by default on the pinned 1.96 toolchain; these settings harden the crate
+# by default on the pinned toolchain; these settings harden the crate
# *download* step, which is where the failures occurred.)
[net]
retry = 10
diff --git a/.github/actions/rust-setup/action.yml b/.github/actions/rust-setup/action.yml
index b7289679..89be97fc 100644
--- a/.github/actions/rust-setup/action.yml
+++ b/.github/actions/rust-setup/action.yml
@@ -118,7 +118,7 @@ runs:
# them were reviewed rather than trusted -- runner updates, patch-release
# table entries, a cross-device-copy fix and checkout bumps.
- name: Install Rust toolchain
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1
+ uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # v1
with:
toolchain: ${{ steps.resolve.outputs.toolchain }}
targets: ${{ inputs.targets }}
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 61be99f6..c96114c7 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -60,6 +60,12 @@ concurrency:
env:
CARGO_TERM_COLOR: always
+ # v3.0.1: NDK r30, the newest stable (`ndk;30.0.16248370` in Google's SDK
+ # repository). The runner image ships r29 as its newest, so every job
+ # installs this one with `sdkmanager` in its "Resolve NDK" step. r30's
+ # changelog lists a newer clang, simpleperf prebuilts and sysroots up to
+ # API 37; nothing that touches a `--platform 26` build.
+ NDK_VERSION: "30.0.16248370"
jobs:
cross-build:
@@ -103,7 +109,7 @@ jobs:
- 'crates/rustynes-mobile/**'
- '.github/workflows/android.yml'
- # The project toolchain (rust-toolchain.toml = 1.96) plus the two shipped
+ # The project toolchain (rust-toolchain.toml = 1.99) plus the two shipped
# Android targets (arm64 ships; x86_64 is the emulator/CI ABI), through
# the shared composite. A bare `rustup target add` made rustup
# AUTO-INSTALL the pinned toolchain first, which rustup now deprecates
@@ -122,19 +128,25 @@ jobs:
# `taiki-e/install-action` knows, which is why this is the caching form.
- uses: taiki-e/cache-cargo-install-action@v3
with:
- tool: cargo-ndk@3
+ tool: cargo-ndk@4
# ubuntu-26.04 ships the Android SDK + an NDK; resolve its path so
# cargo-ndk finds the toolchain. (We pin a recent NDK; r27+ aligns .so to
# 16 KB by default — a Play requirement for Android 15+.)
# Both variables, to the same NDK. The runner image sets ANDROID_NDK_ROOT
- # to its default NDK (27.x) while this job selects the latest (29.x) via
+ # to its default NDK (27.x) while this job selects its own (r30, above) via
# ANDROID_NDK_HOME, and cargo-ndk warned on every build that the two
# disagree -- a real ambiguity about which toolchain linked the library.
- name: Resolve NDK
run: |
- echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
- echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
+ set -euo pipefail
+ sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
+ yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null
+ ndk="$sdk/ndk/$NDK_VERSION"
+ test -x "$ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" \
+ || { echo "::error::NDK $NDK_VERSION did not install at $ndk"; exit 1; }
+ echo "ANDROID_NDK_HOME=$ndk" >> "$GITHUB_ENV"
+ echo "ANDROID_NDK_ROOT=$ndk" >> "$GITHUB_ENV"
- name: Cross-compile mobile + android (arm64 + x86_64)
run: |
@@ -198,15 +210,25 @@ jobs:
cache-key: android-ndk
- uses: taiki-e/cache-cargo-install-action@v3
with:
- tool: cargo-ndk@3
+ tool: cargo-ndk@4
+ # v3.0.1: Temurin 25, the newest LTS (was 17). This is the JDK that RUNS
+ # Gradle; the bytecode target stays JVM 17 (`jvmTarget` and
+ # `sourceCompatibility` in app/build.gradle.kts), which is AGP 9.4's
+ # floor. Gradle 9.8 supports running on Java 25 (9.1.0 and later).
- uses: actions/setup-java@v6
with:
distribution: temurin
- java-version: '17'
+ java-version: '25'
- name: Resolve NDK
run: |
- echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
- echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
+ set -euo pipefail
+ sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
+ yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null
+ ndk="$sdk/ndk/$NDK_VERSION"
+ test -x "$ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" \
+ || { echo "::error::NDK $NDK_VERSION did not install at $ndk"; exit 1; }
+ echo "ANDROID_NDK_HOME=$ndk" >> "$GITHUB_ENV"
+ echo "ANDROID_NDK_ROOT=$ndk" >> "$GITHUB_ENV"
- uses: gradle/actions/setup-gradle@v6.4.0
with:
cache-provider: basic
@@ -254,19 +276,29 @@ jobs:
# `taiki-e/install-action` knows, which is why this is the caching form.
- uses: taiki-e/cache-cargo-install-action@v3
with:
- tool: cargo-ndk@3
+ tool: cargo-ndk@4
+ # v3.0.1: Temurin 25, the newest LTS (was 17). This is the JDK that RUNS
+ # Gradle; the bytecode target stays JVM 17 (`jvmTarget` and
+ # `sourceCompatibility` in app/build.gradle.kts), which is AGP 9.4's
+ # floor. Gradle 9.8 supports running on Java 25 (9.1.0 and later).
- uses: actions/setup-java@v6
with:
distribution: temurin
- java-version: '17'
+ java-version: '25'
# Both variables, to the same NDK. The runner image sets ANDROID_NDK_ROOT
- # to its default NDK (27.x) while this job selects the latest (29.x) via
+ # to its default NDK (27.x) while this job selects its own (r30, above) via
# ANDROID_NDK_HOME, and cargo-ndk warned on every build that the two
# disagree -- a real ambiguity about which toolchain linked the library.
- name: Resolve NDK
run: |
- echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
- echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
+ set -euo pipefail
+ sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
+ yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null
+ ndk="$sdk/ndk/$NDK_VERSION"
+ test -x "$ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" \
+ || { echo "::error::NDK $NDK_VERSION did not install at $ndk"; exit 1; }
+ echo "ANDROID_NDK_HOME=$ndk" >> "$GITHUB_ENV"
+ echo "ANDROID_NDK_ROOT=$ndk" >> "$GITHUB_ENV"
- uses: gradle/actions/setup-gradle@v6.4.0
with:
# v6 extracted the default ("enhanced") Gradle User Home cache into the
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e1c2dbe1..6e4c9f22 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -244,11 +244,11 @@ jobs:
# spin-ups vs three separate jobs. fmt runs first (it compiles nothing), so
# a formatting slip still fails in seconds.
#
- # Pinned to the project toolchain (rust-toolchain.toml = 1.96.0), NOT a
+ # Pinned to the project toolchain (rust-toolchain.toml = 1.99.0), NOT a
# floating `stable`: clippy adds lints every release, so `stable` makes the
# lint gate drift and break on a toolchain bump. Pinning ALSO makes the
# rustdoc gate match local `cargo doc` exactly (rust-toolchain.toml pins the
- # local compiler to 1.96 too) — the stable-vs-1.96 mismatch previously let a
+ # local compiler too) — a stable-vs-pin mismatch previously let a
# broken-intra-doc-link slip past local and only fail in CI.
#
# Compute ONE "full-run" flag that gates both the OS matrix and the expensive
@@ -493,10 +493,11 @@ jobs:
--features commercial-roms,debug-hooks -- -D warnings
# Cross-platform behaviour: full `cargo test` on stable across the three
- # shipped OSes. The previous ubuntu / 1.96 MSRV *test* entry is dropped — the
+ # shipped OSes. The previous ubuntu MSRV *test* entry is dropped — the
# `lint` job above already compiles the whole workspace (`clippy
- # --all-targets`) on 1.96, which IS the MSRV compile gate, and the core's
- # determinism makes "tests pass on 1.96" equivalent to "tests pass on stable".
+ # --all-targets`) on the pin, which IS the MSRV compile gate (the 1.96 floor
+ # of the libretro-path crates is `libretro-cross`'s job), and the core's
+ # determinism makes "tests pass on the pin" equivalent to "tests pass on stable".
test:
name: test (${{ matrix.os }})
# Gate the matrix on the fast fmt+clippy+rustdoc job (a lint slip fails in
@@ -587,7 +588,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [macos-14, windows-latest]
+ # macos-15, not macos-14: the 14 image is deprecated (runner-images
+ # #13518, brownouts from 2026-10-05, unsupported from 2026-11-02).
+ os: [macos-15, windows-latest]
steps:
- uses: actions/checkout@v7
with:
@@ -821,11 +824,39 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
- # No `toolchain:` input anywhere in this repo: `rust-setup` resolves it
- # from `rust-toolchain.toml`, so this job rehearses the buildbot on the
- # exact channel the buildbot uses.
+ # v3.0.1: the buildbot stays on an older toolchain than
+ # `rust-toolchain.toml` (it runs 1.96.0, the workspace 1.99.0; the
+ # reason is in `.gitlab-ci.yml`'s `.core-defs`). This job rehearses the
+ # buildbot, so it runs the buildbot's toolchain, read from
+ # `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN` -- the one place that version is
+ # written, so `.github/` still carries no version literal
+ # (`docs/agents/ci-and-release.md`). Exporting the variable makes every
+ # later `cargo` here use it, exactly as on the buildbot, which makes
+ # this job the check behind the seven libretro-path crates'
+ # `rust-version = "1.96"`. Table-scoped and fail-closed, like
+ # `rust-setup`'s own resolver: a missing or unquoted value aborts.
+ - name: Resolve the buildbot toolchain
+ id: buildbot
+ shell: bash
+ run: |
+ set -euo pipefail
+ tc="$(awk '
+ /^\.core-defs:/ { in_defs = 1; next }
+ in_defs && /^[^[:space:]#]/ { in_defs = 0 }
+ in_defs && $1 == "RUSTUP_TOOLCHAIN:" {
+ v = $2; gsub(/"/, "", v); print v; exit
+ }
+ ' .gitlab-ci.yml)"
+ if [ -z "$tc" ]; then
+ echo "::error::No RUSTUP_TOOLCHAIN under .core-defs in .gitlab-ci.yml"
+ exit 1
+ fi
+ echo "Buildbot toolchain: $tc"
+ echo "toolchain=$tc" >> "$GITHUB_OUTPUT"
+ echo "RUSTUP_TOOLCHAIN=$tc" >> "$GITHUB_ENV"
- uses: ./.github/actions/rust-setup
with:
+ toolchain: ${{ steps.buildbot.outputs.toolchain }}
apt: "false"
cache-key: libretro-${{ matrix.target }}
# Point bindgen at the NDK sysroot, which is what `cargo ndk` does for us
@@ -900,14 +931,14 @@ jobs:
# Deliberately NOT the composite action's `targets:` input. That routes
# through `dtolnay/rust-toolchain`, which installs the target for the
# channel IT pins (stable) — while `rust-toolchain.toml` then switches
- # cargo to 1.96.0, leaving the target missing on the toolchain that
+ # cargo to the pinned toolchain, leaving the target missing on the toolchain that
# actually builds. `no_std` / `wasm` escape this only because their two
# targets are listed in `rust-toolchain.toml`. Running `rustup target
# add` from inside the checkout resolves that file first, so the target
- # lands on 1.96.0 — the identical one-liner `.gitlab-ci.yml` runs, which
+ # lands on that toolchain — the identical one-liner `.gitlab-ci.yml` runs, which
# keeps this job an honest rehearsal of the buildbot rather than a
# differently-configured lookalike.
- - run: rustup target add ${{ matrix.target }}
+ - run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --target ${{ matrix.target }}
# Until this job existed there was ZERO libretro coverage in GitHub
# Actions, so breakage only surfaced on libretro's own GitLab buildbot —
# where we can't push, can't re-run, and turnaround is days. Pipeline
@@ -1004,7 +1035,7 @@ jobs:
toolchain: nightly
apt: "false"
cache-key: fuzz
- - uses: taiki-e/install-action@v2.87.21
+ - uses: taiki-e/install-action@v2.87.26
with:
tool: cargo-fuzz
- name: Build and run every fuzz target
@@ -1068,7 +1099,7 @@ jobs:
with:
components: llvm-tools-preview
cache-key: coverage
- - uses: taiki-e/install-action@v2.87.21
+ - uses: taiki-e/install-action@v2.87.26
with:
tool: cargo-llvm-cov
- run: cargo llvm-cov --workspace --lib --summary-only --fail-under-lines 70
diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml
index 8f2afd88..668cf836 100644
--- a/.github/workflows/ios.yml
+++ b/.github/workflows/ios.yml
@@ -129,7 +129,7 @@ jobs:
echo "::warning title=Xcode 26 not on runner::Building with $(xcodebuild -version | head -1). App Store uploads require Xcode 26 / the iOS 26 SDK from 2026-04-28 (App Store submission floor); ensure the release runner image has Xcode 26 before the v2.1.0 store launch."
fi
- # The project toolchain (rust-toolchain.toml = 1.96) plus the iOS targets:
+ # The project toolchain (rust-toolchain.toml = 1.99) plus the iOS targets:
# the device ABI + both simulator ABIs (the sim slice is lipo'd from the
# Apple-silicon + Intel sim builds).
- name: Install iOS Rust targets
diff --git a/.github/workflows/pgo.yml b/.github/workflows/pgo.yml
index 679ec3db..e70a5545 100644
--- a/.github/workflows/pgo.yml
+++ b/.github/workflows/pgo.yml
@@ -108,7 +108,7 @@ jobs:
with:
persist-credentials: false
- # Pinned to the project toolchain (rust-toolchain.toml = 1.96.0), matching
+ # Pinned to the project toolchain (rust-toolchain.toml = 1.99.0), matching
# the other jobs. PGO needs the llvm-tools-preview component (profdata
# merge) and the wgpu/winit/cpal apt deps (the optimized rebuild links the
# full `rustynes-frontend` binary).
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4a6e8d05..0167b2ed 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -77,7 +77,10 @@ jobs:
# a shrinking cohort, the release matrix ships aarch64 macOS only;
# Intel-Mac users build from source (`cargo build --release -p
# rustynes-frontend`). See docs/adr/0009-drop-x86_64-darwin-release-target.md.
- - os: macos-14
+ # v3.0.1: macos-14 is deprecated (runner-images#13518: brownouts
+ # from 2026-10-05, unsupported from 2026-11-02); macos-15 is the
+ # next Apple-silicon image, still aarch64.
+ - os: macos-15
target: aarch64-apple-darwin
archive_ext: tar.gz
bin_name: rustynes
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index 495dd9a8..42dc496c 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -76,7 +76,7 @@ jobs:
# Reason 2 was an ACTIVE constraint until the v1.3.0 pin bump (1.86 vs
# cargo-audit's 1.88); it is slack today and can bind again after an MSRV
# change in either direction, so it is kept rather than deleted as history.
- - uses: taiki-e/install-action@v2.87.21
+ - uses: taiki-e/install-action@v2.87.26
with:
tool: cargo-audit
- run: cargo audit
@@ -92,7 +92,7 @@ jobs:
# Prebuilt binary for the same two reasons as the audit job above: it is
# much faster than compiling cargo-deny, and it keeps the security gate
# independent of this repo's rustc pin. Policy lives in `deny.toml`.
- - uses: taiki-e/install-action@v2.87.21
+ - uses: taiki-e/install-action@v2.87.26
with:
tool: cargo-deny
- run: cargo deny check
diff --git a/.github/workflows/toolchain-canary.yml b/.github/workflows/toolchain-canary.yml
index 0072364a..8fe9ad52 100644
--- a/.github/workflows/toolchain-canary.yml
+++ b/.github/workflows/toolchain-canary.yml
@@ -2,11 +2,12 @@ name: Toolchain canary
# Early warning for toolchain drift, NOT a gate.
#
-# The project pins Rust 1.96 in rust-toolchain.toml and will stay there until
-# the libretro buildbot image stops passing `-C ar`, which breaks the Apple jobs
-# on 1.97 and later (maintainer decision, 2026-09-28). A pin that holds for
-# months hides every new lint and every deprecation until the day it moves, and
-# then they all arrive at once. This workflow builds, lints and tests the
+# The project pins a Rust release in rust-toolchain.toml (1.99.0 since v3.0.1;
+# 1.96.0 before it, held from 2026-09-28 because the libretro buildbot image
+# passes `-C ar`, a hard error from 1.97; the buildbot alone stays on 1.96.0).
+# A pin that holds for months hides every new lint and every deprecation until
+# the day it moves, and then they all arrive at once: the move to 1.99 brought
+# 70-odd new clippy findings in one go. This workflow builds, lints and tests the
# workspace on the current stable and beta once a week, so the cost of moving
# the pin is visible while it is still small.
#
diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml
index 4dca4a80..c83f4492 100644
--- a/.github/workflows/web.yml
+++ b/.github/workflows/web.yml
@@ -19,7 +19,9 @@ name: Deploy Pages (demo + docs)
# does NOT) so the size budget gates PRs without publishing a preview.
#
# trunk auto-downloads the wasm-bindgen CLI + wasm-opt versions pinned in
-# crates/rustynes-frontend/web/Trunk.toml.
+# crates/rustynes-frontend/web/Trunk.toml. (Until v3.0.1 only wasm-bindgen was
+# pinned there, and this line overstated it: wasm-opt fell back to trunk's
+# built-in default.)
on:
push:
@@ -150,7 +152,7 @@ jobs:
- name: Set up Python for MkDocs
uses: actions/setup-python@v7
with:
- python-version: "3.12"
+ python-version: "3.14"
- name: Install the social-card image libraries (Cairo + Pango)
# Required by the mkdocs-material `social` plugin's PNG renderer.
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7cad53ad..30be17ec 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -70,12 +70,26 @@
# per-package `panic`, hence the profile environment variable; every job
# here extends `.core-defs`, and GitLab merges `variables` across `extends`.
CARGO_PROFILE_RELEASE_PANIC: "unwind"
+ # v3.0.1: the buildbot stays on Rust 1.96.0 while the workspace moves on.
+ # `rust-toolchain.toml` pins 1.99.0 for development and GitHub CI, but the
+ # libretro build image injects `-Car=...` into every Apple job, and `-C ar`
+ # became a hard error in Rust 1.97 ("this option has been removed"). The
+ # environment variable outranks `rust-toolchain.toml`, so every `cargo`
+ # here runs 1.96.0, and each job installs that toolchain with its target
+ # (the line that replaced `rustup target add`). The seven crates this
+ # core compiles declare `rust-version = "1.96"`, and GitHub CI builds
+ # `rustynes-libretro` on 1.96.0 (`libretro-cross`), so the floor this pin
+ # relies on is checked on every PR rather than discovered on the buildbot.
+ # The `-C ar` fix that would let this pin go is recorded in
+ # `rust-toolchain.toml`; it is not taken blind because no pipeline here can
+ # be run before `main` moves.
+ RUSTUP_TOOLCHAIN: "1.96.0"
# Shared `before_script` for the jobs whose upstream template defines none
# (Windows, Linux, and all four Android). See the post-mortem above.
.rust-target-install:
before_script:
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
include:
################################## DESKTOPS ################################
@@ -143,7 +157,7 @@ libretro-build-windows-i686:
RUST_TARGET: i686-pc-windows-gnu
before_script:
- !reference [.libretro-rust-windows-i686, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# Linux 64-bit
# `RUST_TARGET` is the host triple here, so the `rustup target add` is a
@@ -169,7 +183,7 @@ libretro-build-linux-i686:
RUST_TARGET: i686-unknown-linux-gnu
before_script:
- !reference [.libretro-rust-linux-i686, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# Linux aarch64
# Cross-compiled from the same image as the x64 job. This job used to install
@@ -186,7 +200,7 @@ libretro-build-linux-aarch64:
RUST_TARGET: aarch64-unknown-linux-gnu
before_script:
- !reference [.libretro-rust-linux-aarch64, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# macOS 64-bit
#
@@ -209,7 +223,7 @@ libretro-build-osx-x64:
before_script:
- !reference [.libretro-rust-osx-x86_64-default, before_script]
- export SDKROOT=$OSX_SDKROOT
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# macOS ARM 64-bit
libretro-build-osx-arm64:
@@ -221,7 +235,7 @@ libretro-build-osx-arm64:
before_script:
- !reference [.libretro-rust-osx-arm64-default, before_script]
- export SDKROOT=$OSX_SDKROOT
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
################################### CELLULAR #################################
# Android ARMv7a
@@ -272,7 +286,7 @@ libretro-build-ios-arm64:
RUST_TARGET: aarch64-apple-ios
before_script:
- !reference [.libretro-rust-ios-arm64-default, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# tvOS
#
@@ -337,7 +351,7 @@ libretro-build-tvos-arm64:
RUST_TARGET: aarch64-apple-tvos
before_script:
- !reference [.libretro-rust-tvos-arm64-default, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
script:
- !reference [.libretro-rust-apple-base, script]
@@ -356,7 +370,7 @@ libretro-build-webos-armv7a:
RUST_TARGET: armv7-unknown-linux-gnueabi
before_script:
- !reference [.libretro-rust-webos, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
# webOS aarch64
# The image points bindgen at the SDK sysroot for armv7 only
@@ -379,4 +393,4 @@ libretro-build-webos-aarch64:
BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_gnu: "--sysroot=/developer/aarch64-webos-linux-gnu_sdk-buildroot/aarch64-webos-linux-gnu/sysroot"
before_script:
- !reference [.libretro-rust-webos, before_script]
- - rustup target add ${RUST_TARGET}
+ - rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5218580c..25cef413 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -70,7 +70,7 @@ repos:
# here, like every other hook, so a new ruff release cannot redden unchanged
# scripts.
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.16.9
+ rev: v0.16.10
hooks:
- id: ruff-check
diff --git a/AGENTS.md b/AGENTS.md
index 092e93a1..6b1a642e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -152,7 +152,7 @@ cargo bench -p rustynes-mappers
cargo bench -p rustynes-core
```
-Toolchain is **Rust 1.96** pinned in `rust-toolchain.toml` (bumped from 1.86 in v1.3.0 to unblock the edition-2024 + egui 0.34 / wgpu 29 / rfd 0.17 dependency tier). CI runs the test job on stable across Linux/macOS/Windows plus an MSRV pin at 1.96 on Linux.
+Toolchain is **Rust 1.99** pinned in `rust-toolchain.toml` (1.86 until v1.3.0, 1.96 until v3.0.1). **The libretro buildbot stays on 1.96.0** (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`): its image passes `-C ar`, which Rust 1.97 made a hard error, so the seven crates it builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`) declare `rust-version = "1.96"` and CI's `libretro-cross` job builds them on 1.96.0. **Clippy on 1.99 will suggest rewrites those crates cannot use**: it rewrote a `Box<[u8; N]>` loop that only compiles on 1.97+, and only the 1.96 build caught it. CI runs the tests on the pinned toolchain across Linux/macOS/Windows.
On Linux, anything that pulls in `rustynes-frontend` (which `cargo test --workspace` does) needs the wgpu/winit/cpal system deps:
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index b8712c86..30850292 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -53,7 +53,7 @@ These cross-cutting choices span many files and are not negotiable without re-de
```text
rustynes/
-├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.96)
+├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.99; libretro path 1.96)
├── crates/
│ ├── rustynes-core/ # Glue: Nes struct, run loop, scheduler, Bus,
│ │ # save state, region config. Re-exports chip crates.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7485c312..0af1ee8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,26 @@ cycle-accurate core later replaced.
### Changed
+- **Rust 1.99.** The pinned toolchain moves from 1.96.0 to 1.99.0, the newest
+ stable. The libretro buildbot stays on 1.96.0 (`RUSTUP_TOOLCHAIN` in
+ `.gitlab-ci.yml`), because its build image passes `-C ar`, a hard error from
+ Rust 1.97; the seven crates it builds keep `rust-version = "1.96"`, and CI's
+ `libretro-cross` job builds them on 1.96.0. That check caught a clippy 1.99
+ rewrite in `rustynes-core` that only 1.97+ accepts. Rust 1.99's new
+ `extern "C"` variadic definitions do not simplify the RetroAchievements
+ bridge: none of the rcheevos functions it calls is variadic. About 70 new
+ clippy findings were fixed, all exact rewrites (`as_chunks`, `fill`,
+ `assert_eq!` against an empty value so a failure shows the contents).
+- **Every dependency at its newest release.** Crates (`cargo update`; the only
+ holds are forced upstream: `getrandom` 0.2/0.3 by `piccolo` 0.3.3, and
+ `generic-array` 0.14.7 by `crypto-common` 0.1.7). GitHub Actions:
+ `taiki-e/install-action` 2.87.26 (supersedes Dependabot's PR for 2.87.22)
+ and `dtolnay/rust-toolchain` at its current `v1`. Android: `cargo-ndk` 4,
+ NDK r30, Gradle run on Temurin 25, `org.json` 20260814. Web: wasm-opt is
+ now pinned (`version_133`; it was unpinned, so trunk used its built-in
+ `version_123`). Docs build on Python 3.14; Docker images on Rust 1.99 and
+ Debian 13 (the signaling image still named Rust 1.86); `ruff` 0.16.10.
+ macOS jobs move from the deprecated `macos-14` image to `macos-15`.
- **`EMULATION_EPOCH` is 2.** The mapper 45 fix changes what *Famicom Yarou
Vol.1* produces, so movies recorded and netplay peers running v3.0.0 are
refused, naming both epochs (ADR 0045).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0165556e..95c2ad02 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,7 +50,7 @@ Areas where help is especially valued:
### Prerequisites
-- **Rust 1.96** (pinned in `rust-toolchain.toml`; `rustup` auto-installs it, including the `wasm32-unknown-unknown` and `thumbv7em-none-eabihf` targets).
+- **Rust 1.99** (pinned in `rust-toolchain.toml`; `rustup` auto-installs it, including the `wasm32-unknown-unknown` and `thumbv7em-none-eabihf` targets).
- **Git**.
- **System libraries** for the `winit` + `wgpu` + `cpal` frontend.
@@ -134,7 +134,7 @@ forbids, so a flaky test is a bug to fix, not to retry.
- **Format:** `cargo fmt` (rustfmt defaults).
- **Lint:** pass `cargo clippy --workspace --all-targets -- -D warnings` with no warnings.
-- **Edition:** Rust 2024. **MSRV:** 1.96 (pinned in `rust-toolchain.toml`).
+- **Edition:** Rust 2024. **Toolchain:** 1.99 (pinned in `rust-toolchain.toml`). **MSRV:** 1.99, except the seven crates the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which keep 1.96 for the libretro buildbot; CI checks them on 1.96.
- The chip stack (`rustynes-{cpu,ppu,apu,mappers,core}`) is `#![no_std]` + `extern crate alloc;`. `unsafe` is only permitted at FFI boundaries (`rustynes-cheevos`) and the one native priority hook in `rustynes-frontend`, and **must** carry a `// SAFETY:` comment explaining the invariant.
- No emojis in code, comments, or commits (project policy).
diff --git a/Cargo.lock b/Cargo.lock
index 92b1a5fd..42bc6a8e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -224,7 +224,7 @@ dependencies = [
"clipboard-win",
"image",
"log",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-core-foundation",
"objc2-core-graphics",
@@ -460,7 +460,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
- "objc2 0.5.2",
+ "objc2 0.5.3",
]
[[package]]
@@ -469,7 +469,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
- "objc2 0.6.4",
+ "objc2 0.6.5",
]
[[package]]
@@ -608,9 +608,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.5.1"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
+checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -940,7 +940,7 @@ dependencies = [
"ndk-context",
"num-derive",
"num-traits",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-audio-toolbox",
"objc2-avf-audio",
"objc2-core-audio",
@@ -1256,7 +1256,7 @@ dependencies = [
"bitflags 2.13.2",
"block2 0.6.2",
"libc",
- "objc2 0.6.4",
+ "objc2 0.6.5",
]
[[package]]
@@ -1355,7 +1355,7 @@ dependencies = [
"bytemuck",
"egui",
"log",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-foundation 0.3.2",
"objc2-ui-kit 0.3.2",
"profiling",
@@ -1367,9 +1367,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.18.0"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
[[package]]
name = "emath"
@@ -1561,9 +1561,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "font-types"
-version = "0.12.5"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6"
+checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274"
dependencies = [
"bytemuck",
]
@@ -1606,9 +1606,9 @@ dependencies = [
[[package]]
name = "fs-err"
-version = "3.3.1"
+version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
+checksum = "b5c95b673b8f6f7235229ae11c5642d81b04c2e64c1e2fb417bc0cf73ca45f29"
dependencies = [
"autocfg",
]
@@ -2145,9 +2145,9 @@ dependencies = [
[[package]]
name = "insta"
-version = "1.48.0"
+version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82"
+checksum = "b67d3d2e287e4b86c10b3f3b641033d1f89b74bdb39d05f34952e2b9a6fe21cd"
dependencies = [
"console",
"once_cell",
@@ -2334,9 +2334,9 @@ checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
[[package]]
name = "lazy_static"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
[[package]]
name = "lazycell"
@@ -2357,9 +2357,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.189"
+version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "libloading"
@@ -2500,12 +2500,12 @@ checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226"
[[package]]
name = "mac_address"
-version = "1.1.8"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+checksum = "b367b50a9be9a5d3b5718c1da74e5d6b9c17647f89752ee2562a470cc3c4eb1a"
dependencies = [
- "nix 0.29.0",
- "winapi",
+ "nix 0.30.1",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2582,9 +2582,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.2.3"
+version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
+checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
dependencies = [
"libc",
"log",
@@ -2594,9 +2594,9 @@ dependencies = [
[[package]]
name = "mlua"
-version = "0.12.1"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d96e5d00f19d8c46c71ceaced99593b90c31c57aa1fe2cb3e93a8b1698eedba9"
+checksum = "7a7ae6b65bf54b7fdd4cf5b6f44f60862350ebf3fe3779b3a9cc005e7fa14f81"
dependencies = [
"bstr",
"either",
@@ -2609,9 +2609,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
-version = "0.12.0"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b806d7ade031f5d6607eae3e283fb034cb795a76247dd0d1ba753c8c42debccf"
+checksum = "1631cda17bd6486410ab751d91f5fd4484f441589278cdad879eb3afc721a285"
dependencies = [
"cc",
"cfg-if",
@@ -2704,6 +2704,18 @@ name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.13.2",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.13.2",
"cfg-if",
@@ -2824,9 +2836,9 @@ checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]]
name = "objc2"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
+checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b"
dependencies = [
"objc-sys",
"objc2-encode",
@@ -2834,9 +2846,9 @@ dependencies = [
[[package]]
name = "objc2"
-version = "0.6.4"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
+checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
dependencies = [
"objc2-encode",
]
@@ -2850,7 +2862,7 @@ dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
"libc",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-core-data",
"objc2-core-image",
"objc2-foundation 0.2.2",
@@ -2865,7 +2877,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.13.2",
"block2 0.6.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
]
@@ -2878,7 +2890,7 @@ checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [
"bitflags 2.13.2",
"libc",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
@@ -2892,7 +2904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"bitflags 2.13.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-foundation 0.3.2",
]
@@ -2904,7 +2916,7 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-core-location",
"objc2-foundation 0.2.2",
]
@@ -2916,7 +2928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
]
@@ -2927,7 +2939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
dependencies = [
"dispatch2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
@@ -2940,7 +2952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags 2.13.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
]
[[package]]
@@ -2951,7 +2963,7 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
]
@@ -2965,7 +2977,7 @@ dependencies = [
"block2 0.6.2",
"dispatch2",
"libc",
- "objc2 0.6.4",
+ "objc2 0.6.5",
]
[[package]]
@@ -2976,7 +2988,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.13.2",
"dispatch2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
"objc2-io-surface",
]
@@ -2988,7 +3000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
"objc2-metal 0.2.2",
]
@@ -3000,7 +3012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-contacts",
"objc2-foundation 0.2.2",
]
@@ -3021,7 +3033,7 @@ dependencies = [
"block2 0.5.1",
"dispatch",
"libc",
- "objc2 0.5.2",
+ "objc2 0.5.3",
]
[[package]]
@@ -3033,7 +3045,7 @@ dependencies = [
"bitflags 2.13.2",
"block2 0.6.2",
"libc",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
]
@@ -3055,7 +3067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.13.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
]
@@ -3066,7 +3078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
]
@@ -3079,7 +3091,7 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
]
@@ -3091,7 +3103,7 @@ checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
dependencies = [
"bitflags 2.13.2",
"block2 0.6.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-foundation 0.3.2",
]
@@ -3103,7 +3115,7 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
"objc2-metal 0.2.2",
]
@@ -3115,7 +3127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.13.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
@@ -3128,7 +3140,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
]
@@ -3140,7 +3152,7 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-image",
@@ -3160,7 +3172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags 2.13.2",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
]
@@ -3172,7 +3184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-foundation 0.2.2",
]
@@ -3184,7 +3196,7 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [
"bitflags 2.13.2",
"block2 0.5.1",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-core-location",
"objc2-foundation 0.2.2",
]
@@ -3599,9 +3611,9 @@ dependencies = [
[[package]]
name = "powerfmt"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
[[package]]
name = "ppv-lite86"
@@ -3932,7 +3944,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
dependencies = [
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
"objc2-quartz-core 0.3.2",
@@ -4061,7 +4073,7 @@ dependencies = [
"js-sys",
"libc",
"log",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
@@ -5247,9 +5259,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
-version = "1.53.1"
+version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [
"bytes",
"libc",
@@ -5640,9 +5652,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.26.1"
+version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
+checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"atomic",
"getrandom 0.4.3",
@@ -5932,7 +5944,7 @@ dependencies = [
"jni",
"log",
"ndk-context",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
"url",
@@ -6172,7 +6184,7 @@ dependencies = [
"naga",
"naga-types",
"ndk-sys",
- "objc2 0.6.4",
+ "objc2 0.6.5",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
@@ -6587,7 +6599,7 @@ dependencies = [
"libc",
"memmap2",
"ndk",
- "objc2 0.5.2",
+ "objc2 0.5.3",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
"objc2-ui-kit 0.2.2",
@@ -6731,18 +6743,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.59"
+version = "0.8.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb"
+checksum = "6400fc4bb7426f6faac3fb07566122bb18622e3022bd2c36426a92ed489b1dbe"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.59"
+version = "0.8.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82"
+checksum = "99c27cc7525bad4df59a45e073c45b2c2aa820efb588234cc23a547ec69a34db"
dependencies = [
"proc-macro2",
"quote",
@@ -6772,9 +6784,9 @@ dependencies = [
[[package]]
name = "zeroize"
-version = "1.9.0"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
[[package]]
name = "zerotrie"
diff --git a/Cargo.toml b/Cargo.toml
index 7c74c105..4954b83f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -79,7 +79,11 @@ default-members = ["crates/rustynes-libretro"]
# bearing, not decoration.
version = "3.0.0"
edition = "2024"
-rust-version = "1.96"
+# The toolchain is 1.99 (rust-toolchain.toml). The seven crates the libretro
+# core compiles override this with "1.96", because the libretro buildbot stays
+# on 1.96.0 (see `.gitlab-ci.yml`); CI builds `rustynes-libretro` on 1.96 so
+# that floor is checked, not just declared.
+rust-version = "1.99"
license = "GPL-3.0-or-later"
authors = ["DoubleGate "]
repository = "https://github.com/doublegate/RustyNES"
diff --git a/README.md b/README.md
index ed663da2..d76e8e2f 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-

+


@@ -109,7 +109,7 @@ Launch it without a ROM and use **F12**, the File menu, or drag and drop a
### Build from source
-You need **Rust 1.96** (pinned in `rust-toolchain.toml`; [rustup](https://rustup.rs)
+You need **Rust 1.99** (pinned in `rust-toolchain.toml`; [rustup](https://rustup.rs)
installs it) and Git.
```bash
diff --git a/SUPPORT.md b/SUPPORT.md
index da60b8e9..7609d599 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -140,13 +140,13 @@ cargo build --release --workspace
**Q: What are the prerequisites?**
-A: Rust 1.96 (pinned in `rust-toolchain.toml`; `rustup` auto-installs it) and the `winit` + `wgpu` + `cpal` system libraries (libxkbcommon / wayland / alsa / udev on Linux; nothing extra on macOS/Windows). See [docs/dev/BUILD.md](docs/dev/BUILD.md) for platform-specific instructions.
+A: Rust 1.99 (pinned in `rust-toolchain.toml`; `rustup` auto-installs it) and the `winit` + `wgpu` + `cpal` system libraries (libxkbcommon / wayland / alsa / udev on Linux; nothing extra on macOS/Windows). See [docs/dev/BUILD.md](docs/dev/BUILD.md) for platform-specific instructions.
**Q: Build is failing, what do I do?**
A:
-1. Ensure you have Rust 1.96 or newer: `rustc --version`
+1. Ensure you have Rust 1.99 or newer: `rustc --version`
2. Install the frontend system libraries (see [BUILD.md](docs/dev/BUILD.md))
3. Try a clean build: `cargo clean && cargo build --workspace`
4. Check [GitHub Issues](https://github.com/doublegate/RustyNES/issues) for known build problems
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 5d01ec87..0d63d739 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -425,7 +425,7 @@ dependencies {
// a stub, and with `isReturnDefaultValues` it returns null / 0 instead of
// throwing, so a test of the JSON stores (game_config.json, library.json)
// would pass or fail on stub defaults, not on the code.
- testImplementation("org.json:json:20250517")
+ testImplementation("org.json:json:20260814")
// v2.0.1 (ADR 0025): the optional Google Play services below are PLAY-FLAVOR ONLY.
// `playImplementation` keeps these proprietary Google-Play SDKs out of the `foss`
// (F-Droid/sideload) artifact entirely — the `foss` variant links none of them (its
diff --git a/crates/rustynes-apu/Cargo.toml b/crates/rustynes-apu/Cargo.toml
index ad1c9df4..2079e459 100644
--- a/crates/rustynes-apu/Cargo.toml
+++ b/crates/rustynes-apu/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-apu"
description = "Cycle-accurate Ricoh 2A03 APU (Audio Processing Unit) for the NES"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-apu/src/blip.rs b/crates/rustynes-apu/src/blip.rs
index a2f5e63f..aece2fa4 100644
--- a/crates/rustynes-apu/src/blip.rs
+++ b/crates/rustynes-apu/src/blip.rs
@@ -444,7 +444,7 @@ mod tests {
let _ = b.drain_all();
let kept = b.samples.capacity();
let empty = b.drain_all();
- assert!(empty.is_empty());
+ assert_eq!(empty, [] as [f32; 0]);
assert_eq!(empty.capacity(), 0, "no buffer handed out for nothing");
assert_eq!(b.samples.capacity(), kept, "the kept buffer stays");
}
diff --git a/crates/rustynes-apu/src/lib.rs b/crates/rustynes-apu/src/lib.rs
index 269925e8..35c77c50 100644
--- a/crates/rustynes-apu/src/lib.rs
+++ b/crates/rustynes-apu/src/lib.rs
@@ -95,6 +95,6 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
}
diff --git a/crates/rustynes-cheevos/Cargo.toml b/crates/rustynes-cheevos/Cargo.toml
index 956f5e45..f886d11e 100644
--- a/crates/rustynes-cheevos/Cargo.toml
+++ b/crates/rustynes-cheevos/Cargo.toml
@@ -2,7 +2,7 @@
name = "rustynes-cheevos"
version.workspace = true
edition = "2024"
-rust-version = "1.96"
+rust-version.workspace = true
license = "GPL-3.0-or-later"
authors = ["Parobek "]
repository.workspace = true
diff --git a/crates/rustynes-cheevos/src/lib.rs b/crates/rustynes-cheevos/src/lib.rs
index 4e182b65..cfbd09ce 100644
--- a/crates/rustynes-cheevos/src/lib.rs
+++ b/crates/rustynes-cheevos/src/lib.rs
@@ -88,8 +88,8 @@ mod smoke {
);
// With no game loaded these are empty/zero but must not crash.
- assert!(client.achievement_list().is_empty());
- assert!(client.leaderboard_list().is_empty());
+ assert_eq!(client.achievement_list(), [] as [client::RaAchievement; 0]);
+ assert_eq!(client.leaderboard_list(), [] as [client::RaLeaderboard; 0]);
assert_eq!(client.user_game_summary(), RaGameSummary::default());
assert!(client.user_info().is_none());
let _ = client.rich_presence();
diff --git a/crates/rustynes-core/Cargo.toml b/crates/rustynes-core/Cargo.toml
index 1039df0a..644ceebe 100644
--- a/crates/rustynes-core/Cargo.toml
+++ b/crates/rustynes-core/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-core"
description = "Cycle-accurate NES emulator core: scheduler, bus, save state, public Nes facade"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-core/benches/snapshot_restore.rs b/crates/rustynes-core/benches/snapshot_restore.rs
index 5db1bc3b..ef57c51b 100644
--- a/crates/rustynes-core/benches/snapshot_restore.rs
+++ b/crates/rustynes-core/benches/snapshot_restore.rs
@@ -223,7 +223,7 @@ fn flash_board_rom(mapper: u8, battery: bool) -> Vec {
rom[5] = 0; // CHR-RAM
rom[6] = ((mapper & 0x0F) << 4) | if battery { 0x02 } else { 0 };
rom[7] = mapper & 0xF0;
- for bank in rom[16..].chunks_exact_mut(0x4000) {
+ for bank in rom[16..].as_chunks_mut::<0x4000>().0 {
bank[0..3].copy_from_slice(&[0x4C, 0x00, 0xC0]); // JMP $C000
bank[0x3FFA..0x4000].copy_from_slice(&[0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0]);
}
diff --git a/crates/rustynes-core/src/bus.rs b/crates/rustynes-core/src/bus.rs
index 657b3e83..5a68b765 100644
--- a/crates/rustynes-core/src/bus.rs
+++ b/crates/rustynes-core/src/bus.rs
@@ -1260,7 +1260,10 @@ impl SystemBus {
// Byte 3 (bits 24-31) — extracted without a truncating cast.
s.to_le_bytes()[3]
};
- for byte in self.ram.iter_mut() {
+ // `&mut *self.ram`, not `&mut self.ram`: `&mut Box<[T; N]>` only
+ // became iterable after Rust 1.96, and this crate builds on 1.96 for
+ // the libretro buildbot. Reborrowing the array works on both.
+ for byte in &mut *self.ram {
*byte = next();
}
self.open_bus = next();
diff --git a/crates/rustynes-core/src/cpu_boot_trace.rs b/crates/rustynes-core/src/cpu_boot_trace.rs
index c00a787f..2b5703d3 100644
--- a/crates/rustynes-core/src/cpu_boot_trace.rs
+++ b/crates/rustynes-core/src/cpu_boot_trace.rs
@@ -419,7 +419,7 @@ impl CpuBootTrace {
}
let n = body.len() / RECORD_SIZE;
let mut records = Vec::with_capacity(n);
- for chunk in body.chunks_exact(RECORD_SIZE) {
+ for chunk in body.as_chunks::().0 {
let rec = CpuBootRecord::from_bytes(chunk)
.ok_or_else(|| String::from("CpuBootRecord::from_bytes returned None"))?;
records.push(rec);
diff --git a/crates/rustynes-core/src/hardware_options.rs b/crates/rustynes-core/src/hardware_options.rs
index e5244efb..4b3bfd37 100644
--- a/crates/rustynes-core/src/hardware_options.rs
+++ b/crates/rustynes-core/src/hardware_options.rs
@@ -867,7 +867,7 @@ mod tests {
#[test]
fn differences_name_each_differing_option() {
let a = HardwareOptions::default();
- assert!(a.differences(&a).is_empty());
+ assert_eq!(a.differences(&a), [] as [&str; 0]);
let b = HardwareOptions {
oam_decay: true,
console_model: ConsoleModel::Famicom,
diff --git a/crates/rustynes-core/src/lib.rs b/crates/rustynes-core/src/lib.rs
index f57c6d6c..11aa9bbc 100644
--- a/crates/rustynes-core/src/lib.rs
+++ b/crates/rustynes-core/src/lib.rs
@@ -218,6 +218,6 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
}
diff --git a/crates/rustynes-core/src/movie.rs b/crates/rustynes-core/src/movie.rs
index dfee1cc2..d368fea7 100644
--- a/crates/rustynes-core/src/movie.rs
+++ b/crates/rustynes-core/src/movie.rs
@@ -369,12 +369,10 @@ impl RollingHash {
/// zero-padded, which is unambiguous here because every input is a
/// fixed-size framebuffer.
fn write(&mut self, bytes: &[u8]) {
- let mut chunks = bytes.chunks_exact(8);
- for c in &mut chunks {
- let w = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]);
- self.0 = (self.0 ^ w).wrapping_mul(Self::PRIME);
+ let (words, rem) = bytes.as_chunks::<8>();
+ for w in words {
+ self.0 = (self.0 ^ u64::from_le_bytes(*w)).wrapping_mul(Self::PRIME);
}
- let rem = chunks.remainder();
if !rem.is_empty() {
let mut buf = [0u8; 8];
buf[..rem.len()].copy_from_slice(rem);
diff --git a/crates/rustynes-core/src/movie_interop.rs b/crates/rustynes-core/src/movie_interop.rs
index 2484547f..f6c65b47 100644
--- a/crates/rustynes-core/src/movie_interop.rs
+++ b/crates/rustynes-core/src/movie_interop.rs
@@ -373,7 +373,7 @@ pub fn export_fm2(movie: &Movie, opts: &Fm2ExportOpts) -> Result"]
repository = "https://github.com/doublegate/RustyNES"
diff --git a/crates/rustynes-cosim/src/checkpoint.rs b/crates/rustynes-cosim/src/checkpoint.rs
index 2245bead..b811d41f 100644
--- a/crates/rustynes-cosim/src/checkpoint.rs
+++ b/crates/rustynes-cosim/src/checkpoint.rs
@@ -303,8 +303,10 @@ pub fn observables_from_bytes(bytes: &[u8]) -> Result, &'static
return Err("observable stream length is not a multiple of 16 bytes");
}
bytes
- .chunks_exact(ENCODED_LEN)
- .map(Observable::decode)
+ .as_chunks::()
+ .0
+ .iter()
+ .map(|c| Observable::decode(c))
.collect()
}
@@ -649,7 +651,9 @@ pub fn from_bytes(bytes: &[u8]) -> Result, &'static str> {
return Err("checkpoint stream length is not a multiple of 16 bytes");
}
Ok(bytes
- .chunks_exact(16)
+ .as_chunks::<16>()
+ .0
+ .iter()
.map(|c| Checkpoint {
through_cycle: u64::from_le_bytes(c[0..8].try_into().expect("8 bytes")),
hash: u64::from_le_bytes(c[8..16].try_into().expect("8 bytes")),
diff --git a/crates/rustynes-cosim/src/lib.rs b/crates/rustynes-cosim/src/lib.rs
index cf9726e1..7ce5cb21 100644
--- a/crates/rustynes-cosim/src/lib.rs
+++ b/crates/rustynes-cosim/src/lib.rs
@@ -1081,7 +1081,7 @@ mod tests {
.take_irq_artifacts(checkpoint::DEFAULT_INTERVAL)
.expect("armed");
assert!(a.csv.starts_with("cpu_cycle,"));
- assert!(!a.checkpoints.expect("no overflow").is_empty());
+ assert_ne!(a.checkpoints.expect("no overflow").len(), 0);
}
/// The `CpuBootTrace` wire format, pinned to a literal.
diff --git a/crates/rustynes-cpu/Cargo.toml b/crates/rustynes-cpu/Cargo.toml
index dbd11541..d6526195 100644
--- a/crates/rustynes-cpu/Cargo.toml
+++ b/crates/rustynes-cpu/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-cpu"
description = "Cycle-accurate Ricoh 2A03 (6502 derivative) CPU core for the NES"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-cpu/src/lib.rs b/crates/rustynes-cpu/src/lib.rs
index 8b71a63c..942a9d24 100644
--- a/crates/rustynes-cpu/src/lib.rs
+++ b/crates/rustynes-cpu/src/lib.rs
@@ -52,6 +52,6 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
}
diff --git a/crates/rustynes-frontend/src/about_fx.rs b/crates/rustynes-frontend/src/about_fx.rs
index 3956d5ff..3c540ce0 100644
--- a/crates/rustynes-frontend/src/about_fx.rs
+++ b/crates/rustynes-frontend/src/about_fx.rs
@@ -135,7 +135,9 @@ fn decode_png(bytes: &[u8]) -> Option<(Vec, usize, usize)> {
let rgba = match info.color_type {
png::ColorType::Rgba => buf,
png::ColorType::Rgb => buf
- .chunks_exact(3)
+ .as_chunks::<3>()
+ .0
+ .iter()
.flat_map(|p| [p[0], p[1], p[2], 0xFF])
.collect(),
_ => return None,
diff --git a/crates/rustynes-frontend/src/audio.rs b/crates/rustynes-frontend/src/audio.rs
index 2ba09e3f..b6015cbc 100644
--- a/crates/rustynes-frontend/src/audio.rs
+++ b/crates/rustynes-frontend/src/audio.rs
@@ -1323,7 +1323,7 @@ fn fill>(
let v = match c {
0 => l,
1 => r,
- _ => 0.5 * (l + r),
+ _ => f32::midpoint(l, r),
};
data[out_idx] = S::from_sample(v);
}
diff --git a/crates/rustynes-frontend/src/cheats.rs b/crates/rustynes-frontend/src/cheats.rs
index 171631ca..5c235792 100644
--- a/crates/rustynes-frontend/src/cheats.rs
+++ b/crates/rustynes-frontend/src/cheats.rs
@@ -194,8 +194,8 @@ mod tests {
fn load_missing_file_is_empty() {
let tmp = TempDir::new().unwrap();
let back = load(tmp.path(), &h(0x00));
- assert!(back.genie.is_empty());
- assert!(back.raw.is_empty());
+ assert_eq!(back.genie, [] as [crate::cheats::CheatEntry; 0]);
+ assert_eq!(back.raw, [] as [crate::cheats::RawCheat; 0]);
}
#[test]
@@ -265,8 +265,8 @@ mod tests {
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, "this is = = not toml").unwrap();
let back = load(tmp.path(), &h(0x07));
- assert!(back.genie.is_empty());
- assert!(back.raw.is_empty());
+ assert_eq!(back.genie, [] as [crate::cheats::CheatEntry; 0]);
+ assert_eq!(back.raw, [] as [crate::cheats::RawCheat; 0]);
}
/// Back-compat: a pre-v1.7.0 cheat file (Game Genie only, no `raw` key)
@@ -282,7 +282,7 @@ mod tests {
assert_eq!(back.genie.len(), 1);
assert_eq!(back.genie[0].code, "SXIOPO");
assert!(back.genie[0].enabled);
- assert!(back.raw.is_empty());
+ assert_eq!(back.raw, [] as [crate::cheats::RawCheat; 0]);
}
/// A raw cheat with no `compare` key deserializes to `compare: None`.
diff --git a/crates/rustynes-frontend/src/cli.rs b/crates/rustynes-frontend/src/cli.rs
index 1fb303cb..a990f1a5 100644
--- a/crates/rustynes-frontend/src/cli.rs
+++ b/crates/rustynes-frontend/src/cli.rs
@@ -540,7 +540,7 @@ mod tests {
"topic id must be lowercase"
);
assert!(seen.insert(t.id), "duplicate topic id: {}", t.id);
- assert!(!t.title.is_empty());
+ assert_ne!(t.title, "");
assert!(!t.body.is_empty(), "empty body for topic {}", t.id);
}
}
diff --git a/crates/rustynes-frontend/src/config.rs b/crates/rustynes-frontend/src/config.rs
index a990f209..8c30e4c8 100644
--- a/crates/rustynes-frontend/src/config.rs
+++ b/crates/rustynes-frontend/src/config.rs
@@ -1190,7 +1190,7 @@ pub fn parse_pal(bytes: &[u8]) -> Option<[[u8; 3]; 64]> {
return None;
}
let mut pal = [[0u8; 3]; 64];
- for (i, chunk) in bytes[..192].chunks_exact(3).enumerate() {
+ for (i, chunk) in bytes[..192].as_chunks::<3>().0.iter().enumerate() {
pal[i] = [chunk[0], chunk[1], chunk[2]];
}
Some(pal)
diff --git a/crates/rustynes-frontend/src/debugger/access_counter.rs b/crates/rustynes-frontend/src/debugger/access_counter.rs
index 5be73619..ad8170b1 100644
--- a/crates/rustynes-frontend/src/debugger/access_counter.rs
+++ b/crates/rustynes-frontend/src/debugger/access_counter.rs
@@ -121,7 +121,7 @@ impl MemoryAccessCounter {
/// Zero every counter + clear the uninitialized-read state (e.g. on
/// reset / power-cycle / a user "clear" click).
pub fn reset(&mut self) {
- for c in self.counters.iter_mut() {
+ for c in &mut self.counters {
*c = AddressCounters::default();
}
self.uninit_total = 0;
diff --git a/crates/rustynes-frontend/src/debugger/atlas_panel.rs b/crates/rustynes-frontend/src/debugger/atlas_panel.rs
index 2a225ee3..c736a711 100644
--- a/crates/rustynes-frontend/src/debugger/atlas_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/atlas_panel.rs
@@ -891,7 +891,7 @@ mod tests {
};
p.clear();
assert_eq!(p.take_watch_request(), None);
- assert!(p.status.is_empty());
+ assert_eq!(p.status, "");
}
/// A ROM change must discard the whole atlas. Two thousand labels describing
@@ -907,10 +907,10 @@ mod tests {
..AtlasPanel::default()
};
p.clear();
- assert!(p.labels.is_empty());
+ assert_eq!(p.labels, [] as [rustynes_probe::atlas::Label; 0]);
assert_eq!(p.frames, 0);
assert_eq!(p.selected, None);
- assert!(p.status.is_empty());
+ assert_eq!(p.status, "");
}
/// A batch must never spend trials on untouched addresses: they are the bulk
diff --git a/crates/rustynes-frontend/src/debugger/badge_cache.rs b/crates/rustynes-frontend/src/debugger/badge_cache.rs
index bd92a434..f686f9e3 100644
--- a/crates/rustynes-frontend/src/debugger/badge_cache.rs
+++ b/crates/rustynes-frontend/src/debugger/badge_cache.rs
@@ -194,11 +194,15 @@ fn decode_png(bytes: &[u8]) -> Option {
let rgba: Vec = match info.color_type {
png::ColorType::Rgba => data.to_vec(),
png::ColorType::Rgb => data
- .chunks_exact(3)
+ .as_chunks::<3>()
+ .0
+ .iter()
.flat_map(|p| [p[0], p[1], p[2], 0xFF])
.collect(),
png::ColorType::GrayscaleAlpha => data
- .chunks_exact(2)
+ .as_chunks::<2>()
+ .0
+ .iter()
.flat_map(|p| [p[0], p[0], p[0], p[1]])
.collect(),
png::ColorType::Grayscale => data.iter().flat_map(|&g| [g, g, g, 0xFF]).collect(),
diff --git a/crates/rustynes-frontend/src/debugger/callstack.rs b/crates/rustynes-frontend/src/debugger/callstack.rs
index 980feae1..d4a729d2 100644
--- a/crates/rustynes-frontend/src/debugger/callstack.rs
+++ b/crates/rustynes-frontend/src/debugger/callstack.rs
@@ -154,8 +154,8 @@ const OP_JMP_ABS: u8 = 0x4C;
const OP_JMP_IND: u8 = 0x6C;
impl CallstackTracker {
- /// The current call stack, outermost first.
- #[must_use]
+ /// The current call stack, outermost first. (No `#[must_use]`: an
+ /// iterator already carries it.)
pub fn frames(&self) -> impl ExactSizeIterator- + DoubleEndedIterator {
self.stack.iter()
}
diff --git a/crates/rustynes-frontend/src/debugger/cpu_panel.rs b/crates/rustynes-frontend/src/debugger/cpu_panel.rs
index 6d189088..519ae662 100644
--- a/crates/rustynes-frontend/src/debugger/cpu_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/cpu_panel.rs
@@ -496,7 +496,7 @@ mod tests {
status.contains("work RAM"),
"expected a work-RAM rejection, got {status:?}"
);
- assert!(pending.is_empty());
+ assert_eq!(pending, [] as [crate::emu::DebugPoke; 0]);
}
#[test]
@@ -507,6 +507,6 @@ mod tests {
status.contains("work RAM"),
"expected an overrun rejection, got {status:?}"
);
- assert!(pending.is_empty());
+ assert_eq!(pending, [] as [crate::emu::DebugPoke; 0]);
}
}
diff --git a/crates/rustynes-frontend/src/debugger/divergence_panel.rs b/crates/rustynes-frontend/src/debugger/divergence_panel.rs
index 1131cd6b..cf5605a3 100644
--- a/crates/rustynes-frontend/src/debugger/divergence_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/divergence_panel.rs
@@ -527,7 +527,7 @@ mod tests {
);
assert!(panel.audio.is_none(), "a located cycle outlived its ROM");
assert!(panel.requested.is_none(), "a queued run outlived its ROM");
- assert!(panel.status.is_empty());
+ assert_eq!(panel.status, "");
assert_eq!(panel.addr, 0, "the perturbation target is ROM-bound too");
}
}
diff --git a/crates/rustynes-frontend/src/debugger/latency_panel.rs b/crates/rustynes-frontend/src/debugger/latency_panel.rs
index eb84dad7..de9fd54f 100644
--- a/crates/rustynes-frontend/src/debugger/latency_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/latency_panel.rs
@@ -968,7 +968,7 @@ mod tests {
None,
"the previous game's run-ahead depth was still queued to apply"
);
- assert!(panel.status.is_empty());
+ assert_eq!(panel.status, "");
assert!(!panel.measure_requested);
}
diff --git a/crates/rustynes-frontend/src/debugger/memory_compare_panel.rs b/crates/rustynes-frontend/src/debugger/memory_compare_panel.rs
index a79aa58a..56bebca3 100644
--- a/crates/rustynes-frontend/src/debugger/memory_compare_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/memory_compare_panel.rs
@@ -809,7 +809,7 @@ mod tests {
#[test]
fn empty_freeze_is_empty() {
let s = MemoryComparePanelState::default();
- assert!(s.freeze_cheats().is_empty());
+ assert_eq!(s.freeze_cheats(), [] as [crate::cheats::RawCheat; 0]);
}
#[cfg(not(target_arch = "wasm32"))]
diff --git a/crates/rustynes-frontend/src/debugger/memory_panel.rs b/crates/rustynes-frontend/src/debugger/memory_panel.rs
index 4cc7d3fa..b735a9f7 100644
--- a/crates/rustynes-frontend/src/debugger/memory_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/memory_panel.rs
@@ -523,7 +523,7 @@ mod tests {
#[test]
fn empty_freeze_is_empty_cheats() {
let s = MemoryPanelState::default();
- assert!(s.freeze_cheats().is_empty());
+ assert_eq!(s.freeze_cheats(), [] as [crate::cheats::RawCheat; 0]);
assert!(!s.wants_access_log());
}
diff --git a/crates/rustynes-frontend/src/debugger/tastudio_panel.rs b/crates/rustynes-frontend/src/debugger/tastudio_panel.rs
index 2e9157eb..25a8b0ee 100644
--- a/crates/rustynes-frontend/src/debugger/tastudio_panel.rs
+++ b/crates/rustynes-frontend/src/debugger/tastudio_panel.rs
@@ -633,7 +633,10 @@ mod tests {
#[test]
fn requests_drain_in_order_then_empty() {
let mut s = TasStudioPanelState::default();
- assert!(s.take_requests().is_empty());
+ assert_eq!(
+ s.take_requests(),
+ [] as [crate::debugger::tastudio_panel::TasRequest; 0]
+ );
s.emit(TasRequest::Seek(5));
s.emit(TasRequest::DeleteBranch(2));
let drained = s.take_requests();
diff --git a/crates/rustynes-frontend/src/genie_db.rs b/crates/rustynes-frontend/src/genie_db.rs
index d0146825..8f979ff1 100644
--- a/crates/rustynes-frontend/src/genie_db.rs
+++ b/crates/rustynes-frontend/src/genie_db.rs
@@ -288,7 +288,10 @@ mod tests {
#[test]
fn lookup_miss_is_empty() {
- assert!(codes_for_crc(0xDEAD_BEEF).is_empty());
+ assert_eq!(
+ codes_for_crc(0xDEAD_BEEF),
+ [] as [crate::genie_db::GenieDbCode; 0]
+ );
assert_eq!(game_for_crc(0xDEAD_BEEF), None);
}
@@ -333,7 +336,7 @@ mod tests {
Some("Super Mario Bros.")
);
// The empty key set matches nothing.
- assert!(codes_for_crcs(&[]).is_empty());
+ assert_eq!(codes_for_crcs(&[]), [] as [crate::genie_db::GenieDbCode; 0]);
assert_eq!(game_for_crcs(&[]), None);
// The by-category union covers the same codes as the flat union.
let flat = codes_for_crcs(&[0x3337_EC46]);
diff --git a/crates/rustynes-frontend/src/genie_encode.rs b/crates/rustynes-frontend/src/genie_encode.rs
index a29624ec..3f8e7443 100644
--- a/crates/rustynes-frontend/src/genie_encode.rs
+++ b/crates/rustynes-frontend/src/genie_encode.rs
@@ -79,7 +79,7 @@ pub fn encode_8(addr: u16, data: u8, compare: u8) -> String {
/// + (((h3 & 7) << 12) | ((h5 & 7) << 8) | ((h4 & 8) << 8)
/// | ((h2 & 7) << 4) | ((h1 & 8) << 4) | (h4 & 7) | (h3 & 8))
/// ```
-fn write_addr_nibbles(hex: &mut [u8], addr: u16) {
+const fn write_addr_nibbles(hex: &mut [u8], addr: u16) {
let a = addr & 0x7FFF; // the $8000 base is implicit in the encoding.
hex[3] |= ((a >> 12) & 7) as u8; // bits 12..15
hex[5] |= ((a >> 8) & 7) as u8; // bits 8..11
diff --git a/crates/rustynes-frontend/src/help_tui.rs b/crates/rustynes-frontend/src/help_tui.rs
index 27bad3e7..a65f6ab3 100644
--- a/crates/rustynes-frontend/src/help_tui.rs
+++ b/crates/rustynes-frontend/src/help_tui.rs
@@ -51,7 +51,7 @@ impl HelpApp {
}
}
- fn current(&self) -> &'static HelpTopic {
+ const fn current(&self) -> &'static HelpTopic {
&HELP_TOPICS[self.selected]
}
diff --git a/crates/rustynes-frontend/src/i18n.rs b/crates/rustynes-frontend/src/i18n.rs
index a1fefe9d..fcd3fbfa 100644
--- a/crates/rustynes-frontend/src/i18n.rs
+++ b/crates/rustynes-frontend/src/i18n.rs
@@ -917,8 +917,8 @@ mod tests {
Key::ButtonOk,
Key::StatusIdle,
] {
- assert!(!tr_in(Locale::Spanish, key).is_empty());
- assert!(!tr_in(Locale::English, key).is_empty());
+ assert_ne!(tr_in(Locale::Spanish, key), "");
+ assert_ne!(tr_in(Locale::English, key), "");
}
}
@@ -967,7 +967,7 @@ mod tests {
!english(key).is_empty(),
"{key:?} has an empty English string"
);
- assert!(!tr_in(Locale::English, key).is_empty());
+ assert_ne!(tr_in(Locale::English, key), "");
}
}
diff --git a/crates/rustynes-frontend/src/icon.rs b/crates/rustynes-frontend/src/icon.rs
index 6a618740..427b2a08 100644
--- a/crates/rustynes-frontend/src/icon.rs
+++ b/crates/rustynes-frontend/src/icon.rs
@@ -21,7 +21,9 @@ fn decode() -> Option<(Vec, u32, u32)> {
let rgba = match info.color_type {
png::ColorType::Rgba => buf,
png::ColorType::Rgb => buf
- .chunks_exact(3)
+ .as_chunks::<3>()
+ .0
+ .iter()
.flat_map(|p| [p[0], p[1], p[2], 0xFF])
.collect(),
// The shipped icon is 8-bit RGBA; anything else is unexpected.
diff --git a/crates/rustynes-frontend/src/input_macros.rs b/crates/rustynes-frontend/src/input_macros.rs
index 62b6b338..b15b4771 100644
--- a/crates/rustynes-frontend/src/input_macros.rs
+++ b/crates/rustynes-frontend/src/input_macros.rs
@@ -170,7 +170,10 @@ mod tests {
#[test]
fn empty_bank_round_trips() {
let bytes = MacroBank::default().serialize();
- assert!(MacroBank::deserialize(&bytes).unwrap().macros.is_empty());
+ assert_eq!(
+ MacroBank::deserialize(&bytes).unwrap().macros,
+ [] as [crate::input_macros::InputMacro; 0]
+ );
}
#[test]
diff --git a/crates/rustynes-frontend/src/movie_srt.rs b/crates/rustynes-frontend/src/movie_srt.rs
index 07a28909..96dd815b 100644
--- a/crates/rustynes-frontend/src/movie_srt.rs
+++ b/crates/rustynes-frontend/src/movie_srt.rs
@@ -112,10 +112,10 @@ mod tests {
#[test]
fn empty_markers_make_empty_srt() {
let s = markers_to_srt(Vec::<(u64, String)>::new(), NTSC_NUM, NTSC_DEN, 120);
- assert!(s.is_empty());
+ assert_eq!(s, "");
// Blank labels are skipped too.
let s = markers_to_srt(vec![(0u64, " ".to_string())], NTSC_NUM, NTSC_DEN, 120);
- assert!(s.is_empty());
+ assert_eq!(s, "");
}
#[test]
diff --git a/crates/rustynes-frontend/src/patch.rs b/crates/rustynes-frontend/src/patch.rs
index 0ebc903f..d9c8e33b 100644
--- a/crates/rustynes-frontend/src/patch.rs
+++ b/crates/rustynes-frontend/src/patch.rs
@@ -485,7 +485,7 @@ fn apply_signed_offset(cursor: &mut usize, raw: u64) -> Result<(), PatchError> {
}
/// Read a little-endian `u32` from a 4-byte slice.
-fn read_le_u32(bytes: &[u8]) -> u32 {
+const fn read_le_u32(bytes: &[u8]) -> u32 {
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
diff --git a/crates/rustynes-frontend/src/perf.rs b/crates/rustynes-frontend/src/perf.rs
index d878e206..9051d6b1 100644
--- a/crates/rustynes-frontend/src/perf.rs
+++ b/crates/rustynes-frontend/src/perf.rs
@@ -1096,7 +1096,7 @@ mod tests {
// More than available -> the whole ring.
assert_eq!(r.recent(100).len(), 10);
// Empty ring -> empty vec.
- assert!(SampleRing::default().recent(5).is_empty());
+ assert_eq!(SampleRing::default().recent(5), [] as [f32; 0]);
}
// v1.3.0 Workstream B — the present/produce mismatch diagnostics (the
diff --git a/crates/rustynes-frontend/src/shader_pass.rs b/crates/rustynes-frontend/src/shader_pass.rs
index 3b726124..b8acf3f9 100644
--- a/crates/rustynes-frontend/src/shader_pass.rs
+++ b/crates/rustynes-frontend/src/shader_pass.rs
@@ -977,7 +977,10 @@ mod tests {
#[test]
fn ignores_non_pragma_lines() {
- assert!(parse_pragma_parameters("let x = 1.0;\n// a comment\n").is_empty());
+ assert_eq!(
+ parse_pragma_parameters("let x = 1.0;\n// a comment\n"),
+ [] as [crate::shader_pass::ShaderParam; 0]
+ );
}
#[test]
@@ -1053,7 +1056,10 @@ mod tests {
// The load-bearing invariant at the config level.
let cfg = ShaderStackConfig::default();
assert!(!cfg.has_enabled_passes());
- assert!(cfg.effective_passes().is_empty());
+ assert_eq!(
+ cfg.effective_passes(),
+ [] as [&crate::shader_pass::ShaderPassDesc; 0]
+ );
}
#[test]
@@ -1098,7 +1104,10 @@ mod tests {
passes: vec![ShaderPassDesc::new("from-a-newer-build")],
};
assert!(!cfg.has_enabled_passes());
- assert!(cfg.effective_passes().is_empty());
+ assert_eq!(
+ cfg.effective_passes(),
+ [] as [&crate::shader_pass::ShaderPassDesc; 0]
+ );
}
#[test]
@@ -1129,7 +1138,10 @@ mod tests {
// family (the legacy `crt` scanline pass or, since v2.1.9, one of the
// marquee CRT-stack / raw-signal passes) and must resolve to a builtin.
let presets = ShaderPresetBank::builtins();
- assert!(!presets.is_empty());
+ assert_ne!(
+ presets,
+ [] as [(std::string::String, crate::shader_pass::ShaderStackConfig); 0]
+ );
for (name, stack) in presets {
assert!(stack.has_enabled_passes(), "{name}");
assert_eq!(stack.passes.len(), 1, "{name} should be a single pass");
diff --git a/crates/rustynes-frontend/src/wasm_io.rs b/crates/rustynes-frontend/src/wasm_io.rs
index 21b98799..665b866f 100644
--- a/crates/rustynes-frontend/src/wasm_io.rs
+++ b/crates/rustynes-frontend/src/wasm_io.rs
@@ -149,8 +149,7 @@ pub fn download_bytes(filename: &str, bytes: &[u8]) {
pub fn fs_access_supported() -> bool {
web_sys::window().is_some_and(|w| {
js_sys::Reflect::get(&w, &JsValue::from_str("showSaveFilePicker"))
- .ok()
- .is_some_and(|f| f.is_function())
+ .is_ok_and(|f| f.is_function())
})
}
diff --git a/crates/rustynes-frontend/web/Trunk.toml b/crates/rustynes-frontend/web/Trunk.toml
index 349c0c3b..3d9a9c32 100644
--- a/crates/rustynes-frontend/web/Trunk.toml
+++ b/crates/rustynes-frontend/web/Trunk.toml
@@ -35,3 +35,8 @@ port = 8081
# library are released in lockstep and version-check each other.
# Check with `grep -A1 'name = "wasm-bindgen"' Cargo.lock`.
wasm_bindgen = "0.2.129"
+# v3.0.1: wasm-opt (binaryen) pinned too. Unset, trunk 0.21.14 falls back to
+# its built-in default (`version_123`, about two years old), so the optimiser
+# that shapes the shipped demo was whatever that trunk release happened to
+# carry. version_133 is binaryen's newest release (2026-09-21).
+wasm_opt = "version_133"
diff --git a/crates/rustynes-gamedb/Cargo.toml b/crates/rustynes-gamedb/Cargo.toml
index e5d407f6..b14c97f6 100644
--- a/crates/rustynes-gamedb/Cargo.toml
+++ b/crates/rustynes-gamedb/Cargo.toml
@@ -11,7 +11,8 @@
name = "rustynes-gamedb"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-gfx-shaders/src/crt_stack.rs b/crates/rustynes-gfx-shaders/src/crt_stack.rs
index 9ab45171..8f0f17d3 100644
--- a/crates/rustynes-gfx-shaders/src/crt_stack.rs
+++ b/crates/rustynes-gfx-shaders/src/crt_stack.rs
@@ -124,8 +124,8 @@ mod tests {
fn every_shader_has_nonempty_source() {
for s in CrtStackShader::ALL {
assert!(!s.wgsl().is_empty(), "{s:?} has empty WGSL");
- assert!(!s.slug().is_empty());
- assert!(!s.display_name().is_empty());
+ assert_ne!(s.slug(), "");
+ assert_ne!(s.display_name(), "");
}
}
diff --git a/crates/rustynes-hdpack/src/hdpack.rs b/crates/rustynes-hdpack/src/hdpack.rs
index f2462e82..a1d87efa 100644
--- a/crates/rustynes-hdpack/src/hdpack.rs
+++ b/crates/rustynes-hdpack/src/hdpack.rs
@@ -953,7 +953,7 @@ fn decode_png_pixels(
png::ColorType::Rgba => buf,
png::ColorType::Rgb => {
let mut out = Vec::with_capacity(rgba_len);
- for px in buf.chunks_exact(3) {
+ for px in buf.as_chunks::<3>().0 {
out.extend_from_slice(&[px[0], px[1], px[2], 0xFF]);
}
out
@@ -967,7 +967,7 @@ fn decode_png_pixels(
}
png::ColorType::GrayscaleAlpha => {
let mut out = Vec::with_capacity(rgba_len);
- for px in buf.chunks_exact(2) {
+ for px in buf.as_chunks::<2>().0 {
out.extend_from_slice(&[px[0], px[0], px[0], px[1]]);
}
out
@@ -2629,7 +2629,7 @@ fn blend_over(
/// it bit-for-bit or no tile matches.
fn calculate_hash(key: &[u8]) -> u32 {
let mut result: u32 = 0;
- for chunk in key.chunks_exact(4) {
+ for chunk in key.as_chunks::<4>().0 {
let val = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
result = result.wrapping_add(val).rotate_left(2);
}
@@ -3122,7 +3122,7 @@ mod tests {
assert_eq!(rule.image, 0, "bitmap index 0 = first
");
assert_eq!(rule.x, 16);
assert_eq!(rule.y, 0);
- assert!(rule.conditions.is_empty());
+ assert_eq!(rule.conditions, [] as [usize; 0]);
assert_eq!(parsed.image_names, vec!["tiles.png".to_string()]);
}
@@ -3392,7 +3392,7 @@ mod tests {
assert_eq!(parsed.backgrounds[0].y, 0);
// No priority field present -> Mesen default priority 10.
assert_eq!(parsed.backgrounds[0].priority, 10);
- assert!(parsed.backgrounds[0].conditions.is_empty());
+ assert_eq!(parsed.backgrounds[0].conditions, [] as [usize; 0]);
assert_eq!(parsed.backgrounds[1].priority, 3);
assert_eq!(parsed.backgrounds[1].x, 16);
assert_eq!(parsed.backgrounds[1].y, 32);
diff --git a/crates/rustynes-ios/src/audio_ring.rs b/crates/rustynes-ios/src/audio_ring.rs
index beff37d1..fbc17761 100644
--- a/crates/rustynes-ios/src/audio_ring.rs
+++ b/crates/rustynes-ios/src/audio_ring.rs
@@ -369,7 +369,7 @@ impl Producer {
/// before v2.7.4. (Unreachable with cpal 0.18 on iOS, which always negotiates
/// stereo, but a surround route would otherwise play only the left side there.)
pub fn fan_out(frame: &mut [f32], l: f32, r: f32) {
- let centre = 0.5 * (l + r);
+ let centre = f32::midpoint(l, r);
match frame {
[] => {}
[c0] => *c0 = centre,
diff --git a/crates/rustynes-libretro/Cargo.toml b/crates/rustynes-libretro/Cargo.toml
index 9fb7d833..f403b6cc 100644
--- a/crates/rustynes-libretro/Cargo.toml
+++ b/crates/rustynes-libretro/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-libretro"
description = "Cycle-accurate NES emulator libretro core wrapper"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-libretro/src/abi_tests.rs b/crates/rustynes-libretro/src/abi_tests.rs
index 40aafd9b..a0e17d81 100644
--- a/crates/rustynes-libretro/src/abi_tests.rs
+++ b/crates/rustynes-libretro/src/abi_tests.rs
@@ -1535,9 +1535,7 @@ fn fds_disk_writes_survive_closing_the_game() {
.join(format!("{sha}.fds.sav"));
let _ = std::fs::remove_file(&file);
let written = |file: &std::path::Path| {
- std::fs::read(file)
- .ok()
- .is_some_and(|b| b.get(15..15 + DISK_MARKER.len()) == Some(DISK_MARKER))
+ std::fs::read(file).is_ok_and(|b| b.get(15..15 + DISK_MARKER.len()) == Some(DISK_MARKER))
};
// A clean boot: nothing written yet.
@@ -1854,7 +1852,7 @@ fn oracle_frame(mut nes: Nes, frames: u32) -> Vec {
nes.run_frame();
}
let mut out = nes.framebuffer().to_vec();
- for px in out.chunks_exact_mut(4) {
+ for px in out.as_chunks_mut::<4>().0 {
px.swap(0, 2);
}
out
diff --git a/crates/rustynes-libretro/src/lib.rs b/crates/rustynes-libretro/src/lib.rs
index 454e8303..7cdfadf5 100644
--- a/crates/rustynes-libretro/src/lib.rs
+++ b/crates/rustynes-libretro/src/lib.rs
@@ -1701,7 +1701,7 @@ impl RustyNesLibretro {
return;
};
self.video_buffer.extend_from_slice(nes.framebuffer());
- for chunk in self.video_buffer.chunks_exact_mut(4) {
+ for chunk in self.video_buffer.as_chunks_mut::<4>().0 {
chunk.swap(0, 2); // RGBA8 → XRGB8888 (in-memory B G R X).
}
ctx.draw_frame(&self.video_buffer, NES_W as u32, NES_H as u32, NES_W * 4);
@@ -3513,7 +3513,7 @@ mod tests {
let mut expected = Vec::with_capacity(DUAL_W * NES_H * 4);
for y in 0..NES_H {
for fb in [dual.main_framebuffer(), dual.sub_framebuffer()] {
- for px in fb[y * NES_W * 4..(y + 1) * NES_W * 4].chunks_exact(4) {
+ for px in fb[y * NES_W * 4..(y + 1) * NES_W * 4].as_chunks::<4>().0 {
expected.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
}
}
diff --git a/crates/rustynes-mappers/Cargo.toml b/crates/rustynes-mappers/Cargo.toml
index 9bf50c06..57430cc0 100644
--- a/crates/rustynes-mappers/Cargo.toml
+++ b/crates/rustynes-mappers/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-mappers"
description = "Cartridge file format parsing and mapper implementations for the NES"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-mappers/src/bmc_simple.rs b/crates/rustynes-mappers/src/bmc_simple.rs
index 0e03207b..2704e449 100644
--- a/crates/rustynes-mappers/src/bmc_simple.rs
+++ b/crates/rustynes-mappers/src/bmc_simple.rs
@@ -195,12 +195,8 @@ impl SimpleBmc {
SimpleBoard::M286 => {
let last8 = self.prg_count_8k() - 1;
let last_chr2 = (self.chr.len() / CHR_BANK_2K).max(1) - 1;
- for s in &mut self.prg8 {
- *s = last8;
- }
- for c in &mut self.chr2 {
- *c = last_chr2;
- }
+ self.prg8.fill(last8);
+ self.chr2.fill(last_chr2);
}
SimpleBoard::M320 => self.update_m320(),
SimpleBoard::M289 => self.update_m289(),
diff --git a/crates/rustynes-mappers/src/header.rs b/crates/rustynes-mappers/src/header.rs
index 3639e834..22933862 100644
--- a/crates/rustynes-mappers/src/header.rs
+++ b/crates/rustynes-mappers/src/header.rs
@@ -1528,7 +1528,7 @@ mod tests {
#[test]
fn truncated_header() {
- let bytes = [b'N', b'E', b'S'];
+ let bytes = *b"NES";
assert!(matches!(
parse_header(&bytes),
Err(RomError::Truncated { needed: 16, got: 3 })
diff --git a/crates/rustynes-mappers/src/homebrew_boards.rs b/crates/rustynes-mappers/src/homebrew_boards.rs
index 222ef1aa..9062f6a1 100644
--- a/crates/rustynes-mappers/src/homebrew_boards.rs
+++ b/crates/rustynes-mappers/src/homebrew_boards.rs
@@ -2230,7 +2230,7 @@ mod tests {
#[test]
fn m30_non_flashable_board_has_no_flash() {
let mut m = Unrom512M30::new(synth_prg_16k(16), &[], false, true, 0, false).unwrap();
- assert!(m.save_data().is_empty());
+ assert_eq!(m.save_data(), []);
m.cpu_write(0x9555, 0xAA);
assert!(m.cpu_read_unmapped(0x6000));
}
diff --git a/crates/rustynes-mappers/src/lib.rs b/crates/rustynes-mappers/src/lib.rs
index 63f81ee3..eefad19d 100644
--- a/crates/rustynes-mappers/src/lib.rs
+++ b/crates/rustynes-mappers/src/lib.rs
@@ -1611,7 +1611,7 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
#[test]
diff --git a/crates/rustynes-mappers/src/mmc3_boards.rs b/crates/rustynes-mappers/src/mmc3_boards.rs
index 36030f96..23432d3d 100644
--- a/crates/rustynes-mappers/src/mmc3_boards.rs
+++ b/crates/rustynes-mappers/src/mmc3_boards.rs
@@ -848,7 +848,7 @@ impl Mapper for Mmc3Board {
out.push(u8::from(o.is_some()));
out.push(o.unwrap_or(0));
}
- out.push(self.sticky.map_or(0xFF, |s| s));
+ out.push(self.sticky.unwrap_or(0xFF));
out.push(self.dip);
out.extend_from_slice(&(core.len() as u32).to_le_bytes());
out.extend_from_slice(&core);
diff --git a/crates/rustynes-mappers/tests/battery_sram_exposed.rs b/crates/rustynes-mappers/tests/battery_sram_exposed.rs
index be60541c..ef7ebf71 100644
--- a/crates/rustynes-mappers/tests/battery_sram_exposed.rs
+++ b/crates/rustynes-mappers/tests/battery_sram_exposed.rs
@@ -56,7 +56,7 @@ fn image(mapper: u16, chr_rom: bool) -> Vec {
/// Write a 12-bit mapper number into an NES 2.0 header (bytes 6-8), keeping the
/// battery and NES 2.0 identifier bits. The sweep patches one template image
/// per CHR variant with this instead of rebuilding 384 KiB for every number.
-fn set_mapper(h: &mut [u8], mapper: u16) {
+const fn set_mapper(h: &mut [u8], mapper: u16) {
h[6] = (((mapper & 0x0F) as u8) << 4) | 0b0000_0010;
h[7] = ((mapper & 0xF0) as u8) | 0b0000_1000;
h[8] = ((mapper >> 8) & 0x0F) as u8;
diff --git a/crates/rustynes-mobile/src/lib.rs b/crates/rustynes-mobile/src/lib.rs
index 05715304..abf1b144 100644
--- a/crates/rustynes-mobile/src/lib.rs
+++ b/crates/rustynes-mobile/src/lib.rs
@@ -1841,7 +1841,7 @@ impl NesController {
});
}
let mut pal = [[0u8; 3]; 64];
- for (i, chunk) in bytes[..192].chunks_exact(3).enumerate() {
+ for (i, chunk) in bytes[..192].as_chunks::<3>().0.iter().enumerate() {
pal[i] = [chunk[0], chunk[1], chunk[2]];
}
// v2.9.9 (NF-22) — both consoles of a cabinet.
@@ -3688,7 +3688,7 @@ mod tests {
let ctrl = NesController::new(battery_nrom(false), DEFAULT_SAMPLE_RATE).expect("load");
assert!(!ctrl.has_battery());
ctrl.step_frame();
- assert!(ctrl.battery_ram().is_empty());
+ assert_eq!(ctrl.battery_ram(), [] as [u8; 0]);
assert!(matches!(
ctrl.load_battery_ram(vec![0; 0x2000]),
Err(MobileError::Battery { .. })
@@ -4212,7 +4212,7 @@ mod tests {
ctrl.set_disk_side(Some(7));
assert_eq!(ctrl.inserted_disk_side(), None, "out of range is ignored");
assert!(!ctrl.disk_is_dirty());
- assert!(!ctrl.disk_image_bytes().is_empty());
+ assert_ne!(ctrl.disk_image_bytes(), [] as [u8; 0]);
assert_eq!(ctrl.nsf_song_count(), 0, "a disk is not an NSF");
}
@@ -4286,7 +4286,7 @@ mod tests {
let single = NesController::new(tiny_nrom(), DEFAULT_SAMPLE_RATE).expect("load");
assert!(ctrl.load_state(single.save_state()).is_err());
assert!(!single.is_dual_system());
- assert!(single.sub_framebuffer().is_empty());
+ assert_eq!(single.sub_framebuffer(), [] as [u8; 0]);
}
/// Plan item 7: the features the core scopes out of a cabinet (one
@@ -4419,7 +4419,7 @@ mod tests {
);
// Recovered once, warned once: later calls are ordinary.
ctrl.step_frame();
- assert!(ctrl.drain_warning_codes().is_empty());
+ assert_eq!(ctrl.drain_warning_codes(), [] as [HostWarning; 0]);
}
/// v2.7.4 (review of MOB-03): a panic while a frame runs must not reach the
@@ -4539,7 +4539,7 @@ mod tests {
let frozen = ctrl.frame();
ctrl.run_frame();
assert_eq!(ctrl.frame(), frozen, "no cycle may run after the panic");
- assert!(ctrl.composite_hd_frame().is_empty());
+ assert_eq!(ctrl.composite_hd_frame(), [] as [u8; 0]);
assert!(ctrl.drain_warning_codes().is_empty(), "warned once");
// A fresh start thaws it.
diff --git a/crates/rustynes-netplay/src/relay.rs b/crates/rustynes-netplay/src/relay.rs
index 37ac8d29..a5574838 100644
--- a/crates/rustynes-netplay/src/relay.rs
+++ b/crates/rustynes-netplay/src/relay.rs
@@ -804,9 +804,9 @@ fn sha1(data: &[u8]) -> [u8; 20] {
msg.push(0);
}
msg.extend_from_slice(&ml.to_be_bytes());
- for chunk in msg.chunks_exact(64) {
+ for chunk in msg.as_chunks::<64>().0 {
let mut w = [0u32; 80];
- for (i, word) in chunk.chunks_exact(4).enumerate() {
+ for (i, word) in chunk.as_chunks::<4>().0.iter().enumerate() {
w[i] = u32::from_be_bytes([word[0], word[1], word[2], word[3]]);
}
for i in 16..80 {
@@ -936,9 +936,9 @@ fn md5(data: &[u8]) -> [u8; 16] {
msg.push(0);
}
msg.extend_from_slice(&ml.to_le_bytes());
- for chunk in msg.chunks_exact(64) {
+ for chunk in msg.as_chunks::<64>().0 {
let mut m = [0u32; 16];
- for (i, word) in chunk.chunks_exact(4).enumerate() {
+ for (i, word) in chunk.as_chunks::<4>().0.iter().enumerate() {
m[i] = u32::from_le_bytes([word[0], word[1], word[2], word[3]]);
}
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
diff --git a/crates/rustynes-netplay/src/signaling.rs b/crates/rustynes-netplay/src/signaling.rs
index 93ff6bf8..aaf7c343 100644
--- a/crates/rustynes-netplay/src/signaling.rs
+++ b/crates/rustynes-netplay/src/signaling.rs
@@ -1275,7 +1275,7 @@ mod tests {
assert_eq!(relay.room_count(), 1);
// Peer 2 leaves → room is dropped.
let acts = relay.disconnect(2);
- assert!(acts.is_empty());
+ assert_eq!(acts, [] as [crate::signaling::Action; 0]);
assert_eq!(relay.room_count(), 0);
}
diff --git a/crates/rustynes-netplay/src/transport.rs b/crates/rustynes-netplay/src/transport.rs
index 15e92505..0d1fbb2b 100644
--- a/crates/rustynes-netplay/src/transport.rs
+++ b/crates/rustynes-netplay/src/transport.rs
@@ -326,7 +326,7 @@ mod tests {
// With zero latency, the message is deliverable on b's next poll.
let got = b.poll();
assert_eq!(got, vec![NetMessage::InputAck { frame: 5 }]);
- assert!(b.poll().is_empty());
+ assert_eq!(b.poll(), [] as [crate::message::NetMessage; 0]);
}
#[test]
@@ -334,9 +334,9 @@ mod tests {
let (mut a, mut b) = MemoryTransport::pair(LinkConditions::fixed_latency(3), 7);
a.send(&NetMessage::InputAck { frame: 1 });
// Three polls of nothing, then delivery.
- assert!(b.poll().is_empty());
- assert!(b.poll().is_empty());
- assert!(b.poll().is_empty());
+ assert_eq!(b.poll(), [] as [crate::message::NetMessage; 0]);
+ assert_eq!(b.poll(), [] as [crate::message::NetMessage; 0]);
+ assert_eq!(b.poll(), [] as [crate::message::NetMessage; 0]);
assert_eq!(b.poll(), vec![NetMessage::InputAck { frame: 1 }]);
}
diff --git a/crates/rustynes-ppu/Cargo.toml b/crates/rustynes-ppu/Cargo.toml
index 9b96a974..84c7c624 100644
--- a/crates/rustynes-ppu/Cargo.toml
+++ b/crates/rustynes-ppu/Cargo.toml
@@ -3,7 +3,8 @@ name = "rustynes-ppu"
description = "Cycle-accurate Ricoh 2C02 PPU (Picture Processing Unit) for the NES"
version.workspace = true
edition.workspace = true
-rust-version.workspace = true
+# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`).
+rust-version = "1.96"
license.workspace = true
authors.workspace = true
repository.workspace = true
diff --git a/crates/rustynes-ppu/src/lib.rs b/crates/rustynes-ppu/src/lib.rs
index ba9ba4d0..d4aed5e7 100644
--- a/crates/rustynes-ppu/src/lib.rs
+++ b/crates/rustynes-ppu/src/lib.rs
@@ -104,6 +104,6 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
}
diff --git a/crates/rustynes-ppu/src/state_trace.rs b/crates/rustynes-ppu/src/state_trace.rs
index fbae4868..f8c7121b 100644
--- a/crates/rustynes-ppu/src/state_trace.rs
+++ b/crates/rustynes-ppu/src/state_trace.rs
@@ -757,7 +757,7 @@ impl PpuStateTrace {
}
let n = body.len() / RECORD_SIZE;
let mut records = Vec::with_capacity(n);
- for chunk in body.chunks_exact(RECORD_SIZE) {
+ for chunk in body.as_chunks::().0 {
let rec = PpuStateRecord::from_bytes(chunk)
.ok_or_else(|| String::from("PpuStateRecord::from_bytes returned None"))?;
records.push(rec);
diff --git a/crates/rustynes-probe/src/atlas.rs b/crates/rustynes-probe/src/atlas.rs
index 43bbbe2f..26be0f76 100644
--- a/crates/rustynes-probe/src/atlas.rs
+++ b/crates/rustynes-probe/src/atlas.rs
@@ -195,7 +195,7 @@ where
let n = frames as usize;
let mut data = vec![0u8; WRAM_LEN * n];
- for (f, chunk) in frame_major.chunks_exact(WRAM_LEN).enumerate() {
+ for (f, chunk) in frame_major.as_chunks::().0.iter().enumerate() {
for (a, &v) in chunk.iter().enumerate() {
data[a * n + f] = v;
}
diff --git a/crates/rustynes-script/Cargo.toml b/crates/rustynes-script/Cargo.toml
index 8602ee69..1df8691c 100644
--- a/crates/rustynes-script/Cargo.toml
+++ b/crates/rustynes-script/Cargo.toml
@@ -2,7 +2,7 @@
name = "rustynes-script"
version.workspace = true
edition = "2024"
-rust-version = "1.96"
+rust-version.workspace = true
description = "Sandboxed Lua 5.4 scripting engine for the RustyNES emulator (memory/state access, per-frame callbacks)."
license.workspace = true
authors.workspace = true
diff --git a/crates/rustynes-script/src/lib.rs b/crates/rustynes-script/src/lib.rs
index e55bf45a..135bb8cc 100644
--- a/crates/rustynes-script/src/lib.rs
+++ b/crates/rustynes-script/src/lib.rs
@@ -1294,7 +1294,7 @@ mod tests {
assert_eq!(draws.len(), 2);
assert!(matches!(&draws[0], DrawCmd::Text { text, .. } if text == "HP: 3"));
// Drained — a second drain is empty.
- assert!(eng.drain_controls().is_empty());
+ assert_eq!(eng.drain_controls(), [] as [types::ControlCmd; 0]);
}
/// v2.1.10 "Creator Tools" (B9) — `emu.drawLine` queues a `DrawCmd::Line`
@@ -2112,7 +2112,7 @@ mod tests {
"error should name the port rule: {err}"
);
// Nothing was staged, so a follow-up apply produces no command.
- assert!(eng.drain_tas_commands().is_empty());
+ assert_eq!(eng.drain_tas_commands(), [] as [types::TasCmd; 0]);
let mut eng = ScriptEngine::new().expect("engine");
eng.load(
diff --git a/crates/rustynes-script/src/mlua_backend.rs b/crates/rustynes-script/src/mlua_backend.rs
index 3c619bba..83a7d539 100644
--- a/crates/rustynes-script/src/mlua_backend.rs
+++ b/crates/rustynes-script/src/mlua_backend.rs
@@ -1759,7 +1759,7 @@ impl VmBackend for MluaBackend {
let nes = nes_cell.borrow();
let fb = nes.framebuffer();
let t = lua.create_table_with_capacity(fb.len() / 4, 0)?;
- for (i, px) in fb.chunks_exact(4).enumerate() {
+ for (i, px) in fb.as_chunks::<4>().0.iter().enumerate() {
let argb = (u32::from(px[0]) << 24)
| (u32::from(px[1]) << 16)
| (u32::from(px[2]) << 8)
diff --git a/crates/rustynes-test-harness/src/bin/fds_smoke.rs b/crates/rustynes-test-harness/src/bin/fds_smoke.rs
index 7c28d6ee..6b670dfa 100644
--- a/crates/rustynes-test-harness/src/bin/fds_smoke.rs
+++ b/crates/rustynes-test-harness/src/bin/fds_smoke.rs
@@ -102,7 +102,9 @@ fn main() {
match result {
Ok(Ok(fb)) => {
let colours: HashSet<[u8; 4]> = fb
- .chunks_exact(4)
+ .as_chunks::<4>()
+ .0
+ .iter()
.map(|c| [c[0], c[1], c[2], c[3]])
.collect();
let n = colours.len();
diff --git a/crates/rustynes-test-harness/src/bin/fds_swap_repro.rs b/crates/rustynes-test-harness/src/bin/fds_swap_repro.rs
index dc310df9..5ba944ed 100644
--- a/crates/rustynes-test-harness/src/bin/fds_swap_repro.rs
+++ b/crates/rustynes-test-harness/src/bin/fds_swap_repro.rs
@@ -52,7 +52,9 @@ fn fb_hash(fb: &[u8]) -> u64 {
}
fn colour_count(fb: &[u8]) -> usize {
- fb.chunks_exact(4)
+ fb.as_chunks::<4>()
+ .0
+ .iter()
.map(|c| [c[0], c[1], c[2], c[3]])
.collect::>()
.len()
diff --git a/crates/rustynes-test-harness/src/bin/fds_trace.rs b/crates/rustynes-test-harness/src/bin/fds_trace.rs
index a9cf4867..58317fa7 100644
--- a/crates/rustynes-test-harness/src/bin/fds_trace.rs
+++ b/crates/rustynes-test-harness/src/bin/fds_trace.rs
@@ -275,7 +275,7 @@ mod tests {
let reference = vec![0x5Au8; INFO_BLOCK_LEN];
let (v, diffs) = compare_info_block(&reference[..20], &reference);
assert_eq!(v, BlockVerdict::Truncated { compared: 20 });
- assert!(diffs.is_empty());
+ assert_eq!(diffs, [] as [(usize, u8, u8); 0]);
}
#[test]
diff --git a/crates/rustynes-test-harness/src/bin/repro_smb3.rs b/crates/rustynes-test-harness/src/bin/repro_smb3.rs
index 8b714d38..2a9e8366 100644
--- a/crates/rustynes-test-harness/src/bin/repro_smb3.rs
+++ b/crates/rustynes-test-harness/src/bin/repro_smb3.rs
@@ -224,8 +224,8 @@ fn replay_movie(nes: &mut Nes, movie_path: &str, out_dir: &Path, dump_every: u64
// On-screen sprites only, in OAM order, so we can see which compete on
// Mario's scanline and in what order (the drop is order-dependent).
let mut onlist: Vec<(usize, u8, u8, u8)> = Vec::new();
- for (i, s) in oam.chunks_exact(4).enumerate() {
- let (y, tile, _attr, x) = (s[0], s[1], s[2], s[3]);
+ for (i, s) in oam.as_chunks::<4>().0.iter().enumerate() {
+ let [y, tile, _attr, x] = *s;
if y < 0xEF {
onscreen += 1;
if (40..96).contains(&y) {
@@ -299,7 +299,7 @@ fn observe_idle(nes: &mut Nes, n: usize) {
// Mario; detect "any on-screen sprite in his centre box".
// Precise detector: small-Mario's body tiles (0x05/0x07) on-screen,
// not a loose box (which catches coins/enemies/projectiles as noise).
- let mario_present = oam.chunks_exact(4).any(|s| {
+ let mario_present = oam.as_chunks::<4>().0.iter().any(|s| {
let (y, tile) = (s[0], s[1]);
y < 0xEF && matches!(tile, 0x05 | 0x07)
});
@@ -330,7 +330,7 @@ fn observe_idle(nes: &mut Nes, n: usize) {
/// Mario's body tiles in the OAM/RAM sprite buffer (small Mario, idle).
#[cfg(feature = "debug-hooks")]
fn mario_in_buf(buf: &[u8]) -> bool {
- buf.chunks_exact(4).any(|s| {
+ buf.as_chunks::<4>().0.iter().any(|s| {
let (y, tile) = (s[0], s[1]);
y < 0xEF && matches!(tile, 0x05 | 0x07)
})
@@ -428,7 +428,9 @@ fn diag_idle(nes: &mut Nes, n: usize) {
if ram_mario && !oam_mario {
// Find where Mario's tile pair (0x05 at some +1 offset) sits in RAM.
let ram_idx = ram_buf
- .chunks_exact(4)
+ .as_chunks::<4>()
+ .0
+ .iter()
.position(|s| s[0] < 0xEF && matches!(s[1], 0x05 | 0x07));
// Detect a global byte-shift: best offset k minimising sum|oam[i]-ram[i-k]|.
let mut best_k = 0i32;
diff --git a/crates/rustynes-test-harness/src/bin/smb3_dma_trace.rs b/crates/rustynes-test-harness/src/bin/smb3_dma_trace.rs
index 50787b67..c9604534 100644
--- a/crates/rustynes-test-harness/src/bin/smb3_dma_trace.rs
+++ b/crates/rustynes-test-harness/src/bin/smb3_dma_trace.rs
@@ -26,7 +26,9 @@ use rustynes_core::irq_trace::BusAccess;
use rustynes_core::{Buttons, Movie, MoviePlayer, Nes};
fn mario_in_buf(buf: &[u8]) -> bool {
- buf.chunks_exact(4)
+ buf.as_chunks::<4>()
+ .0
+ .iter()
.any(|s| s[0] < 0xEF && matches!(s[1], 0x05 | 0x07))
}
diff --git a/crates/rustynes-test-harness/src/bin/vs_dual_trace.rs b/crates/rustynes-test-harness/src/bin/vs_dual_trace.rs
index 68577e07..d0a89bb0 100644
--- a/crates/rustynes-test-harness/src/bin/vs_dual_trace.rs
+++ b/crates/rustynes-test-harness/src/bin/vs_dual_trace.rs
@@ -32,7 +32,9 @@ fn main() {
let mut sub_pcs: HashMap = HashMap::new();
let colours = |fb: &[u8]| {
- fb.chunks_exact(4)
+ fb.as_chunks::<4>()
+ .0
+ .iter()
.map(|c| [c[0], c[1], c[2], c[3]])
.collect::>()
.len()
diff --git a/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs b/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
index fb8155ea..4a57bf89 100644
--- a/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
+++ b/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
@@ -243,7 +243,12 @@ fn main() {
// test" from ordinary gameplay, which is how the Duck Hunt sequence is
// recognisable at all.
let fb = nes.framebuffer();
- let mean_luma: u32 = fb.chunks_exact(4).map(|p| u32::from(p[1])).sum::()
+ let mean_luma: u32 = fb
+ .as_chunks::<4>()
+ .0
+ .iter()
+ .map(|p| u32::from(p[1]))
+ .sum::()
/ u32::try_from(fb.len() / 4).unwrap_or(1);
let _ = bright;
if let Ok(dir) = std::env::var("ZAPPER_PROBE_PNG_DIR") {
diff --git a/crates/rustynes-test-harness/src/coverage.rs b/crates/rustynes-test-harness/src/coverage.rs
index ddf5183c..77263d95 100644
--- a/crates/rustynes-test-harness/src/coverage.rs
+++ b/crates/rustynes-test-harness/src/coverage.rs
@@ -101,7 +101,9 @@ pub fn frame_health(fb: &[u8]) -> FrameHealth {
// per-pixel hashing, a single allocation). The distinct-colour count and
// dominant-colour fraction are identical to the map-based tally.
let mut pixels: Vec = fb
- .chunks_exact(4)
+ .as_chunks::<4>()
+ .0
+ .iter()
.map(|px| u32::from_le_bytes([px[0], px[1], px[2], px[3]]))
.collect();
let total = pixels.len();
diff --git a/crates/rustynes-test-harness/src/lib.rs b/crates/rustynes-test-harness/src/lib.rs
index e7ab4e73..5bcdeefc 100644
--- a/crates/rustynes-test-harness/src/lib.rs
+++ b/crates/rustynes-test-harness/src/lib.rs
@@ -57,6 +57,6 @@ mod tests {
#[test]
fn version_is_non_empty() {
- assert!(!version().is_empty());
+ assert_ne!(version(), "");
}
}
diff --git a/crates/rustynes-test-harness/tests/contribution_checklist_audit.rs b/crates/rustynes-test-harness/tests/contribution_checklist_audit.rs
index b0dd2062..e645cedb 100644
--- a/crates/rustynes-test-harness/tests/contribution_checklist_audit.rs
+++ b/crates/rustynes-test-harness/tests/contribution_checklist_audit.rs
@@ -520,7 +520,7 @@ fn a_markdown_link_bullet_is_not_a_checkbox() {
let md = format!("{GOOD}- [NESDev documentation](https://www.nesdev.org/wiki/)\n");
let items = parse(&md).expect("a link bullet is ordinary prose, not a malformed checkbox");
assert_eq!(items.len(), 2, "the link must not become an item");
- assert!(violations(&items).is_empty());
+ assert_eq!(violations(&items), [] as [std::string::String; 0]);
}
#[test]
diff --git a/crates/rustynes-test-harness/tests/holy_mapperel.rs b/crates/rustynes-test-harness/tests/holy_mapperel.rs
index 22258e62..52613f70 100644
--- a/crates/rustynes-test-harness/tests/holy_mapperel.rs
+++ b/crates/rustynes-test-harness/tests/holy_mapperel.rs
@@ -166,7 +166,9 @@ fn fnv1a64(fb: &[u8]) -> u64 {
/// has two (text + backdrop); a blanked crash screen collapses to one.
fn distinct_colors(fb: &[u8]) -> usize {
let mut px: Vec = fb
- .chunks_exact(4)
+ .as_chunks::<4>()
+ .0
+ .iter()
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
px.sort_unstable();
diff --git a/crates/rustynes-test-harness/tests/mister_source_map_audit.rs b/crates/rustynes-test-harness/tests/mister_source_map_audit.rs
index b622ba32..d2fd1087 100644
--- a/crates/rustynes-test-harness/tests/mister_source_map_audit.rs
+++ b/crates/rustynes-test-harness/tests/mister_source_map_audit.rs
@@ -361,7 +361,7 @@ fn a_bare_filename_is_rejected() {
"nesdev_wiki/APU_Sweep.xhtml".to_owned(),
"docs/apu-2a03.md".to_owned(),
];
- assert!(bare_filenames(&clean).is_empty());
+ assert_eq!(bare_filenames(&clean), [] as [&std::string::String; 0]);
let dirty = vec![
"nesdev_wiki/APU_Pulse.xhtml".to_owned(),
@@ -390,7 +390,7 @@ fn a_citation_with_an_unrecognised_extension_is_reported_not_dropped() {
// And a span that is not path-shaped must not be reported as one.
let prose = "the `v` register, `$2005`, and `ppu-state-trace`";
- assert!(unrecognised_extensions(prose).is_empty());
+ assert_eq!(unrecognised_extensions(prose), [] as [&str; 0]);
// A VERSION STRING is the realistic false positive, and these documents are
// full of them. `v2.5.3` passes the character set and contains dots, so
@@ -403,7 +403,7 @@ fn a_citation_with_an_unrecognised_extension_is_reported_not_dropped() {
// both -- the bare-filename blindness again, one extension away.
let bare_pdf = "see `board.pdf` for the pinout";
assert_eq!(unrecognised_extensions(bare_pdf), vec!["board.pdf"]);
- assert!(cited_paths(bare_pdf).is_empty());
+ assert_eq!(cited_paths(bare_pdf), [] as [std::string::String; 0]);
let versions = "shipped in `v2.5.3`, built on `v2.4.9`";
assert!(
diff --git a/crates/rustynes-test-harness/tests/release_notes_render_audit.rs b/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
index 0c08dcbf..9b7ab0c5 100644
--- a/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
+++ b/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
@@ -162,16 +162,37 @@ fn release_notes_are_not_hard_wrapped() {
#[test]
fn the_paragraph_scanner_recognises_the_shapes_release_notes_use() {
// One line per paragraph -- the required form.
- assert!(wrapped_paragraphs("A single long paragraph line.\n\nAnother one.\n").is_empty());
+ assert_eq!(
+ wrapped_paragraphs("A single long paragraph line.\n\nAnother one.\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
// Two lines of one paragraph -- the defect.
assert_eq!(wrapped_paragraphs("wrapped here\nand continued\n").len(), 1);
// Structure that legitimately occupies several short lines.
- assert!(wrapped_paragraphs("| a | b |\n| - | - |\n").is_empty());
- assert!(wrapped_paragraphs("- one\n- two\n").is_empty());
- assert!(wrapped_paragraphs("> quoted\n> more\n").is_empty());
- assert!(wrapped_paragraphs("# head\n## head2\n").is_empty());
+ assert_eq!(
+ wrapped_paragraphs("| a | b |\n| - | - |\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
+ assert_eq!(
+ wrapped_paragraphs("- one\n- two\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
+ assert_eq!(
+ wrapped_paragraphs("> quoted\n> more\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
+ assert_eq!(
+ wrapped_paragraphs("# head\n## head2\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
// Fenced code keeps its own line structure.
- assert!(wrapped_paragraphs("```text\nline one\nline two\n```\n").is_empty());
+ assert_eq!(
+ wrapped_paragraphs("```text\nline one\nline two\n```\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
// A deliberate hard break (two trailing spaces) is not a wrap.
- assert!(wrapped_paragraphs("line one \nline two\n").is_empty());
+ assert_eq!(
+ wrapped_paragraphs("line one \nline two\n"),
+ [] as [(usize, std::vec::Vec); 0]
+ );
}
diff --git a/crates/rustynes-test-harness/tests/release_state_prose_audit.rs b/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
index 1f09ab1a..3636a977 100644
--- a/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
+++ b/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
@@ -276,11 +276,23 @@ fn the_current_label_scanner_ignores_prose_quoting_the_defect() {
"2.3.5"
);
// The historical quotation, which is correct prose and must not be flagged.
- assert!(current_labels("v2.3.5, still marked `(current)`").is_empty());
- assert!(current_labels("the table stopped at v2.3.5, still marked `(current)`").is_empty());
+ assert_eq!(
+ current_labels("v2.3.5, still marked `(current)`"),
+ [] as [(usize, std::string::String); 0]
+ );
+ assert_eq!(
+ current_labels("the table stopped at v2.3.5, still marked `(current)`"),
+ [] as [(usize, std::string::String); 0]
+ );
// A version with no codename, or no label, is not a claim.
- assert!(current_labels("**v2.4.5** (current)").is_empty());
- assert!(current_labels(r#"**v2.4.5 "Compass"** shipped"#).is_empty());
+ assert_eq!(
+ current_labels("**v2.4.5** (current)"),
+ [] as [(usize, std::string::String); 0]
+ );
+ assert_eq!(
+ current_labels(r#"**v2.4.5 "Compass"** shipped"#),
+ [] as [(usize, std::string::String); 0]
+ );
}
/// Phrases that name a release as the one currently tagged.
@@ -409,9 +421,15 @@ fn the_tag_claim_scanner_reads_backward_and_needs_a_version() {
"2.3.9"
);
// No version in reach is not a claim about any release.
- assert!(tag_claims("its version can lag behind the latest tag").is_empty());
+ assert_eq!(
+ tag_claims("its version can lag behind the latest tag"),
+ [] as [(usize, std::string::String); 0]
+ );
// A version far outside the lookback is not the subject of the phrase.
- assert!(tag_claims(&format!("v2.3.9{} the current tag", " ".repeat(400))).is_empty());
+ assert_eq!(
+ tag_claims(&format!("v2.3.9{} the current tag", " ".repeat(400))),
+ [] as [(usize, std::string::String); 0]
+ );
// Multibyte prose must not panic the backward scan.
assert_eq!(
tag_claims("— v2.4.6 → “Abacus” — the current tag")[0].1,
@@ -533,7 +551,7 @@ fn versions_are_found_only_within_the_window() {
assert_eq!(versions_near(line, 0, 60), vec!["1.8.9".to_string()]);
// Outside the window, the same version is not attributed to the label.
let far = format!("In development{} v1.8.9", " ".repeat(80));
- assert!(versions_near(&far, 0, 60).is_empty());
+ assert_eq!(versions_near(&far, 0, 60), [] as [std::string::String; 0]);
}
#[test]
diff --git a/crates/rustynes-test-harness/tests/vs_dualsystem.rs b/crates/rustynes-test-harness/tests/vs_dualsystem.rs
index 8c186a72..16b1a75e 100644
--- a/crates/rustynes-test-harness/tests/vs_dualsystem.rs
+++ b/crates/rustynes-test-harness/tests/vs_dualsystem.rs
@@ -123,7 +123,9 @@ fn boot_dual(rel: &str, frames: u64) -> VsDualSystem {
/// Count distinct RGBA colours in a framebuffer (blank/crash heuristic).
fn colour_count(fb: &[u8]) -> usize {
- fb.chunks_exact(4)
+ fb.as_chunks::<4>()
+ .0
+ .iter()
.map(|c| [c[0], c[1], c[2], c[3]])
.collect::>()
.len()
diff --git a/crates/rustynes-test-harness/tests/vs_system_rgb.rs b/crates/rustynes-test-harness/tests/vs_system_rgb.rs
index c228ee69..3f5574b5 100644
--- a/crates/rustynes-test-harness/tests/vs_system_rgb.rs
+++ b/crates/rustynes-test-harness/tests/vs_system_rgb.rs
@@ -105,7 +105,9 @@ fn boot_with_coin(rel: &str, frames: u64) -> Vec {
fn assert_rgb_routed(label: &str, fb: &[u8]) {
assert_eq!(fb.len() % 4, 0, "{label}: framebuffer must be RGBA");
let colours: HashSet<[u8; 4]> = fb
- .chunks_exact(4)
+ .as_chunks::<4>()
+ .0
+ .iter()
.map(|c| [c[0], c[1], c[2], c[3]])
.collect();
diff --git a/deploy/Dockerfile b/deploy/Dockerfile
index 347a5475..41af6f46 100644
--- a/deploy/Dockerfile
+++ b/deploy/Dockerfile
@@ -11,8 +11,12 @@
# reach it. Pair with a STUN/TURN server (coturn) for NAT traversal.
# --- build stage ---------------------------------------------------------------
-# Pin to the workspace MSRV (rust-toolchain.toml channel = 1.86.0).
-FROM rust:1.86-bookworm AS build
+# Match the workspace toolchain (rust-toolchain.toml channel = 1.99.0, v3.0.1).
+# rustup inside the image reads that file from the copied workspace and
+# installs the pinned channel regardless, so this tag mainly saves the
+# download; keep the two equal. (It said 1.86 for two years after the pin
+# moved to 1.96; the build was still correct only because of that override.)
+FROM rust:1.99-trixie AS build
WORKDIR /src
# Copy the whole workspace (the example depends on the rustynes-netplay crate +
@@ -27,7 +31,9 @@ RUN cargo build --release --locked \
-p rustynes-netplay --features signaling-server --example signaling_server
# --- runtime stage -------------------------------------------------------------
-FROM debian:bookworm-slim AS runtime
+# Same Debian release as the build stage, so the binary links against the
+# glibc it was built with.
+FROM debian:trixie-slim AS runtime
# Non-root user for the long-running service.
RUN useradd --system --create-home --shell /usr/sbin/nologin signal
COPY --from=build /src/target/release/examples/signaling_server /usr/local/bin/signaling_server
diff --git a/deploy/Dockerfile.raproxy b/deploy/Dockerfile.raproxy
index afc8b08a..16db9f8e 100644
--- a/deploy/Dockerfile.raproxy
+++ b/deploy/Dockerfile.raproxy
@@ -16,7 +16,7 @@
# Build context is the workspace root (so the source script under scripts/ is
# reachable), matching the signaling image:
# docker build -f deploy/Dockerfile.raproxy -t rustynes-raproxy ..
-FROM python:3.12-slim
+FROM python:3.14-slim
# Non-root for defence in depth — the proxy needs no privilege.
RUN useradd --create-home --uid 10001 raproxy
diff --git a/docs/agents/ci-and-release.md b/docs/agents/ci-and-release.md
index dcd451ad..be478e7b 100644
--- a/docs/agents/ci-and-release.md
+++ b/docs/agents/ci-and-release.md
@@ -15,7 +15,7 @@
- **CI security hardening (PRs #319 + #320, 2026-07-21, merged `85ee20db` / `a69200ef`).** `persist-credentials: false` on **all 19** `actions/checkout` sites (18 in #319; the last one, `release-auto.yml`, once its tag check stopped needing Git credentials — see the next bullet), because build scripts / proc macros / test binaries / Gradle scripts / MkDocs all execute unreviewed PR code that could read the token out of `.git/config`. Facts worth not re-deriving: `.github/actions/rust-setup` performs **no checkout of its own** (so call-site hardening is complete coverage); `persist-credentials` does **not** affect the `gh` CLI or API calls, only git network ops using the stored credential — which is why `gh release create`, `softprops/action-gh-release`, and `fastlane match` (a *different* repo, own `MATCH_GIT_*` secrets) all look like they need it and don't; and the highest-exposure job is **`web.yml`'s `build`**, not any `ci.yml` job, because `web.yml` declares `pages: write` + `id-token: write` at *workflow* level. There are now **no exceptions**: `release-auto.yml`'s `prepare` was the last holdout (it needed `git ls-remote origin` for the tag check), and that check is now a `gh api` call, so its checkout joined the sweep.
- **The release tag-existence check is FAIL-CLOSED — keep it that way.** `release-auto.yml`'s `decide` step queries `gh api repos/$GITHUB_REPOSITORY/git/matching-refs/tags/`, NOT `git/ref/tags/`: `matching-refs` answers "absent" with HTTP 200 + an empty array, so a genuine miss can never be confused with a lookup failure and no error-body parsing is needed. It matches by **prefix**, so the exact ref is compared in `jq` — this is load-bearing, not defensive: `v2.2` prefix-matches two real tags (`v2.2.0`, `v2.2.1`) while exact-matching none. Under `shell: bash` + `set -euo pipefail` both a `gh` failure and a non-array body abort the job (verified: exit 1 and exit 5 respectively). The old `git ls-remote ... >/dev/null 2>&1` read *any* non-zero exit as "tag absent", so a blip would try to re-release a shipped version. **Never reintroduce a form where a failed lookup is indistinguishable from "absent."** (Note when testing shell behavior locally: this harness's shell is zsh, whose `set -e` semantics for `var="$(cmd)"` differ from bash's — test with `bash -c` or you will get a false result.)
- **`dtolnay/rust-toolchain` is SHA-pinned in `.github/actions/rust-setup` — never put it back on a branch ref.** It is `@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1`; it used to be `@master`, a branch that advances on every upstream commit, so each run silently resolved to whatever HEAD was. That composite feeds 12 of the repo's 19 checkouts, including `release.yml` (`contents: write`, builds the shipped binaries) and `web.yml` (`pages: write` + `id-token: write`), and it is the action that *installs the compiler* — the hardest place to notice a compromise. The trailing `# v1` is the form Dependabot's `github-actions` ecosystem (enabled in `.github/dependabot.yml`) reads to keep the pin current, so don't strip it. The other ~12 actions remain on `@vN` tags by choice: those are not expected to move, and blanket SHA-pinning is a maintenance-policy decision, not a drive-by fix.
-- **There is ONE toolchain, `rust-toolchain.toml`'s `channel`, and no version literal anywhere in `.github/` — don't add one.** `.github/actions/rust-setup` parses the channel out of that file and fails closed if it can't, so a toolchain bump is a one-line edit there. Pass the composite's `toolchain:` input only to install something *deliberately* different from the project pin. **The resolver is table-scoped `awk` on purpose — do NOT "simplify" it back to a one-line `sed`.** Matching the first `channel = "..."` *anywhere* in the file (the first implementation, caught in review on PR #322) resolves `nightly` if any other table carries a `channel` key ahead of `[toolchain]` — silently installing the very toolchain this setup exists to keep out, while the step still reports success. `awk` rather than `tomllib` because the step runs on Windows and macOS runners too and Python ≥3.11 is not a safe assumption there; only double-quoted TOML strings are accepted, and anything else (missing table, single-quoted value, empty file) aborts the job rather than being guessed at. The old `stable` default was misleading rather than wrong: `rust-toolchain.toml` is a directory override that outranks the `rustup default` the action performs, so every job was already compiling on 1.96.0 (rustup logs `overridden by .../rust-toolchain.toml`) — `stable` just downloaded a second toolchain nothing used and made the workflows *read* as though they tested latest stable, which they never did. **Nightly is used in exactly one place, not a gate:** `cargo fuzz` (hard requirement — libFuzzer's sanitizer flags are nightly-only). If you think a CI job needs nightly, it doesn't.
+- **There is ONE toolchain, `rust-toolchain.toml`'s `channel`, and no version literal anywhere in `.github/` — don't add one.** (Since v3.0.1 there is one deliberate second toolchain, the libretro buildbot's 1.96.0, and it too is written once: `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN`, which `libretro-cross` parses with the same table-scoped, fail-closed `awk` rather than repeating it.) `.github/actions/rust-setup` parses the channel out of that file and fails closed if it can't, so a toolchain bump is a one-line edit there. Pass the composite's `toolchain:` input only to install something *deliberately* different from the project pin. **The resolver is table-scoped `awk` on purpose — do NOT "simplify" it back to a one-line `sed`.** Matching the first `channel = "..."` *anywhere* in the file (the first implementation, caught in review on PR #322) resolves `nightly` if any other table carries a `channel` key ahead of `[toolchain]` — silently installing the very toolchain this setup exists to keep out, while the step still reports success. `awk` rather than `tomllib` because the step runs on Windows and macOS runners too and Python ≥3.11 is not a safe assumption there; only double-quoted TOML strings are accepted, and anything else (missing table, single-quoted value, empty file) aborts the job rather than being guessed at. The old `stable` default was misleading rather than wrong: `rust-toolchain.toml` is a directory override that outranks the `rustup default` the action performs, so every job was already compiling on 1.96.0 (rustup logs `overridden by .../rust-toolchain.toml`) — `stable` just downloaded a second toolchain nothing used and made the workflows *read* as though they tested latest stable, which they never did. **Nightly is used in exactly one place, not a gate:** `cargo fuzz` (hard requirement — libFuzzer's sanitizer flags are nightly-only). If you think a CI job needs nightly, it doesn't.
- **A SKIPPED job satisfies `CI success`, and `CI success` is `main`'s only required check.** The ruleset `Protect (Default)` requires exactly one status context. That job runs `if: always()` and fails on `contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')` — `skipped` is in **neither** list. So a gate that did not run reports a pass for a property it never tested. This is not theoretical: `test-roms` (the accuracy battery) was FULL-run only, so an accuracy regression could not be caught on the PR that caused it — it landed, turned `main` red, and needed a second PR (#396 introduced the vector move, #403 fixed it; **both PRs were correct, the process was not**). **There is no merge queue** — verified, no `merge_group` event appears in the run history — so nothing caught it later either. v2.3.9 A5 adds an `accuracy` paths-filter (chip crates, `rustynes-core`, `rustynes-gamedb`, `rustynes-test-harness`, `tests/`) so the battery runs on PRs that can actually break it; measured first, 11 of the last 40 merged PRs touch those paths, so ~72% still skip it. When judging whether a gate covers something, check three things separately: which contexts are *required*, what the aggregate treats as failure, and whether the job that tests the property actually **ran**.
- **`timeout-minutes` is NOT valid on a job that uses `uses:` — and `actionlint` is how you settle questions like that.** GitHub's workflow-syntax and reuse-workflows pages state it neither way, and a #406 review asserted the restriction was lifted in 2022. It was not: actionlint reports the key unavailable and lists the seven allowed (`name`, `uses`, `with`, `secrets`, `needs`, `if`, `permissions`), so adding one is a **hard syntax error**, not an ignored key. `release-auto.yml`'s `build` therefore cannot carry a timeout; its budget lives on the jobs inside `release.yml`. actionlint is installed and, from v2.3.9, a pinned pre-commit hook. **`.github/actionlint.yaml` has existed since v2.2.3** (it declares the self-hosted `agy` label) — extend it, never `Write` over it; doing so lost its rationale and produced a false "adding the config activated a dormant check" finding that reached a commit body before being retracted.
- **Bound every workflow job, and every network fetch inside one.** PR #400 bounded `ci.yml` and nothing else; v2.3.9 found **six** more unbounded workflows including `release-auto.yml` itself, after `Clippy Security Lints` hung **two hours** in a setup step and blocked the v2.3.7 release PR. Separately, apt provisioning hung **four times across two PRs in one day**, always in a setup/provisioning step and never in a compile or test step. A job timeout bounds the damage but cannot *notice*: a stalled fetch inside a 25-minute budget is indistinguishable from a slow job, and the run reports as `cancelled`, which reads as noise. `.github/scripts/apt-install-retry.sh` adds a per-command `timeout` plus three attempts, and warns on every attempt including ones that succeed — a run needing three and one needing one are identical in the conclusion, and that difference is the early warning.
diff --git a/docs/agents/libretro.md b/docs/agents/libretro.md
index 388c0f08..50c6d8d3 100644
--- a/docs/agents/libretro.md
+++ b/docs/agents/libretro.md
@@ -20,6 +20,7 @@
- **Libretro buildbot CI (issue #311) shipped in PR #312 (2026-07-19/20, `b49dd1e0`) — and the upstream half is now DONE; the "stays OPEN by design" instruction this bullet used to lead with is RETIRED, see below.** `.gitlab-ci.yml` + a `[lib] name = "rustynes"` naming-collision fix + `[workspace] default-members` + RA memory-maps + an FDS load-path fix/disk-control + native Game Genie cheats all shipped. TAS and Netplay needed no new libretro-side wiring — RetroArch's own rollback/movie machinery already rides the existing `on_serialize`/`on_unserialize` hooks. **DONE as of 2026-07-21, re-measured 2026-09-20** — this bullet described the upstream half as pending for two months after it landed. Companion PRs `libretro/docs#1164` (merged 2026-07-21) and `libretro/libretro-super#2021` (merged 2026-07-24) are both in, the mirror + buildbot step the libretro team owned is complete, and `rustynes_libretro.so.zip` is on the nightly buildbot for **linux/x86_64, windows/x86_64 and apple/osx/arm64** (checked by fetching the three `latest/` listings, not by asking anyone). `dist/info/rustynes_libretro.info` upstream reads `license = "GPLv3+"`, `display_version = "v2.3.9"`. **Issue #311 is CLOSED and that is now CORRECT** — but read its timeline before citing it, because it closed twice for opposite reasons: `2026-07-19T22:28` by doublegate via a commit-body keyword (premature, reopened 27 minutes later at `22:55`), then `2026-07-21T13:07` **by `hizzlekizzle`, an upstream maintainer, by hand and with no commit id** — which is exactly the condition this bullet demanded. **The keyword rule still stands and the timeline is its evidence:** never put a closing keyword for an unfinished issue in a commit or PR body, because the first close proves GitHub acts on it instantly and the tracker then reports finished work that was not done. What retires an issue is a verified outcome — here, three buildbot listings and a maintainer's own click.
- **The libretro buildbot is a THIRD CI system with its own rules — and the pinned toolchain fights it.** The first real run (pipeline #91899, 2026-07-20) passed 1 of 10 jobs. `rust-toolchain.toml`'s `channel = "1.96.0"` makes rustup install a *fresh* toolchain inside libretro's build image, bypassing the image's pre-provisioned cross targets, so 8 jobs died on `E0463: can't find crate for core`; each job in `.gitlab-ci.yml` now runs `rustup target add ${RUST_TARGET}` (NOT added to `rust-toolchain.toml`'s `targets` — that would cost every contributor and GH Actions job ~8 extra `rust-std` downloads). The Apple jobs must use `!reference` rather than `extends` for that, because GitLab's `extends` REPLACES array keys and would silently drop the templates' `SDKROOT`/`STRIP`/`CC`/`CXX` exports. **tvOS: the upstream template's `cargo +nightly build -Zbuild-std` override is OBSOLETE — don't reinstate it.** It dates from when `aarch64-apple-tvos` was tier 3 with no distributed `rust-std`; the target has since been promoted and rustup ships a complete prebuilt std **including `panic_abort`** (verified on the pinned 1.96.0: `rustup target add aarch64-apple-tvos` gives 26 rlibs and the crate `cargo check`s clean, bindgen included). Our job overrides `script` back to `!reference [.libretro-rust-apple-base, script]`, putting tvOS on the same pinned stable as every other job. That one change dissolved THREE stacked workarounds the `+nightly` path had forced: a nightly-channel reinstall (`+nightly` outranks both `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`, so the job rode the image's stale 1.94.0-nightly, below our MSRV); `CARGO_PROFILE_RELEASE_PANIC=unwind` (bare `-Zbuild-std` omits `panic_abort`, and `CARGO_UNSTABLE_BUILD_STD` does NOT override the hardcoded crate list — the CLI `-Z` flag wins); and clearing the image's `-C ar` (see the next bullet). (Since v2.8.0 that same variable is set to `unwind` again for EVERY buildbot job, tvOS included, by `.core-defs` — deliberately, for panic containment (L-1.1), not as the old tvOS workaround; `panic_abort` being available does not make the abort profile effective there.) Worth reporting upstream: every Rust core's tvOS job could drop `+nightly` the same way. **A green GitHub Actions run does not imply a green buildbot** — the `libretro-cross` CI job (the buildbot triples a Linux runner can model — at #554, 2026-09-24: 64- and 32-bit MinGW-Windows, Linux aarch64 / i686, armhf, the webOS `armv7-unknown-linux-gnueabi`, and Android/NDK; this parenthesis first said "MinGW-Windows and Android/NDK", which had been stale since the aarch64 and armhf legs landed; the Apple families are deliberately excluded, as bindgen needs a real per-target sysroot and there is no Apple SDK on a Linux runner) is the early-warning gate; before touching anything libretro-related, cross-check `cargo check --release -p rustynes-libretro --target ` locally.
- **The libretro build image injects `-C ar` into EVERY Apple job, and it is a hard error from Rust 1.97 — a bomb armed against the next MSRV bump.** The image (not the `rust-apple.yml` template, which sets no `RUSTFLAGS` at all, and not our `.cargo/config.toml`) adds `-Car=,Clink-arg=-undefined,Clink-arg=dynamic_lookup,-rpath=` to osx-x64 / osx-arm64 / ios-arm64 / tvos-arm64. `-C ar` was a deprecated no-op for years and became a **hard error in 1.97** (bisected locally: 1.93.0-nightly / 1.96.0 / 1.96.1 warn; 1.97.1 and 1.99.0-nightly error). No job trips it today — all four Apple jobs are on the pinned 1.96.0 and merely log the warning. **The day `rust-toolchain.toml` moves to 1.97+, all four fail together** — the warning lives in that file, at the line someone would edit. Discarding the flags is behaviour-preserving, not a gamble: rustc splits `-C` at the FIRST `=`, so the whole comma-joined string is swallowed as the `ar` value and those link args have never reached the linker for *any* core (cargo prints it as one argv token), and two upstream Rust cores have green tvOS jobs on the same image with the same dead token. The override works without knowing where the image sets it because cargo takes rustflags from exactly one source, first match wins: `CARGO_ENCODED_RUSTFLAGS` → `RUSTFLAGS` → `target..rustflags` → `build.rustflags` (verified locally against a global `~/.cargo/config.toml` `build.rustflags`: `RUSTFLAGS=""` removes every injected `-C`, and empty means zero flags, not one empty argument).
+- **v3.0.1 SPLIT THE TOOLCHAIN instead of disarming the `-C ar` bomb.** `rust-toolchain.toml` moved to 1.99.0; `.gitlab-ci.yml` `.core-defs` sets `RUSTUP_TOOLCHAIN: "1.96.0"` (it outranks `rust-toolchain.toml`), and every job's `rustup target add` became `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}`. The libretro templates set no `RUSTUP_TOOLCHAIN` or rustflags (checked in `rust-apple.yml`, `rust-linux-x64.yml`, `rust-windows-x64.yml`, `rust-android-jni.yml`, `rust-webos.yml`, 2026-10-06). The seven crates the core compiles declare `rust-version = "1.96"`, and GitHub CI's `libretro-cross` reads the version out of `.gitlab-ci.yml` (no literal in `.github/`) and builds on it. **That check has already paid for itself:** clippy 1.99 rewrote `for b in self.ram.iter_mut()` (a `Box<[u8; 2048]>`) to `for b in &mut self.ram`, which 1.96 rejects (`&mut Box<[T; N]>` is not `IntoIterator` there); clippy honoured nothing about the crate's `rust-version`, and only the 1.96 build caught it (`bus.rs`, fixed as `&mut *self.ram`). The documented two-line `RUSTFLAGS` fix in `rust-toolchain.toml` would let the buildbot follow, but it was not taken blind: no buildbot pipeline can run before `main` moves.
- **`rust-libretro 0.3.2` is unmaintained (no commit since 2023-02) and has a MinGW bug we work around.** It casts a keycode with `cfg(target_family = "windows")`, but C enum signedness follows the *ABI*: only **MSVC** gives plain enums `int` — under **MinGW** (`x86_64-pc-windows-gnu`, what the buildbot builds) bindgen emits `c_uint` and the crate fails `E0308`. `.cargo/config.toml`'s `[env] BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu = "--target=x86_64-pc-windows-msvc"` fixes it; the generated-bindings diff is 28 lines, all enum signedness. Don't "clean up" that env var without rebuilding for `x86_64-pc-windows-gnu`.
- **The libretro `.info` RetroArch reads is a DIFFERENT FILE from this repo's, and it went stale for eleven days.** RetroArch downloads `dist/info/rustynes_libretro.info` from `libretro/libretro-super`; `crates/rustynes-libretro/rustynes_libretro.info` is an unrelated copy that nothing syncs and nothing compared. So the v2.2.9 GPL relicense reached `Cargo.toml`, `NOTICE`, `deny.toml`, the SPDX headers and the local `.info` — and **not** the file users actually see, which went on advertising "MIT OR Apache-2.0" at `display_version = v2.2.1`. Both upstream PRs had merged *exactly two weeks before* the relicense, so no sync could have carried it. **Upstream PRs are opened only on MINOR or MAJOR releases** — a `vX.Y.0` where `X` or `Y` changed. Patch releases do NOT trigger an upstream sync; the `.info` `display_version` is allowed to lag through a `v2.4.1`..`v2.4.9` run and is brought current at the next `vX.Y.0`. **Next scheduled upstream sync: v3.0.0, prepared at v2.9.9 and submitted with v3.0.0** (ADR 0043, 2026-09-29, which replaced the 2026-08-23 rule below that it would wait for the MiSTer core to be complete). History, superseded by ADR 0043: the 2026-08-23 decision deferred it past v2.5.0, superseding a 2026-08-20 decision that named v2.5.0; it held that the sync happens at whatever `vX.Y.0` the MiSTer core is fully implemented at, not at the next `vX.Y.0` on the calendar, so v2.5.0 opened no upstream PR and no release before the core was done would. Its reason, which ADR 0043 answered by labelling the v3.0.0 bitstreams not hardware-verified, was that the Fabric line's user-visible claims are about a core that does not exist yet, and an `.info` advertising a version whose headline feature is unfinished is the same class of stale declaration this bullet exists to prevent — just in the other direction. The one override is a **licence change, which syncs immediately** regardless of version — that is what this bullet's incident was about, and it stays on the same footing as a release. `crates/rustynes-test-harness/tests/libretro_info_audit.rs` pins the local file against the workspace manifest so the sync is a *copy*, never a re-derivation; it cannot see upstream, so the sync itself stays a human step. libretro `.info` uses short license tokens, not SPDX, and marks "or later" with a trailing `+` (tallied across all 316 upstream cores: `GPLv2` x100, `GPLv3` x64, `GPLv2+` x19, `GPLv3+` x5) — RustyNES is **`GPLv3+`**; a bare `GPLv3` understates it as GPL-3.0-only. Full detail + the surface table: `docs/libretro/UPSTREAM_SYNC.md`.
- **iOS/iPadOS/tvOS availability is a THIRD repo and a HARDCODED list — being on the buildbot buys nothing there.** **RESOLVED 2026-08-16** by `libretro/RetroArch#19416` (merged `76f60626984a`; verified against `master`, not the PR state — `rustynes` sits at line 268 between `reminiscence` and `sameboy`). The mechanism below is retained because it recurs for any other core and for the sibling forges, and because "in the build list" is not "installable": it ships with the next App Store RetroArch build. iOS cannot download cores (Apple bans fetching executable code), so the App Store build bundles a fixed set chosen by `pkg/apple/update-cores.sh` in `libretro/RetroArch`. That script has two lists: `allcores`, fetched *dynamically* from the buildbot directory (RustyNES is in it automatically), and `appstore_cores`, a hardcoded array (RustyNES is **absent**). The iOS/tvOS build phases run `rm -f ${SRCROOT}//modules/*.dylib` then `./update-cores.sh appstore`, so only the hardcoded list survives. One entry covers iOS + tvOS + macOS App Store. **Alphabetical order is mandatory** — `rustynes` sorts between `reminiscence` and `sameboy`; re-check the neighbours at submission time rather than trusting a line number.
diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md
index ee0dc317..12df0751 100644
--- a/docs/build-and-tooling.md
+++ b/docs/build-and-tooling.md
@@ -5,8 +5,8 @@
## Toolchain
- **Rust edition**: 2024.
-- **MSRV (minimum supported Rust version)**: 1.96.0. Pinned via `rust-toolchain.toml`. (Bumped from 1.86 in v1.3.0 "Bedrock" to unblock the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier.)
-- **Channel**: the pinned `1.96.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), the libretro buildbot builds all ten of its jobs on it, and local builds pick it up automatically as a directory override. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
+- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate except the seven the libretro core builds (`rustynes-{cpu,ppu,apu,mappers,core,gamedb,libretro}`), which declare 1.96 because the libretro buildbot stays on 1.96.0 (v3.0.1; see `.gitlab-ci.yml`). (History: 1.86 until v1.3.0 "Bedrock", which moved to 1.96 for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier; 1.96 until v3.0.1.)
+- **Channel**: the pinned `1.99.0` stable release — *not* a floating `stable`. `rust-toolchain.toml` is the single source of truth: every GitHub Actions job resolves its toolchain from that file (`.github/actions/rust-setup` parses the `channel` and fails closed if it cannot), and local builds pick it up automatically as a directory override. **One exception:** the libretro buildbot runs 1.96.0, set by `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file), because its build image passes `-C ar`, a hard error from Rust 1.97. CI's `libretro-cross` job reads that value from `.gitlab-ci.yml` and builds on it, so the 1.96 floor is checked on every PR. There is no `toolchain:` version literal anywhere in `.github/`, so bumping the pin is a one-line edit here — but read the `-C ar` warning in `rust-toolchain.toml` before bumping to 1.97 or newer.
- **Nightly** is used for exactly one thing, outside CI and not a gate: `cargo fuzz`, which requires it for the sanitizer flags it threads through `rustc` (`cargo +nightly fuzz run ` — see `fuzz/README.md`). No build, test, lint, docs, release, or packaging path uses nightly.
- **Targets supported**: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`. Tier 2: `aarch64-unknown-linux-gnu`. Cross-compile targets declared in `rust-toolchain.toml` (auto-installed): `thumbv7em-none-eabihf` (the `no_std` chip-stack gate) and `wasm32-unknown-unknown` (browser). Android arm64/arm/x86_64 via `cargo ndk` (see `docs/android.md`). The `x86_64-apple-darwin` release target was retired (ADR 0009).
@@ -14,7 +14,7 @@
```text
Cargo.toml # workspace manifest
-rust-toolchain.toml # pin 1.96.0 stable + thumbv7em + wasm32 targets
+rust-toolchain.toml # pin 1.99.0 stable + thumbv7em + wasm32 targets
crates/
├── rustynes-core/ # public re-exports + Nes facade + scheduler + save state
├── rustynes-cpu/ # 2A03 CPU
@@ -148,7 +148,7 @@ cargo build -p rustynes-core --target thumbv7em-none-eabihf --no-default-feature
- `actions/rust-setup/action.yml` — shared composite action (toolchain +
Linux wgpu/winit/cpal deps + cargo cache) used by all three workflows, so
the setup steps + the apt package list live in exactly one place.
-- `workflows/ci.yml` — lint (fmt + clippy + rustdoc on the pinned 1.96
+- `workflows/ci.yml` — lint (fmt + clippy + rustdoc on the pinned 1.99
toolchain, so the gate matches local) + the cross-platform test matrix +
test-roms + no_std + wasm32 clippy + the frame-time bench gate. Runs the
feature-combo clippy gates (`scripting`, `hd-pack`, `retroachievements`, and
diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md
index 318dbe93..4eba091f 100644
--- a/docs/dev/BUILD.md
+++ b/docs/dev/BUILD.md
@@ -21,9 +21,10 @@
### Required
-- **Rust** 1.96.0 (pinned in `rust-toolchain.toml`; the channel auto-installs).
- Edition 2024. MSRV 1.96 unblocks the edition-2024 + egui 0.34.3 / wgpu 29 /
- rfd 0.17.2 dependency tier (bumped from 1.86 in v1.3.0 "Bedrock").
+- **Rust** 1.99.0 (pinned in `rust-toolchain.toml`; the channel auto-installs). The libretro core's crates must also build on 1.96.0, which the libretro buildbot uses: `RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p rustynes-libretro`.
+ Edition 2024. (The pin was 1.86 until v1.3.0 "Bedrock", which moved to 1.96
+ for the edition-2024 + egui 0.34.3 / wgpu 29 / rfd 0.17.2 dependency tier,
+ and 1.96 until v3.0.1.)
- **Cargo** (included with Rust).
### System libraries
@@ -44,14 +45,14 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
**Or visit**:
-The pinned toolchain (1.96.0) and the cross-compile targets
+The pinned toolchain (1.99.0) and the cross-compile targets
(`thumbv7em-none-eabihf` and `wasm32-unknown-unknown`) are all declared in
`rust-toolchain.toml`, so `rustup` installs them automatically on first build.
### Verify Installation
```bash
-rustc --version # Should report 1.96.0 (the pinned channel)
+rustc --version # Should report 1.99.0 (the pinned channel)
cargo --version
```
diff --git a/docs/dev/STYLE_GUIDE.md b/docs/dev/STYLE_GUIDE.md
index 7ffc833b..68b9ce73 100644
--- a/docs/dev/STYLE_GUIDE.md
+++ b/docs/dev/STYLE_GUIDE.md
@@ -16,7 +16,7 @@ Coding conventions and best practices for contributing to RustyNES.
```toml
[package]
edition = "2024"
-rust-version = "1.96" # Minimum Supported Rust Version (pinned in rust-toolchain.toml)
+rust-version.workspace = true # 1.99; the seven libretro-path crates declare "1.96"
```
### Formatting
diff --git a/docs/testing-strategy.md b/docs/testing-strategy.md
index 98ef4c03..ba4316ea 100644
--- a/docs/testing-strategy.md
+++ b/docs/testing-strategy.md
@@ -209,7 +209,7 @@ fails if any job it aggregates failed):
feature set the project enumerates (including `retroachievements`, `full` and
both wasm32 builds), rustdoc `-D warnings`.
- **Tests:** `cargo test --workspace` on Linux (plus macOS and Windows on full
- runs), on the pinned toolchain (`rust-toolchain.toml`, 1.96). Since v2.9.4 the
+ runs), on the pinned toolchain (`rust-toolchain.toml`, 1.99). Since v2.9.4 the
Linux leg also runs the tests behind non-default features: the frontend with
`full`, `rustynes-core` and `rustynes-ppu` with `debug-hooks,hd-pack`,
`rustynes-apu` with `debug-hooks`, `rustynes-script` with
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 99657fde..a9b92927 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -34,7 +34,7 @@ file with no install step.
### Option 2: build from source
-Building from source requires Rust 1.96 (pinned via `rust-toolchain.toml`)
+Building from source requires Rust 1.99 (pinned via `rust-toolchain.toml`)
and a working system-library set for the windowing / audio stack.
**Linux build deps (Debian / Ubuntu):**
diff --git a/ios/README.md b/ios/README.md
index ba80a256..724b0b88 100644
--- a/ios/README.md
+++ b/ios/README.md
@@ -73,7 +73,7 @@ the gamepad mapper both build this mask and feed it to
Prerequisites (macOS with Xcode):
- Xcode 15 or newer.
-- The Rust toolchain pinned by `rust-toolchain.toml` (1.96).
+- The Rust toolchain pinned by `rust-toolchain.toml` (1.99).
- The iOS Rust targets (the build script adds them):
`aarch64-apple-ios`, `aarch64-apple-ios-sim`, `x86_64-apple-ios`.
- XcodeGen: `brew install xcodegen`.
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 0ef487aa..4b4a19c3 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,5 +1,5 @@
[toolchain]
-channel = "1.96.0"
+channel = "1.99.0"
components = ["rustfmt", "clippy"]
# Declare cross-compile targets here so rustup auto-installs them
# whenever any cargo invocation reads this file. Both CI jobs that
@@ -26,15 +26,23 @@ components = ["rustfmt", "clippy"]
# per-job by `.gitlab-ci.yml` instead, keeping that cost where the need
# is. See the post-mortem comment at the top of `.gitlab-ci.yml`.
#
-# BEFORE BUMPING `channel` TO 1.97 OR NEWER, read this. The libretro build
-# image injects `-Car=,Clink-arg=...` into EVERY Apple job, and `-C ar`
-# — a deprecated no-op for years — became a hard error in Rust 1.97:
+# v3.0.1 moved this pin from 1.96.0 to 1.99.0 (the newest stable on
+# 2026-10-06) and left the libretro buildbot on 1.96.0: `.gitlab-ci.yml` sets
+# `RUSTUP_TOOLCHAIN: "1.96.0"`, which outranks this file, and the seven crates
+# that core compiles keep `rust-version = "1.96"`, checked by CI's
+# `libretro-cross` job. The history below is why the buildbot cannot follow
+# yet, and what would let it.
+#
+# The libretro build image injects `-Car=,Clink-arg=...` into EVERY
+# Apple job, and `-C ar` — a deprecated no-op for years — became a hard
+# error in Rust 1.97:
#
# error: `-C ar`: this option has been removed
#
# No job trips it at 1.96.0 — all four Apple jobs merely log the warning.
-# The moment this pin reaches 1.97+, all four (`libretro-build-osx-x64`,
-# `-osx-arm64`, `-ios-arm64`, `-tvos-arm64`) fail together. The fix is two
+# The moment the buildbot's toolchain reaches 1.97+, all four
+# (`libretro-build-osx-x64`, `-osx-arm64`, `-ios-arm64`, `-tvos-arm64`)
+# fail together. The fix is two
# lines in each job's `before_script`:
#
# - unset CARGO_ENCODED_RUSTFLAGS
From aa55364d70ad425a49b3c34fdd30e156ce2d1e22 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:14:40 -0400
Subject: [PATCH 03/44] docs(plan): the v3.0.1 plan, and the odd-frame A12 task
closed with what it found
to-dos/plans/v3.0.1-plan.md records the maintainer's five-part scope for the
first patch on v3.0.0 (the palette A/B, T-GA23C-CHRRAM, the odd-frame A12
stimulus, every dependency and toolchain bump, and every unanswered bot review
comment back to #1), a measurable gate for each, and the outcome so far. The
file has no codename yet; the maintainer chooses it at the cut.
to-dos/mister/TASKS.md: the odd-frame stimulus item is DONE (sibling fdb48ec).
Its two predictions are corrected openly rather than overwritten: the window
is pre-render dots 333-336, not 337-339 (rendering takes effect a few dots
after the write, so a write at 337-339 usually lands too late for the skip),
and the bitstream does change, because fixing the dot-0 rule is RTL. A new
open item records the one finding the bot sweep left UNSURE: whether a $2006
copy landing on a v_pipeline load dot matches the oracle (ordering confirmed,
divergence unmeasured, needs a dot-257 or increment-dot stimulus).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
to-dos/mister/TASKS.md | 23 +++++++++++++++++++++-
to-dos/plans/v3.0.1-plan.md | 39 +++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 1 deletion(-)
create mode 100644 to-dos/plans/v3.0.1-plan.md
diff --git a/to-dos/mister/TASKS.md b/to-dos/mister/TASKS.md
index b1a0cb95..914060ae 100644
--- a/to-dos/mister/TASKS.md
+++ b/to-dos/mister/TASKS.md
@@ -328,7 +328,28 @@ something as blocked, check the blocker applies to the WHOLE item.**
it is written**: before recording something as blocked, check the blocker
applies to the whole item. It applied to *hardware acceptance*, not to
building the controller or to verifying it against a documented part.
-- [ ] **A stimulus for the odd-frame exception to the dot-0 A12 rule**
+- [x] **DONE v3.0.1 — and it found a DUT defect first.** The sibling's
+ `mapper4mmc3oddskip080` (`tb/roms/mkmapper.py mmc3oddskip`) turns
+ rendering off at vblank and on near the pre-render line's end with a
+ latch-0 IRQ armed; the oracle's trace counts 3 of 100 frames that reach
+ the exception. Before it could gate the exception it showed the rule
+ itself was gated on the live `rendering` at dots 1-2, so a `$2001` write
+ taking effect at dot 2 got the rule; fixed to "was dot 1 rendering"
+ (sibling ledger 3.49). The exception's mutant is CAUGHT on the new
+ `IRQ_RISE` surface (`tb/irq_rise_diff.py`): the bus alone cannot see a
+ one-cycle /IRQ shift. Two predictions below were wrong: the window is
+ dots 333-336, not 337-339 (rendering takes effect a few dots after the
+ write, and a write in 337-339 usually lands too late for the skip), and
+ the bitstream DOES change, because the rule's fix is RTL. The original
+ item follows.
+- [ ] **Does a `$2006` `v <- t` copy on a `v_pipeline` load dot match the
+ oracle?** (raised by agy on sibling #28, left UNSURE by the v3.0.1
+ sweep). The ordering is confirmed: the copy commits before the pipeline
+ in the same dot, so the pipeline's value wins on a load dot, and that
+ precedence predates `v_pipeline`. Whether it diverges from the oracle is
+ unmeasured; settling it needs a stimulus that lands the copy on dot 257
+ or on an increment dot, compared per cycle (fetch or bus gate).
+- [x] **A stimulus for the odd-frame exception to the dot-0 A12 rule**
(T-MMC3-BG-A12; raised by Copilot on sibling #53, 2026-10-06). The
RTL's exception for scanline 0 after an odd frame's skip is
**stimulus-blind**, not inert. blargg's `4-scanline_timing`
diff --git a/to-dos/plans/v3.0.1-plan.md b/to-dos/plans/v3.0.1-plan.md
new file mode 100644
index 00000000..0f6881ca
--- /dev/null
+++ b/to-dos/plans/v3.0.1-plan.md
@@ -0,0 +1,39 @@
+
+# v3.0.1 — the open items, the toolchain, and every unanswered review
+
+The first patch on v3.0.0 "Cornerstone". The maintainer's scope (2026-10-06):
+
+1. the **palette-offset A/B** (`8f449691` → `33ea0572`), which needs a quiet host;
+2. **T-GA23C-CHRRAM** (*Famicom Yarou Vol.1*'s CHR-RAM);
+3. the **odd-frame A12 stimulus** for the MiSTer core (`to-dos/mister/TASKS.md`);
+4. **every dependency and toolchain bump** in both repositories, Dependabot's
+ PR #584 included, except the parts that must stay on Rust 1.96;
+5. **every bot review comment** on every PR in both repositories, back to #1,
+ that never got a reply: adjudicated against the current code, answered,
+ and resolved, with the still-valid ones fixed.
+
+## Gates (how each is known done)
+
+| # | item | gate |
+| --- | --- | --- |
+| 1 | palette A/B | two independent `ab_check.sh` runs of `33ea0572` against `8f449691`, each started on a quiet host (one- and five-minute load under 1.5), order-bias control within ±2% or the run is void; the verdict recorded in `docs/performance.md` either way |
+| 2 | T-GA23C-CHRRAM | a red-first test of the traced upload sequence; Vol.1 boots to its menu; the other mapper 45 dumps unchanged; `EMULATION_EPOCH` raised (ADR 0045's trigger) |
+| 3 | odd-frame stimulus | a generated ROM whose writes REACH the exception (counted from the oracle's trace, not assumed), the DUT exact against the oracle, and the exception's mutant CAUGHT |
+| 4 | dependencies | every gate CI runs, on the new toolchain, plus a build of each changed ecosystem (Android, web, docs, Docker); the libretro path built on 1.96 |
+| 5 | bot sweep | every unanswered thread, review-body finding and agy review has a verdict with evidence; still-valid ones fixed; every reply posted and every open thread resolved |
+
+## Outcome
+
+| # | outcome | evidence |
+| --- | --- | --- |
+| 1 | pending a quiet host | the runner waits on load and a `BUSY` handshake with every heavy job |
+| 2 | done | `34b3d342`: unbanked per NES 2.0 mapper 372's page ("CHR-RAM (1, unbanked)"); Vol.1 draws its "7 IN 1" menu; epoch 1 → 2 |
+| 3 | done, and it found a DUT defect | sibling `fdb48ec`: `mapper4mmc3oddskip080` reaches the exception on 3 of 100 frames; the rule's live `rendering` gate was wrong (ledger 3.49), fixed to "was dot 1 rendering"; the exception's mutant CAUGHT on the new `IRQ_RISE` surface (`tb/irq_rise_diff.py`) |
+| 4 | done | `ec5927bb` (Rust 1.99, libretro buildbot on 1.96 via `RUSTUP_TOOLCHAIN`, every crate and action, Android, web, Docker, pre-commit); sibling `25656ab` (runner pinned, Verilator 5.020 → 5.032 under review) |
+| 5 | in progress | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 being fixed |
+
+## What the sweep is NOT
+
+Bot summaries with no findings (CodeRabbit walkthroughs, Gemini quota notices
+and change summaries, Dependabot) are informational and get no reply. A
+finding answered by a later comment of ours is not re-answered.
From 37e640685f5bcefb296bd51b15a22d1d9b77f3ee Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:30:29 -0400
Subject: [PATCH 04/44] docs(plan): name v3.0.1 "Mortar"
The maintainer chose the codename (2026-10-06); the plan file takes it, as
every earlier plan does (to-dos/plans/vX.Y.Z--plan.md).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
to-dos/plans/{v3.0.1-plan.md => v3.0.1-mortar-plan.md} | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
rename to-dos/plans/{v3.0.1-plan.md => v3.0.1-mortar-plan.md} (93%)
diff --git a/to-dos/plans/v3.0.1-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md
similarity index 93%
rename from to-dos/plans/v3.0.1-plan.md
rename to to-dos/plans/v3.0.1-mortar-plan.md
index 0f6881ca..c336af21 100644
--- a/to-dos/plans/v3.0.1-plan.md
+++ b/to-dos/plans/v3.0.1-mortar-plan.md
@@ -1,7 +1,8 @@
-# v3.0.1 — the open items, the toolchain, and every unanswered review
+# v3.0.1 "Mortar" — the open items, the toolchain, and every unanswered review
-The first patch on v3.0.0 "Cornerstone". The maintainer's scope (2026-10-06):
+The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer,
+2026-10-06). The maintainer's scope (2026-10-06):
1. the **palette-offset A/B** (`8f449691` → `33ea0572`), which needs a quiet host;
2. **T-GA23C-CHRRAM** (*Famicom Yarou Vol.1*'s CHR-RAM);
From 042a205e469e100240da3bd647ce914f4a8bdd44 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:33:07 -0400
Subject: [PATCH 05/44] docs(plan): cite the v3.0.1 commits by their final
hashes
The release-branch commits were re-made to add the session attribution
trailers the first ones lacked (unpushed, re-signed, trees unchanged), so
the hashes the plan quoted no longer existed.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
to-dos/plans/v3.0.1-mortar-plan.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/to-dos/plans/v3.0.1-mortar-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md
index c336af21..7a25cd2f 100644
--- a/to-dos/plans/v3.0.1-mortar-plan.md
+++ b/to-dos/plans/v3.0.1-mortar-plan.md
@@ -28,9 +28,9 @@ The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer,
| # | outcome | evidence |
| --- | --- | --- |
| 1 | pending a quiet host | the runner waits on load and a `BUSY` handshake with every heavy job |
-| 2 | done | `34b3d342`: unbanked per NES 2.0 mapper 372's page ("CHR-RAM (1, unbanked)"); Vol.1 draws its "7 IN 1" menu; epoch 1 → 2 |
-| 3 | done, and it found a DUT defect | sibling `fdb48ec`: `mapper4mmc3oddskip080` reaches the exception on 3 of 100 frames; the rule's live `rendering` gate was wrong (ledger 3.49), fixed to "was dot 1 rendering"; the exception's mutant CAUGHT on the new `IRQ_RISE` surface (`tb/irq_rise_diff.py`) |
-| 4 | done | `ec5927bb` (Rust 1.99, libretro buildbot on 1.96 via `RUSTUP_TOOLCHAIN`, every crate and action, Android, web, Docker, pre-commit); sibling `25656ab` (runner pinned, Verilator 5.020 → 5.032 under review) |
+| 2 | done | `c986411f`: unbanked per NES 2.0 mapper 372's page ("CHR-RAM (1, unbanked)"); Vol.1 draws its "7 IN 1" menu; epoch 1 → 2 |
+| 3 | done, and it found a DUT defect | sibling `684af85`: `mapper4mmc3oddskip080` reaches the exception on 3 of 100 frames; the rule's live `rendering` gate was wrong (ledger 3.49), fixed to "was dot 1 rendering"; the exception's mutant CAUGHT on the new `IRQ_RISE` surface (`tb/irq_rise_diff.py`) |
+| 4 | done | `4aae8c13` (Rust 1.99, libretro buildbot on 1.96 via `RUSTUP_TOOLCHAIN`, every crate and action, Android, web, Docker, pre-commit); sibling `e77050d` (runner pinned, Verilator 5.020 → 5.032 under review) |
| 5 | in progress | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 being fixed |
## What the sweep is NOT
From 9b879cc459798657a86092da401ee57448b09249 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:14:07 -0400
Subject: [PATCH 06/44] ci(dependabot): one chore(deps) scope, and group the
egui tier instead of holding it
Two defects in .github/dependabot.yml, both still present on v3.0.0.
Doubled scope. The cargo, gradle and cosim-cargo entries set
`prefix: "chore(deps)"` together with `include: "scope"`. Dependabot
appends the dependency-type scope (`deps` / `deps-dev`) to the prefix
itself (GitHub's dependabot options reference: "prefix followed by the
type of dependencies updated"), so every Dependabot PR was titled
`chore(deps)(deps): ...` (#565-#569 most recently). The three prefixes
are now `chore`; `include: "scope"` supplies `(deps)` once. The
github-actions entry (`chore(ci)`, no include) was correct and is
unchanged.
Stale hold. The root cargo entry still ignored egui, egui-wgpu and
egui-winit `>=0.36` and wgpu and naga `>=30`, with a comment saying the
tier was held at 0.35. The workspace moved to egui 0.36 / wgpu 30 on
2026-09-28 (vendored egui-winit; Cargo.toml lines 167-205, Cargo.lock
egui 0.36.2, wgpu 30.0.1), so the ignore block was silently suppressing
every future egui and wgpu release. The coupling it protected is real
(Cargo.toml: two wgpu majors break every device/queue handoff, and naga
is a direct frontend dependency that must match wgpu), so the five
entries become an `egui-wgpu-tier` group that bumps them in one PR.
It is listed first and has no `update-types`, because Dependabot puts a
dependency in the first group it matches; below the minor/patch
catch-all groups, a minor bump of one member would land alone. The
comment records the hold as dated history and points at the vendored
egui-winit, which a bump past 0.36.2 bypasses.
Verified: `pre-commit run check-yaml` passes; validated against the
SchemaStore dependabot-2.0 schema with jsonschema. The only schema
error is the pre-existing `reviewers` key (also on HEAD; not touched
here); with it removed both HEAD and this file validate clean.
Bot findings: RustyNES#390/A/5314720292/4, RustyNES#391/A/5314746008/3,
RustyNES#541/A/5758822410/2, RustyNES#543/A/5758833866/1 (doubled
scope); RustyNES#397/A/5323299625/1 (stale egui/wgpu hold).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.github/dependabot.yml | 65 +++++++++++++++++++-----------------------
1 file changed, 29 insertions(+), 36 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3ca59a49..5ea830c1 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -13,47 +13,40 @@ updates:
labels:
- "dependencies"
- "rust"
+ # The prefix carries no scope: `include: "scope"` appends `(deps)` (or
+ # `(deps-dev)`) itself, so a `chore(deps)` prefix titled every PR
+ # `chore(deps)(deps): ...` until v3.0.1.
commit-message:
- prefix: "chore(deps)"
+ prefix: "chore"
include: "scope"
reviewers:
- "doublegate"
assignees:
- "doublegate"
- # HOLD the egui tier. A comment in `Cargo.toml` explains WHY the pin exists,
- # but a comment does not reach Dependabot -- it would keep re-opening the
- # same three PRs every Monday, and the risk is not the noise, it is that the
- # twentieth identical PR gets merged on the assumption it is routine.
- #
- # `egui-winit` 0.36.1 does not compile for `wasm32-unknown-unknown`, and
- # RustyNES ships a wasm demo:
- #
- # error[E0407]: method `bytes` is not a member of trait `egui::DroppedFile`
- #
- # egui 0.36 split `DroppedFile` by target (`bytes_async` on wasm, `bytes` on
- # native) while egui-winit's `NativeFile` impl has no cfg gate, so on wasm it
- # implements a method the trait does not declare and omits the one it does.
- # `wgpu` is held with them only because `egui-wgpu` 0.35 pins it.
- #
- # REMOVE ALL FOUR ENTRIES once upstream ships the fix -- this is a hold, not
- # a policy. Re-check by bumping and running both wasm clippy invocations.
- ignore:
- - dependency-name: "egui"
- versions: [">=0.36"]
- - dependency-name: "egui-wgpu"
- versions: [">=0.36"]
- - dependency-name: "egui-winit"
- versions: [">=0.36"]
- - dependency-name: "wgpu"
- versions: [">=30"]
- # `naga` is a DIRECT dependency of `rustynes-frontend` (WGSL validation),
- # not merely a wgpu transitive, so Dependabot would propose 30 on its own
- # and the hold would be broken from a direction the four entries above do
- # not cover. Caught in review on the v2.6.3 refresh.
- - dependency-name: "naga"
- versions: [">=30"]
- # Group minor and patch updates together
+ # From 2026-09-12 to 2026-09-28 an `ignore` block here held egui,
+ # egui-wgpu, egui-winit (`>=0.36`), wgpu and naga (`>=30`) at the 0.35 tier,
+ # because egui-winit 0.36 did not compile for wasm32. The tier then moved to
+ # 0.36 / wgpu 30 with egui-winit vendored (see `Cargo.toml`), and the hold
+ # stayed behind until v3.0.1 -- silently blocking every later egui and wgpu
+ # release. The coupling it protected is real, so it is now a GROUP instead.
groups:
+ # ONE PR FOR THE WHOLE TIER. `Cargo.toml` says these five move together:
+ # `egui-wgpu` pins one wgpu major, a graph holding two wgpu majors fails
+ # every device/queue handoff, and `naga` is a DIRECT dependency of
+ # `rustynes-frontend` (WGSL validation) that must match wgpu's. Listed
+ # FIRST, with no `update-types`, because Dependabot puts a dependency in
+ # the first group it matches -- below the catch-all groups, a minor bump
+ # of one of these would land alone in `production-dependencies`. Before
+ # merging one: egui-winit is vendored (`vendor/egui-winit/VENDORED.md`),
+ # and a bump past 0.36.2 bypasses that patch, so run both wasm clippy gates.
+ egui-wgpu-tier:
+ patterns:
+ - "egui"
+ - "egui-wgpu"
+ - "egui-winit"
+ - "wgpu"
+ - "naga"
+ # Group minor and patch updates together
development-dependencies:
dependency-type: "development"
update-types:
@@ -119,7 +112,7 @@ updates:
- "dependencies"
- "android"
commit-message:
- prefix: "chore(deps)"
+ prefix: "chore"
include: "scope"
# `crates/rustynes-cosim` is EXCLUDED from the workspace on purpose (cargo
@@ -142,7 +135,7 @@ updates:
- "dependencies"
- "rust"
commit-message:
- prefix: "chore(deps)"
+ prefix: "chore"
include: "scope"
# Maintain GitHub Actions
From 76c167f238ba51605f09f0819d98e3fdfc202531 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:16:30 -0400
Subject: [PATCH 07/44] fix(scripts): fail closed on partial review-thread
payloads; diag output to stdout
Two scripts, both still defective on v3.0.0.
scripts/pr-review/list_unresolved_threads.py is the working list of the
bot-review closeout, and its last line, "0 unresolved thread(s)", is
what lets a merge go ahead. It read the thread list as
`(pr.get("reviewThreads") or {}).get("nodes") or []`, so a payload with
no `reviewThreads`, or a null `nodes`, printed exactly that all-clear
and exited 0 -- a fail-open gate. A partial node died on a bare
KeyError (`isResolved`, `comments`, `databaseId`) with a traceback that
hid which node and field were missing. The script now requires
`reviewThreads.nodes` to be a list and validates each node's boolean
`isResolved`, its `comments.nodes` list and the first comment's
`databaseId`, exiting through SystemExit with a message that names the
node. An empty list is still a genuine all-clear.
New scripts/pr-review/list_unresolved_threads_selftest.py runs the
REAL script as a subprocess with stdin payloads (the reply_and_resolve
selftest's precedent: test the script, not a copy of its rules). Each
refusal is asserted for its stated reason and with no traceback. Red
first: against the v3.0.0 script 5 of 7 checks FAIL (missing list, null
list, node without isResolved, without comments.nodes, comment without
databaseId); against this one, 7/7 pass. Listed in the directory README.
scripts/diag/ppu2002_read_value_histogram.py had its INPUT moved off a
predictable shared-/tmp path earlier, but still wrote its report to the
fixed `/tmp/RustyNES/an_out.txt` with a plain `open(..., 'w')`: no
private directory, no exclusive or no-follow open, so a pre-planted
symlink redirects the write. Running it on a 4-row fixture created
that file (644, 394 B). Nothing in the repository reads the file, so the
report now goes to stdout; after the change the same run prints the
report and no file is created.
Gates: ruff-check and markdownlint (pre-commit) pass on the changed
files; reply_and_resolve_selftest.py still passes.
Bot findings: RustyNES#325/B/4766773317/scripts/pr-review/list_unresolved_threads.py:49-69:174,
RustyNES#325/B/4766656411/scripts/diag/ppu2002_read_value_histogram.py:40-51:173.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
scripts/diag/ppu2002_read_value_histogram.py | 7 +-
scripts/pr-review/README.md | 1 +
scripts/pr-review/list_unresolved_threads.py | 26 +++-
.../list_unresolved_threads_selftest.py | 119 ++++++++++++++++++
4 files changed, 148 insertions(+), 5 deletions(-)
create mode 100644 scripts/pr-review/list_unresolved_threads_selftest.py
diff --git a/scripts/diag/ppu2002_read_value_histogram.py b/scripts/diag/ppu2002_read_value_histogram.py
index c12397a6..26caddce 100644
--- a/scripts/diag/ppu2002_read_value_histogram.py
+++ b/scripts/diag/ppu2002_read_value_histogram.py
@@ -47,5 +47,8 @@
if masks[0]==0xE0 and masks[3]==0x00 and masks[0]>=masks[1]>=masks[2]>=masks[3]:
sls=[r[2] for r in w]; dots=[r[3] for r in w]
out.append(" win@%d sls=%s dots=%s masks=%s"%(i,sls,dots,[hex(m) for m in masks]))
-open('/tmp/RustyNES/an_out.txt','w').write("\n".join(out)+"\n")
-print("done",len(out))
+# The report goes to stdout (redirect it where you want it). Until v3.0.1 it
+# was written to the fixed `/tmp/RustyNES/an_out.txt` -- the same shared-/tmp
+# hazard the input path above had: a pre-planted symlink there redirects the
+# write. stdout needs no directory, no open mode and no cleanup.
+sys.stdout.write("\n".join(out)+"\n")
diff --git a/scripts/pr-review/README.md b/scripts/pr-review/README.md
index fdd4aad0..ab1f2695 100644
--- a/scripts/pr-review/README.md
+++ b/scripts/pr-review/README.md
@@ -28,6 +28,7 @@ gh api graphql -f query='
| `list_all_threads.py` | Every thread with its `isResolved` / `isOutdated` flags — the audit view, for confirming nothing was missed. |
| `reply_and_resolve.py` | Applies prepared replies and resolves **only** the threads that were addressed. Dry run by default. |
| `reply_and_resolve_selftest.py` | No-network selftest of the two above it. Run it after editing either. |
+| `list_unresolved_threads_selftest.py` | No-network selftest of `list_unresolved_threads.py`: a payload with no thread list, or a partial thread node, must exit non-zero with a named error rather than print `0 unresolved thread(s)` (added v3.0.1). Run it after editing that script. |
## Writing the outcome back
diff --git a/scripts/pr-review/list_unresolved_threads.py b/scripts/pr-review/list_unresolved_threads.py
index ff4742e2..ae256cd1 100644
--- a/scripts/pr-review/list_unresolved_threads.py
+++ b/scripts/pr-review/list_unresolved_threads.py
@@ -52,15 +52,35 @@ def main() -> None:
"GraphQL response has no repository/pullRequest data "
"(check the owner/repo/pr arguments and token scope)"
)
- threads = (pr.get("reviewThreads") or {}).get("nodes") or []
+ # FAIL CLOSED. This is a closeout gate: "0 unresolved thread(s)" is the line
+ # that lets a merge go ahead, so it must mean "the payload listed threads and
+ # none was open" -- never "the payload had no thread list". Until v3.0.1 a
+ # missing or null `reviewThreads.nodes` (a query without the field, a partial
+ # response) collapsed to `[]` and printed exactly that all-clear.
+ threads = (pr.get("reviewThreads") or {}).get("nodes")
+ if not isinstance(threads, list):
+ raise SystemExit(
+ "GraphQL response has no reviewThreads.nodes list "
+ "(check that the query selects reviewThreads { nodes { ... } })"
+ )
shown = 0
- for thread in threads:
+ for i, thread in enumerate(threads):
+ # A partial node used to die on a bare KeyError/TypeError traceback;
+ # name the node and the field instead.
+ if not isinstance(thread, dict) or not isinstance(thread.get("isResolved"), bool):
+ raise SystemExit(f"review thread #{i} has no boolean isResolved")
if thread["isResolved"]:
continue
- comments = thread["comments"]["nodes"]
+ comments = (thread.get("comments") or {}).get("nodes")
+ if not isinstance(comments, list):
+ raise SystemExit(f"review thread #{i} ({safe(thread.get('id'))}) has no comments.nodes list")
if not comments:
continue
c = comments[0]
+ if not isinstance(c, dict) or c.get("databaseId") is None:
+ raise SystemExit(
+ f"review thread #{i} ({safe(thread.get('id'))}): first comment has no databaseId"
+ )
author = (c.get("author") or {}).get("login")
print(
f"TID={safe(thread['id'])} dbId={safe(c['databaseId'])} "
diff --git a/scripts/pr-review/list_unresolved_threads_selftest.py b/scripts/pr-review/list_unresolved_threads_selftest.py
new file mode 100644
index 00000000..ac06591b
--- /dev/null
+++ b/scripts/pr-review/list_unresolved_threads_selftest.py
@@ -0,0 +1,119 @@
+"""Selftest for `list_unresolved_threads.py` -- runs the real script, no network.
+
+Same precedent as `reply_and_resolve_selftest.py`: the test drives the ACTUAL
+script (here as a subprocess, feeding a payload on stdin, exactly as the
+ceremony pipes `gh api graphql` into it) rather than a copy of its rules.
+
+The cases that matter are the fail-closed ones. "0 unresolved thread(s)" is the
+line that lets a merge go ahead, so a payload WITHOUT a thread list must be an
+error, not an all-clear -- until v3.0.1 a missing or null `reviewThreads.nodes`
+printed exactly that line and exited 0.
+
+Run: `python3 scripts/pr-review/list_unresolved_threads_selftest.py`
+"""
+
+import json
+import pathlib
+import subprocess
+import sys
+
+_SCRIPT = pathlib.Path(__file__).resolve().parent / "list_unresolved_threads.py"
+
+FAILURES = []
+
+
+def check(name: str, ok: bool, detail: str = "") -> None:
+ print(f" {'ok ' if ok else 'FAIL'} {name}{(' -- ' + detail) if detail and not ok else ''}")
+ if not ok:
+ FAILURES.append(name)
+
+
+def run(doc) -> subprocess.CompletedProcess:
+ return subprocess.run(
+ [sys.executable, "-I", str(_SCRIPT)],
+ input=json.dumps(doc),
+ capture_output=True,
+ text=True,
+ encoding="utf-8",
+ check=False,
+ )
+
+
+def pr(review_threads) -> dict:
+ return {"data": {"repository": {"pullRequest": review_threads}}}
+
+
+def refused(doc, because: str) -> bool:
+ """True only if the script exited non-zero FOR THE STATED REASON, with no traceback."""
+ r = run(doc)
+ return r.returncode != 0 and because in r.stderr and "Traceback" not in r.stderr
+
+
+def comment(db_id=7, body="b") -> dict:
+ return {"databaseId": db_id, "body": body, "author": {"login": "bot"}}
+
+
+def thread(tid="T1", resolved=False, comments=None) -> dict:
+ return {
+ "id": tid,
+ "isResolved": resolved,
+ "path": "a.rs",
+ "line": 1,
+ "comments": {"nodes": [comment()] if comments is None else comments},
+ }
+
+
+def main() -> None:
+ print("list_unresolved_threads selftest")
+
+ r = run(pr({"reviewThreads": {"nodes": [thread("T1"), thread("T2", resolved=True)]}}))
+ check(
+ "an open and a resolved thread list exactly one",
+ r.returncode == 0 and "TID=T1 dbId=7" in r.stdout and "T2" not in r.stdout
+ and r.stdout.rstrip().endswith("1 unresolved thread(s)"),
+ r.stdout + r.stderr,
+ )
+ r = run(pr({"reviewThreads": {"nodes": []}}))
+ check(
+ "an EMPTY list is a genuine all-clear",
+ r.returncode == 0 and r.stdout.strip() == "0 unresolved thread(s)",
+ r.stdout + r.stderr,
+ )
+
+ # Fail closed: no list is not an empty list.
+ check(
+ "missing reviewThreads is refused, not 0 threads",
+ refused(pr({}), "no reviewThreads.nodes list"),
+ )
+ check(
+ "null reviewThreads.nodes is refused, not 0 threads",
+ refused(pr({"reviewThreads": {"nodes": None}}), "no reviewThreads.nodes list"),
+ )
+
+ # Partial nodes: a named error, never a bare KeyError traceback.
+ check(
+ "a node without isResolved is refused by name",
+ refused(pr({"reviewThreads": {"nodes": [{"id": "T1"}]}}), "has no boolean isResolved"),
+ )
+ bad = thread("T1")
+ del bad["comments"]
+ check(
+ "an open node without comments.nodes is refused by name",
+ refused(pr({"reviewThreads": {"nodes": [bad]}}), "has no comments.nodes list"),
+ )
+ check(
+ "a first comment without databaseId is refused by name",
+ refused(
+ pr({"reviewThreads": {"nodes": [thread("T1", comments=[{"body": "x"}])]}}),
+ "first comment has no databaseId",
+ ),
+ )
+
+ if FAILURES:
+ print(f"FAILED: {len(FAILURES)} check(s): {', '.join(FAILURES)}")
+ sys.exit(1)
+ print("all checks passed")
+
+
+if __name__ == "__main__":
+ main()
From 205e22d2b7216a2846dba4836c03d486e126455f Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:16:31 -0400
Subject: [PATCH 08/44] docs(test-roms): point assorted/ at both LICENSES.md
sections that carry it
tests/roms/assorted/README.md said every upstream path was in the
"blargg's NES test ROMs" section of tests/roms/LICENSES.md. That section
carries only the CPU, branch-timing, OAM, reset and APU ROMs; the two
palette ROMs and assorted/nestest.nes (kevtris) are rows of the "full
palette" ROMs section (LICENSES.md lines 193-199). The README now names
both sections and which files each carries. markdownlint passes.
Bot finding: RustyNES#325/B/4766656411/tests/roms/assorted/README.md:28-30:168.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
tests/roms/assorted/README.md | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tests/roms/assorted/README.md b/tests/roms/assorted/README.md
index 775fd89d..952bbc8a 100644
--- a/tests/roms/assorted/README.md
+++ b/tests/roms/assorted/README.md
@@ -25,9 +25,11 @@ corpus for that reason.
## Source
-All files were copied from `christopherpow/nes-test-roms` (specific
-upstream paths are in `tests/roms/LICENSES.md` "blargg's NES test ROMs"
-section).
+All files were copied from `christopherpow/nes-test-roms`. The specific
+upstream paths are in the per-ROM rows of `tests/roms/LICENSES.md`: the
+"blargg's NES test ROMs" section carries the CPU, branch-timing, OAM, reset
+and APU ROMs, and the "full palette" ROMs section carries `full_palette.nes`,
+`flowing_palette.nes` and `nestest.nes` (kevtris).
## What they test
From a9acdd0b44a11148866f262252e2a0ac05da2e23 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:19:20 -0400
Subject: [PATCH 09/44] fix(perf): report zero discards without a presentation
clock as UNVERIFIED
scripts/perf/perf_log_check.py printed "capture VALID -- window was on
screen throughout" whenever the post-warmup `present_discarded` delta
was 0. The frontend writes that column through
`presentation_clock.as_ref().map_or(0, ...)` (app.rs, the F16 block),
so a run with no presentation clock at all (non-Wayland, or the global
never bound) logs 0 on every row, and docs/performance.md already says
zero "is not proof of health" for exactly that reason. The checker
asserted something it never measured.
The checker now reads the header's `measured_refresh_hz` (already
loaded via `load_meta`). That field is set only from the presentation
clock's `poll()` answer (`self.dsync.measured_hz = Some(hz)`), so any
value other than `none` is evidence the counter was live. With `none`
and a zero count the line reads "validity UNVERIFIED (no presentation
clock)". The header is written when logging starts, so a clock that
answered later is also reported unverified -- an understatement, never
an overclaim. Report line only: the >1% discard-rate hard failure and
the exit codes are unchanged. docs/performance.md's capture-validity
section gains the fourth state.
Red first, on a real capture (perf-logs/perf-flowing_palette_nes-
20261001-041210.csv: measured_refresh_hz = none, refresh_source = none,
present_discarded column present): v3.0.0 printed "capture VALID --
window was on screen throughout"; now "validity UNVERIFIED (no
presentation clock)", exit 0 both times. The same capture with the
header changed to measured_refresh_hz = 119.991 still reads "capture
VALID". The script has no selftest mechanism, so these fixture runs are
the check. ruff-check and markdownlint pass.
Bot finding: RustyNES#363/A/5284716625/1.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
docs/performance.md | 10 ++++++++++
scripts/perf/perf_log_check.py | 15 +++++++++++++++
2 files changed, 25 insertions(+)
diff --git a/docs/performance.md b/docs/performance.md
index 7763e4c3..30ff1f08 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -3233,6 +3233,16 @@ the column existed passes but is reported as *"validity UNKNOWN, not verified"*
it cannot be proven valid, and saying "window was on screen" of a log that never
measured it would be a small version of exactly the error F14 is about.
+A fourth state since v3.0.1. Until then, `0` with the column present was reported
+as *"capture VALID — window was on screen throughout"* even when the run had **no
+presentation clock** — and the section above says zero cannot carry that claim,
+because the counter is written through a `map_or(0, ...)` on the clock. The
+checker now reads the header's `measured_refresh_hz`, which is set only from that
+clock's answer: when it is `none`, a zero count is reported as *"validity
+UNVERIFIED (no presentation clock)"*. The header is written when logging starts,
+so a clock that answered later is also reported unverified — an understatement,
+never an overclaim. This changes the report line only; pass and fail are as before.
+
Every pacing conclusion in this document that predates the column therefore
carries an unverifiable assumption: that the window was actually on screen. The
sixteen scanout-bearing captures almost certainly were — they *have* `presented`
diff --git a/scripts/perf/perf_log_check.py b/scripts/perf/perf_log_check.py
index 4ef0b650..97fbc561 100755
--- a/scripts/perf/perf_log_check.py
+++ b/scripts/perf/perf_log_check.py
@@ -395,8 +395,23 @@ def main() -> int:
# reporting that as "VALID — window was on screen" would assert something
# never measured. The gate still passes it (see above), but it must not claim
# to have checked.
+ #
+ # And a fourth (v3.0.1): ZERO is not proof of health either. The frontend
+ # writes `present_discarded` through a `map_or(0, ...)` on the presentation
+ # clock, so a run with no clock at all (non-Wayland, or the global never
+ # bound) logs 0 on every row -- and this branch used to call that "on screen
+ # throughout", the very claim docs/performance.md says zero cannot carry.
+ # `measured_refresh_hz` is set ONLY from that clock's answer, so a header
+ # value other than `none` is the evidence the counter was live. The header
+ # is written when logging starts, so a clock that answered later still
+ # reads `none`: that run is reported UNVERIFIED, which understates rather
+ # than overclaims. Reporting only -- the rate gate above is unchanged.
+ clock_seen = meta.get("measured_refresh_hz", "none").strip().lower() not in ("", "none")
if not has_col:
validity = "capture predates the column — validity UNKNOWN, not verified"
+ elif discarded == 0 and not clock_seen:
+ validity = ("validity UNVERIFIED (no presentation clock) — "
+ "measured_refresh_hz = none, so a zero count was never measured")
elif discarded == 0:
validity = "capture VALID — window was on screen throughout"
elif disc_rate <= 1.0:
From 314849c0aeb1e7aa9d78246d9b85b88c3a6528d3 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:22:01 -0400
Subject: [PATCH 10/44] fix(release): bump_release fails on a stale cosim lock,
reads UTF-8, keeps quoted stops
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Four defects in scripts/release-automation/bump_release.py, all present
on v3.0.0.
1. Swallowed lockfile failure. The `cargo metadata` refresh of the
excluded crate's crates/rustynes-cosim/Cargo.lock was wrapped in
`except Exception`, printed a WARNING and carried on, so `--apply`
could exit 0 with that lockfile still on the old version. It is now
`refresh_cosim_lock()`, which catches only CalledProcessError (with
cargo's last stderr line) and FileNotFoundError (no cargo) and
returns the message; `main` prints it as an ERROR and returns 1.
Anything else is a script bug and still raises.
2. Locale-dependent encoding. Every read_text()/write_text() and the
subprocess call used the locale's encoding. Under an ASCII locale
(PYTHONCOERCECLOCALE=0 PYTHONUTF8=0 LC_ALL=C, preferred encoding
ANSI_X3.4-1968) v3.0.0's chains_needing_a_summary() raised
"UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2" on an
em-dash document. Every call now passes encoding="utf-8".
3. terminate() promised to leave a lead ending in a closing quote or
bracket after terminal punctuation alone, but tested only the last
character: v3.0.0 returns 'the core says "enough.".',
'the core rests (for now.).' and the same for curly quotes. The
closers ("')] and U+2019/U+201D) are now looked through first; a
closer with no stop inside still gets one.
4. The ANCHORS marker decode `m.encode().decode("unicode_escape")`
re-reads UTF-8 bytes as Latin-1, so a non-ASCII marker comes back
garbled ("Current release — v" -> "Current release \xe2\x80\x94 v")
and silently matches no line. Checked: all 15 markers in today's
table are ASCII with no escapes, so nothing is mis-bumped on v3.0.0;
the defect is latent but real. `unescape_marker()` encodes as Latin-1
with backslashreplace first, which round-trips non-ASCII and still
decodes the Rust escapes.
Selftest: new cases for each -- four terminate() shapes, four
unescape_marker() cases, a UTF-8 chain document written as bytes, and a
cosim refresh against a temp root with no manifest. A harness running
the v3.0.0 functions on the same inputs under the ASCII locale fails all
five non-lockfile checks (the outputs quoted in 2-4); v3.0.0 has no lockfile function
to call, its failure path being the `except Exception` itself.
`--selftest` passes under the normal locale and under the ASCII locale.
ruff-check passes.
Bot findings: RustyNES#440/A/5382668515/1 (cargo metadata swallowed),
RustyNES#440/A/5382668515/3 (no explicit UTF-8),
RustyNES#443/A/5383852799/1 (terminate() ignores closing quote/bracket).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
scripts/release-automation/bump_release.py | 130 +++++++++++++++++----
1 file changed, 108 insertions(+), 22 deletions(-)
diff --git a/scripts/release-automation/bump_release.py b/scripts/release-automation/bump_release.py
index f301f84b..d39bbbd3 100755
--- a/scripts/release-automation/bump_release.py
+++ b/scripts/release-automation/bump_release.py
@@ -122,23 +122,67 @@ class Release:
date: str
+# Every file this script reads or writes is UTF-8 (the anchors are em-dash
+# heavy), so the encoding is stated rather than inherited from the locale.
+# Until v3.0.1 it was inherited: on a host whose locale is not UTF-8 the
+# documents would fail to decode, or be rewritten in another encoding.
+UTF8 = "utf-8"
+
+
def run(cmd: list[str], cwd: Path) -> str:
- return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, check=True).stdout
+ return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, encoding=UTF8,
+ check=True).stdout
+
+
+def refresh_cosim_lock(root: Path) -> str:
+ """Refresh `crates/rustynes-cosim/Cargo.lock`; return "" or the failure.
+
+ The excluded cosim crate keeps its own lockfile, and `cargo metadata` is
+ what rewrites it to the moved version. Until v3.0.1 a failure here was
+ caught as `Exception`, printed as a WARNING and ignored, so `--apply`
+ could exit 0 having left that lockfile on the old version. The caller now
+ fails on it. Only the two failures this call can have are caught -- cargo
+ exiting non-zero, and cargo not being installed -- so a bug in this script
+ still surfaces as a traceback rather than as a lockfile message.
+ """
+ try:
+ run(["cargo", "metadata", "--manifest-path",
+ "crates/rustynes-cosim/Cargo.toml", "--format-version", "1"], root)
+ except subprocess.CalledProcessError as e:
+ detail = (e.stderr or "").strip().splitlines()
+ return f"`cargo metadata` exited {e.returncode}: {detail[-1] if detail else 'no stderr'}"
+ except FileNotFoundError as e:
+ return f"cannot run cargo: {e}"
+ return ""
+
+
+def unescape_marker(m: str) -> str:
+ """Decode a Rust string literal's escapes (`\\"`, `\\\\`) in an ANCHORS marker.
+
+ The obvious `m.encode().decode("unicode_escape")` -- what this did until
+ v3.0.1 -- re-reads the UTF-8 BYTES as Latin-1, so a non-ASCII marker comes
+ back garbled (`—` becomes `â\\x80\\x94`) and then matches no line, which
+ skips the anchor silently. No marker is non-ASCII today; one written as
+ `"Current release — v"` would have been. Encoding as Latin-1 with
+ `backslashreplace` turns every non-Latin-1 character into an escape that
+ `unicode_escape` decodes back to itself.
+ """
+ return m.encode("latin-1", "backslashreplace").decode("unicode_escape")
def anchors(root: Path) -> list[tuple[str, str]]:
- src = (root / AUDIT).read_text()
+ src = (root / AUDIT).read_text(encoding=UTF8)
i = src.index("const ANCHORS")
blk = src[i: src.index("\n];", i)]
out = re.findall(r'path:\s*"((?:[^"\\]|\\.)*)".*?marker:\s*"((?:[^"\\]|\\.)*)"', blk, re.S)
if not out:
raise SystemExit(f"no anchors parsed from {AUDIT}; the table moved")
- return [(p, m.encode().decode("unicode_escape")) for p, m in out]
+ return [(p, unescape_marker(m)) for p, m in out]
def changelog_releases(root: Path) -> list[Release]:
rel = []
- for line in (root / CHANGELOG).read_text().splitlines():
+ for line in (root / CHANGELOG).read_text(encoding=UTF8).splitlines():
m = SECTION.match(line)
if m:
rel.append(Release(m["v"], m["c"], m["d"]))
@@ -148,7 +192,7 @@ def changelog_releases(root: Path) -> list[Release]:
def workspace_version(root: Path) -> str:
- t = (root / "Cargo.toml").read_text()
+ t = (root / "Cargo.toml").read_text(encoding=UTF8)
seg = t[t.index("[workspace.package]"):]
m = re.search(r'^version\s*=\s*"([^"]+)"', seg, re.M)
if not m:
@@ -224,11 +268,16 @@ def terminate(lead: str) -> str:
A lead already ending in `.`, `!`, `?`, or a closing quote/bracket after one
is left exactly as written -- including the `,` and `;` cases, where the
author is deliberately continuing rather than ending.
+
+ Until v3.0.1 the quote/bracket half of that promise was not kept: only the
+ last character was tested, so `... "quote."` and `(... .)` gained a
+ second stop. The closers are now looked through before the test.
"""
s = lead.rstrip()
if not s:
return s
- return s if s[-1] in '.!?,;:' else s + "."
+ core = s.rstrip("\"')]’”")
+ return s if core and core[-1] in '.!?,;:' else s + "."
def extend_chain(line: str, marker: str, old: Release, new: Release,
@@ -321,7 +370,7 @@ def chains_needing_a_summary(texts: dict, root: Path, new: Release) -> list[str]
p = root / rel
if not p.is_file():
continue
- text = texts.get(p, p.read_text())
+ text = texts.get(p, p.read_text(encoding=UTF8))
at = 0
while True:
idx = text.find(CHAIN_TAIL_PHRASE, at)
@@ -426,10 +475,30 @@ def selftest() -> int:
("trailing space ", "trailing space."),
("a clause,", "a clause,"),
("", ""),
+ # v3.0.1: the docstring's closing quote/bracket promise, which the
+ # last-character test did not keep (two stops were appended).
+ ('the core says "enough."', 'the core says "enough."'),
+ ("the core rests (for now.)", "the core rests (for now.)"),
+ ("it reads “done.”", "it reads “done.”"),
+ # ...and a closer WITHOUT a stop inside still gets one, outside.
+ ("the core rests (for now)", "the core rests (for now)."),
]:
got = terminate(raw)
assert got == want, f"terminate({raw!r}) = {got!r}, want {want!r}"
- print(" terminate(): 7 cases ok")
+ print(" terminate(): 11 cases ok")
+
+ # v3.0.1: ANCHORS markers are Rust string literals. Escapes decode; a
+ # non-ASCII character survives (`encode().decode("unicode_escape")`
+ # turned `—` into `â\x80\x94`, so the anchor matched nothing).
+ for raw, want in [
+ ("**Current release: v", "**Current release: v"),
+ ('say \\"v', 'say "v'),
+ ("Current release — v", "Current release — v"),
+ ("café — \\\\v", "café — \\v"),
+ ]:
+ got = unescape_marker(raw)
+ assert got == want, f"unescape_marker({raw!r}) = {got!r}, want {want!r}"
+ print(" unescape_marker(): 4 cases ok")
old = Release("2.4.4", "Ignition", "2026-08-22")
new = Release("2.4.5", "Compass", "2026-08-22")
LEAD = "the core reaches memory."
@@ -589,15 +658,28 @@ def check(name, got, want):
_root = Path(_td)
(_root / "to-dos").mkdir()
stale = _root / "to-dos" / "ROADMAP.md"
- stale.write_text('chain ... **v2.4.4 "Ignition"**, the current release\n')
+ stale.write_text('chain ... **v2.4.4 "Ignition"**, the current release\n', encoding=UTF8)
check("chain tail: a stale `the current release` is refused",
len(chains_needing_a_summary({}, _root, new)), 1)
- stale.write_text('chain ... **v2.4.5 "Compass"**, the current release\n')
+ stale.write_text('chain ... **v2.4.5 "Compass"**, the current release\n', encoding=UTF8)
check("chain tail: a current one is accepted",
chains_needing_a_summary({}, _root, new), [])
- stale.write_text('nothing here at all\n')
+ stale.write_text('nothing here at all\n', encoding=UTF8)
check("chain tail: absence is not a finding",
chains_needing_a_summary({}, _root, new), [])
+ # v3.0.1: the real documents are em-dash heavy UTF-8. Written as bytes
+ # so the test does not depend on the locale it is checking; under a
+ # non-UTF-8 locale a locale-default read raised UnicodeDecodeError here.
+ stale.write_bytes('chain — **v2.4.4 "Ignition"** — café, the current release\n'
+ .encode(UTF8))
+ check("chain tail: UTF-8 is read as UTF-8 whatever the locale",
+ len(chains_needing_a_summary({}, _root, new)), 1)
+
+ # v3.0.1: a failed cosim lockfile refresh is REPORTED, not swallowed.
+ # This temporary root has no `crates/rustynes-cosim/Cargo.toml`, so
+ # `cargo metadata` fails (or cargo is absent): either way a message.
+ check("cosim lock: a failed refresh returns an error",
+ bool(refresh_cosim_lock(_root)), True)
# Android's versionCode (v3.0.0): the scheme, its ordering, and refusal of a
# version it cannot encode without colliding.
@@ -679,7 +761,7 @@ def main() -> int:
for path, marker in anchors(root):
p = root / path
marker_index.setdefault(p, []).append((path, marker))
- text = edits.get(p, p.read_text())
+ text = edits.get(p, p.read_text(encoding=UTF8))
out = []
for line in text.split("\n"):
if marker + old.version in line:
@@ -710,7 +792,7 @@ def main() -> int:
for path, pat in MANIFESTS:
p = root / path
- text = edits.get(p, p.read_text())
+ text = edits.get(p, p.read_text(encoding=UTF8))
want = pat.format(v=old.version, code=version_code(old.version))
if text.count(want) != 1:
unknown.append(f"{path}: expected exactly one {want!r}, found {text.count(want)}")
@@ -723,7 +805,7 @@ def main() -> int:
# resolving. Every manifest that declares one is scanned, not a list, so a
# crate added later cannot be missed.
for p in [root / "Cargo.toml", *sorted((root / "crates").glob("*/Cargo.toml"))]:
- text = edits.get(p, p.read_text())
+ text = edits.get(p, p.read_text(encoding=UTF8))
text, moved = bump_internal_requirements(text, new.version)
if moved:
edits[p] = text
@@ -777,12 +859,12 @@ def main() -> int:
print("classified: " + ", ".join(f"{v} {k.lower()}" for k, v in counts.items() if v) + "\n")
for p, text in sorted(edits.items()):
- before = p.read_text()
+ before = p.read_text(encoding=UTF8)
if before == text:
continue
rel = p.relative_to(root)
if args.apply:
- p.write_text(text)
+ p.write_text(text, encoding=UTF8)
print(f" wrote {rel}")
else:
d = list(difflib.unified_diff(before.split("\n"), text.split("\n"),
@@ -805,12 +887,16 @@ def report_owed() -> None:
return 1
return 0
- try:
- run(["cargo", "metadata", "--manifest-path",
- "crates/rustynes-cosim/Cargo.toml", "--format-version", "1"], root)
- print(" refreshed crates/rustynes-cosim/Cargo.lock")
- except Exception as e: # noqa: BLE001
- print(f" WARNING: could not refresh the cosim lockfile: {e}", file=sys.stderr)
+ lock_err = refresh_cosim_lock(root)
+ if lock_err:
+ # The anchors above are already written; a stale cosim lockfile beside
+ # them is an incomplete bump, so this is a failure, not a WARNING.
+ print(f"\nERROR: could not refresh crates/rustynes-cosim/Cargo.lock: {lock_err}\n"
+ "Fix that and re-run `cargo metadata --manifest-path "
+ "crates/rustynes-cosim/Cargo.toml --format-version 1` before committing.",
+ file=sys.stderr)
+ return 1
+ print(" refreshed crates/rustynes-cosim/Cargo.lock")
# A chain whose tail says "the current release" cannot be extended by a
# token swap: the new link needs a WRITTEN SUMMARY of the release, which
# this script does not have and must not invent. Through v2.6.14 it said
From 9a8e9e7a4da1ac4b2580731b306fd1431d1066b9 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:23:39 -0400
Subject: [PATCH 11/44] test(harness): release-notes render audit fails on a
file it cannot read
`release_notes_are_not_hard_wrapped` (crates/rustynes-test-harness/
tests/release_notes_render_audit.rs) failed open in two places on
v3.0.0. `read_dir(...).flatten()` dropped any directory entry that
returned an error, and `read_to_string(f).unwrap_or_default()` turned a
read error -- including a non-UTF-8 file -- into an empty string, which
has no paragraphs and therefore no findings: a pass for a file the
audit never read. Each entry error and each read error now panics with
the path and the error.
Red first: with a probe `.github/release-notes/vzz-red-probe.md`
holding the bytes `v\xff\xfe broken`, v3.0.0's test passed (2 passed).
With the fix the same probe fails with "read .../vzz-red-probe.md:
stream did not contain valid UTF-8"; with the probe removed both tests
pass. cargo fmt --check and cargo clippy -p rustynes-test-harness
--all-targets -D warnings are clean.
Bot finding: RustyNES#440/A/5382668515/2.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.../tests/release_notes_render_audit.rs | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/crates/rustynes-test-harness/tests/release_notes_render_audit.rs b/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
index 9b7ab0c5..fb5cc993 100644
--- a/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
+++ b/crates/rustynes-test-harness/tests/release_notes_render_audit.rs
@@ -110,8 +110,13 @@ fn release_notes_are_not_hard_wrapped() {
let dir = repo_root().join(".github/release-notes");
let mut files: Vec = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
- .flatten()
- .map(|e| e.path())
+ // Fail closed per entry too. Until v3.0.1 this was `.flatten()`, which
+ // drops an entry whose metadata cannot be read -- a release note the
+ // audit then never examines, and never says it skipped.
+ .map(|e| {
+ e.unwrap_or_else(|e| panic!("read an entry of {}: {e}", dir.display()))
+ .path()
+ })
.filter(|p| {
p.extension().is_some_and(|x| x == "md")
&& p.file_name()
@@ -131,7 +136,11 @@ fn release_notes_are_not_hard_wrapped() {
let mut findings = Vec::new();
for f in &files {
- let text = std::fs::read_to_string(f).unwrap_or_default();
+ // A read error (or a non-UTF-8 file) is a failure, not an empty file:
+ // until v3.0.1 `unwrap_or_default()` turned it into "" -- zero
+ // paragraphs, zero findings, a pass for a file nobody read.
+ let text =
+ std::fs::read_to_string(f).unwrap_or_else(|e| panic!("read {}: {e}", f.display()));
let name = f
.file_name()
.unwrap_or_default()
From 4f7272062b07678a8f2bcf9baf82ac0b27c0f3ca Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:34:55 -0400
Subject: [PATCH 12/44] fix(review): test the 406 fallback matcher; use the API
merge base only if present
Two bot-review findings on scripts/agy-review.sh, which is installed
from the antigravity-pr-review template. The same change is committed
in the template (branch fix/v301-sweep, 9acd7f2). This repository's
copy is an older install than the template; this commit changes only
these two sites and does not re-sync the rest.
1. No test for the 406 fallback matcher. GitHub refuses an oversized
diff with two different messages (over 20,000 lines, over 300
files); both must reach the local-diff fallback, and the log must
name the limit that fired. Nothing in the self-test exercised either
message. The matcher and the label are now `diff_limit_hit()`
inside a `SELFTEST-EXTRACT: diff-limit classifier` block, and the
self-test feeds it both GitHub strings plus an unrelated HTTP 404.
Behaviour is unchanged. Mutation check: narrowing the pattern to
`(lines)`, or mislabelling the files case, each makes "406, files
variant: falls back, named 300-file" FAIL.
2. API merge base used without checking it exists. If the SHA fetch
failed and `--deepen=250` succeeded without reaching the merge base,
`git merge-base ... || echo "$api_base"` used the absent object,
logged "resolved via the compare API", and the failure surfaced as
the generic "local git diff failed". The API value is now used only
when `git cat-file -e "$api_base^{commit}"` succeeds; otherwise a
specific line says the commit is not in the local clone, and the
existing "could not compute the merge base" exit follows.
Red: the new self-test against the v3.0.0 agy-review.sh stops with
"SELFTEST-EXTRACT block 'diff-limit classifier' is missing or empty".
bash scripts/agy-review-selftest.sh: all checks passed (here and in
the template). shellcheck
reports the same findings as before the change, none on these lines.
Bot findings: RustyNES#374/A/5303548954/1, RustyNES#375/A/5303660637/2.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
scripts/agy-review-selftest.sh | 14 ++++++++-
scripts/agy-review.sh | 56 +++++++++++++++++++++++-----------
2 files changed, 51 insertions(+), 19 deletions(-)
diff --git a/scripts/agy-review-selftest.sh b/scripts/agy-review-selftest.sh
index 24b74cff..b485f90d 100755
--- a/scripts/agy-review-selftest.sh
+++ b/scripts/agy-review-selftest.sh
@@ -61,7 +61,7 @@ log() { :; }
# otherwise extract EMPTY, and an empty guard sources fine and asserts nothing -- the same
# absence-reads-as-agreement failure the markers were adopted to prevent.
for guard in "service-error guard" "oauth guard" "ours-comment filter" "duration parser" \
- "numeric env validation" "diff-size scaling"; do
+ "numeric env validation" "diff-size scaling" "diff-limit classifier"; do
blk="$(extract_block "$guard")"
[ -n "$blk" ] || { echo "FAIL: SELFTEST-EXTRACT block '$guard' is missing or empty" >&2; exit 1; }
printf '%s\n' "$blk" | bash -n - 2>/dev/null \
@@ -397,6 +397,18 @@ check "numeric env: canonical value is arithmetic-safe" "9" \
check "numeric env: the RAW value would have crashed" "CRASHED" \
"$(bash -c 'set -e; echo $(( 1048576 * 09 / 1048576 ))' 2>/dev/null || echo CRASHED)"
+# --- the 406 diff-limit classifier -------------------------------------------------------
+# GitHub's two "too big for the API" refusals must BOTH reach the local-diff fallback, and the
+# log must name the limit that fired. Matching only the lines variant failed a wide-but-shallow
+# PR outright; the label is what a reader triages from. Anything else is a real failure.
+dl() { printf '%s\n' "$1" > "$TMPD/derr"; diff_limit_hit "$TMPD/derr" || printf 'NO-FALLBACK'; }
+check "406, lines variant: falls back, named 20,000-line" "20,000-line" \
+ "$(dl 'could not find pull request diff: HTTP 406: Sorry, the diff exceeded the maximum number of lines (20000)')"
+check "406, files variant: falls back, named 300-file" "300-file" \
+ "$(dl 'could not find pull request diff: HTTP 406: Sorry, the diff exceeded the maximum number of files (300)')"
+check "an unrelated gh error does not fall back" "NO-FALLBACK" \
+ "$(dl 'could not find pull request diff: HTTP 404: Not Found')"
+
# --- the print-timeout guard -------------------------------------------------------------
# agy exits 0 on its own --print-timeout and prints a notice, alone or after a partial review.
# `have_text` passed that, so a truncated review was posted as the whole one (2026-09-28).
diff --git a/scripts/agy-review.sh b/scripts/agy-review.sh
index c34530e2..02053f4f 100755
--- a/scripts/agy-review.sh
+++ b/scripts/agy-review.sh
@@ -486,28 +486,36 @@ esac
# ambient credential helper.
# ($diff_err was allocated alongside $diff_file / $meta_file above, so the cleanup
# trap never references it before it exists.)
+# >>> SELFTEST-EXTRACT: diff-limit classifier
+# GitHub refuses an oversized diff TWO ways, with different wording: over
+# 20,000 lines, and over 300 FILES. Both are HTTP 406 and both mean the same
+# thing here -- the PR is too big for the API, not that anything went wrong --
+# so both must reach the local fallback. Matching only the `lines` variant made
+# a wide-but-shallow PR -- hundreds of files, well under the line limit, as a
+# bulk regeneration of test baselines produces -- fail the review outright
+# instead of falling back.
+#
+# On a match, print the limit that actually fired and succeed. Reporting
+# "20,000-line" for a file-count refusal is the same class of misleading triage
+# signal that made this bug look like a runner auth failure in the first place.
+# A function, not inline, so the self-test executes the real matcher and label
+# against both GitHub messages rather than grepping for its text.
+diff_limit_hit() {
+ grep -qiE 'diff exceeded the maximum number of (lines|files)' "$1" || return 1
+ if grep -qi 'maximum number of files' "$1"; then
+ printf '300-file'
+ else
+ printf '20,000-line'
+ fi
+}
+# <<< SELFTEST-EXTRACT
if ! gh pr diff "$PR" --repo "$REPO" > "$diff_file" 2>"$diff_err"; then
- # GitHub refuses an oversized diff TWO ways, with different wording: over
- # 20,000 lines, and over 300 FILES. Both are HTTP 406 and both mean the same
- # thing here -- the PR is too big for the API, not that anything went wrong --
- # so both must reach the local fallback. Matching only the `lines` variant made
- # a wide-but-shallow PR -- hundreds of files, well under the line limit, as a
- # bulk regeneration of test baselines produces -- fail the review outright
- # instead of falling back.
- if grep -qiE 'diff exceeded the maximum number of (lines|files)' "$diff_err"; then
+ if hit="$(diff_limit_hit "$diff_err")"; then
base_ref="$(jq -r '.baseRefName // empty' "$meta_file")"
if [ -z "$base_ref" ] || [ "$base_ref" = "null" ]; then
log "diff exceeds the API limit and the base branch is unknown; cannot fall back"
exit 1
fi
- # Name the limit that actually fired. Reporting "20,000-line" for a
- # file-count refusal is the same class of misleading triage signal that
- # made this bug look like a runner auth failure in the first place.
- if grep -qi 'maximum number of files' "$diff_err"; then
- hit="300-file"
- else
- hit="20,000-line"
- fi
log "diff exceeds GitHub's ${hit} API limit; falling back to a local git diff"
pr_ref="refs/agy/pr-${PR}"
base_local="refs/agy/base-${PR}"
@@ -565,8 +573,20 @@ if ! gh pr diff "$PR" --repo "$REPO" > "$diff_file" 2>"$diff_err"; then
if [ -n "$api_base" ] && [ "$api_base" != "null" ]; then
if git fetch --no-tags --quiet origin "$api_base" 2>/dev/null \
|| git fetch --no-tags --quiet --deepen=250 origin "${fetch_refspecs[@]}" 2>/dev/null; then
- merge_base="$(git merge-base "$base_local" "$pr_ref" 2>/dev/null || echo "$api_base")"
- log "shallow clone: merge base ${merge_base} resolved via the compare API"
+ merge_base="$(git merge-base "$base_local" "$pr_ref" 2>/dev/null || true)"
+ # Fall back to the API's merge base only if that commit is actually
+ # here. When the SHA fetch fails and `--deepen=250` succeeds without
+ # reaching it, the object is absent, and using it anyway surfaced later
+ # as the generic "local git diff failed" -- the wrong diagnosis.
+ if [ -z "$merge_base" ]; then
+ if git cat-file -e "${api_base}^{commit}" 2>/dev/null; then
+ merge_base="$api_base"
+ else
+ log "compare API merge base ${api_base} is not in the local clone after both fetches"
+ fi
+ fi
+ [ -z "$merge_base" ] \
+ || log "shallow clone: merge base ${merge_base} resolved via the compare API"
fi
fi
fi
From 58ac019e03fbf8c29c17663f9701c7676236f905 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:37:21 -0400
Subject: [PATCH 13/44] ci(dependabot): drop the retired `reviewers` key;
CODEOWNERS assigns review
GitHub removed Dependabot's `reviewers` option in 2025 and routes review
assignment through CODEOWNERS instead (github.blog changelog 2025-04-29
announcing it, 2025-08-08 completing it). The key survived in two update
entries here (cargo at the root, github-actions); the current options
reference no longer lists it and the SchemaStore schema rejects it, which is
how the v3.0.1 sweep's tooling agent noticed it while validating the egui
grouping change.
Nothing about who reviews changes: `.github/CODEOWNERS` opens with
`* @doublegate`, so every Dependabot PR still requests the same reviewer.
`assignees` is a supported option and stays. One comment at the first site
records why the key is absent, so it is not "restored" later.
Verified: check-yaml passes; `grep reviewers:` matches only that comment.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.github/dependabot.yml | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 5ea830c1..d4a1a005 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -19,8 +19,9 @@ updates:
commit-message:
prefix: "chore"
include: "scope"
- reviewers:
- - "doublegate"
+ # No `reviewers:` key: GitHub retired it in 2025 in favour of CODEOWNERS
+ # (github.blog changelog 2025-08-08), and `.github/CODEOWNERS` assigns
+ # @doublegate to every path. Removed in v3.0.1.
assignees:
- "doublegate"
# From 2026-09-12 to 2026-09-28 an `ignore` block here held egui,
@@ -152,7 +153,5 @@ updates:
- "github-actions"
commit-message:
prefix: "chore(ci)"
- reviewers:
- - "doublegate"
assignees:
- "doublegate"
From 49ea391b505a59a6e11e7d118bc6891925693892 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:30:23 -0400
Subject: [PATCH 14/44] docs(provenance): name the upstream files in three
headers; amend ADR 0036
Four bot findings on the provenance record (PR #346), re-verified on
v3.0.0 and fixed without removing or weakening any statement.
ADR 0036 Decision 3 and docs/originality-and-provenance.md say every
derived file's provenance header names its upstream file or function.
Three did not: kaiser.rs and ntdec.rs said only "derived from Mesen2",
and m069_sunsoft_fme7.rs named projects, not files. The citations now in
the headers come only from this repository's own record, never from the
upstream source:
- kaiser.rs: `Waixing/Mapper253.h`, already in the section 1 row and in
the pre-v2.2.5 "Ported from Mesen2 Waixing/Mapper253.h" comment
(git show 0265b3bd^:crates/rustynes-mappers/src/kaiser.rs:609).
- ntdec.rs: `Txc/Bmc11160.h` (section 1), plus `Ntdec/NtdecTc112.h` and
`Unlicensed/Mapper204.h`, which the file's own pre-v2.2.5 "Ported from
Mesen2" comments named (0265b3bd^ ntdec.rs:864-865, 1049-1050) and
which section 1 had not recorded; the row now records them too.
- m069_sunsoft_fme7.rs: Mesen2 `NesSoundMixer::GetOutputVolume` over
`Sunsoft5bAudio::_volumeLut`, the functions the file's pre-v2.2.5
"Target, derived from Mesen2" comment named (0265b3bd^ :115-118);
section 1 now records the same. No Nestopia file is recorded anywhere,
and the header says so rather than inventing one.
ADR 0036's Consequences said distributors "keep those terms for those
releases", asserting the old MIT/Apache grant was effective. Accepted
ADRs are not rewritten: a dated amendment adopts the provenance
document's neutral wording and leaves the bullet as the 2026-08-04
record.
The postmortem's executive summary stated the model's decision as fact;
section 5 says that reasoning is reconstructed from the result. The
sentence now carries that qualifier and points to section 5. Its
timeline called the transplanted code the "v2.8.0 engine stack" with no
lineage label, ambiguous now that RustyNES shipped its own v2.8.0; it is
labelled as the private RustyNES_v2 project's internal v2.8.0. The
quoted commit subject is unchanged.
Verified: cargo test -p rustynes-test-harness --test
provenance_record_audit passes (header and section 1 sets agree);
markdownlint passes on the changed files.
Bot findings RustyNES#346/T/PRRT_kwDOQrRows6WfQMt,
RustyNES#346/T/PRRT_kwDOQrRows6Wf6_-,
RustyNES#346/T/PRRT_kwDOQrRows6Wf7AB,
RustyNES#346/T/PRRT_kwDOQrRows6Wf7AG.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
crates/rustynes-mappers/src/kaiser.rs | 2 +-
.../rustynes-mappers/src/m069_sunsoft_fme7.rs | 2 +-
crates/rustynes-mappers/src/ntdec.rs | 2 +-
.../adr/0036-relicense-gplv3-derivative-work.md | 17 +++++++++++++++++
docs/originality-and-provenance.md | 4 ++--
docs/provenance-failure-postmortem.md | 5 +++--
6 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/crates/rustynes-mappers/src/kaiser.rs b/crates/rustynes-mappers/src/kaiser.rs
index a2f0594d..1872d289 100644
--- a/crates/rustynes-mappers/src/kaiser.rs
+++ b/crates/rustynes-mappers/src/kaiser.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
-// Provenance: the Kaiser boards are derived from Mesen2 (GPL-3.0-or-later). See docs/originality-and-provenance.md (Section 1)
+// Provenance: the Kaiser boards are derived from Mesen2 (GPL-3.0-or-later), `Waixing/Mapper253.h` (the mapper 253 Waixing VRC4 clone). See docs/originality-and-provenance.md (Section 1)
// and NOTICE for the complete, audited derivation record.
//! Kaiser boards: `KS202` (mapper 56), `KS7017` (142), `KS7031` (303),
//! `KS7016` (305), `KS7013B` (306) and relatives.
diff --git a/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs b/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
index 056d7553..0c09e5b7 100644
--- a/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
+++ b/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
-// Provenance: the Sunsoft FME-7 / 5B audio detail is derived from Mesen2 (GPL-3.0-or-later) and cross-referenced with Nestopia UE (GPL-2.0-or-later). See docs/originality-and-provenance.md (Section 1)
+// Provenance: the Sunsoft FME-7 / 5B audio detail is derived from Mesen2 (GPL-3.0-or-later), `NesSoundMixer::GetOutputVolume` (the 5B `* 15` output weight) over `Sunsoft5bAudio::_volumeLut`, and cross-referenced with Nestopia UE (GPL-2.0-or-later; no upstream file is recorded). See docs/originality-and-provenance.md (Section 1)
// and NOTICE for the complete, audited derivation record.
//! Sunsoft FME-7 (mapper 69) -- banking, the CPU-cycle IRQ counter, and the
//! on-cart Sunsoft 5B audio chip.
diff --git a/crates/rustynes-mappers/src/ntdec.rs b/crates/rustynes-mappers/src/ntdec.rs
index ae58091a..799eb621 100644
--- a/crates/rustynes-mappers/src/ntdec.rs
+++ b/crates/rustynes-mappers/src/ntdec.rs
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
-// Provenance: the NTDEC boards are derived from Mesen2 (GPL-3.0-or-later). See docs/originality-and-provenance.md (Section 1)
+// Provenance: the NTDEC boards are derived from Mesen2 (GPL-3.0-or-later), `Ntdec/NtdecTc112.h` (mapper 193), `Unlicensed/Mapper204.h` (mapper 204) and `Txc/Bmc11160.h` (mapper 299). See docs/originality-and-provenance.md (Section 1)
// and NOTICE for the complete, audited derivation record.
//! NTDEC boards decoded from the address bus: mappers 63 and 174.
//!
diff --git a/docs/adr/0036-relicense-gplv3-derivative-work.md b/docs/adr/0036-relicense-gplv3-derivative-work.md
index 7b1bfc46..d0d2c255 100644
--- a/docs/adr/0036-relicense-gplv3-derivative-work.md
+++ b/docs/adr/0036-relicense-gplv3-derivative-work.md
@@ -97,3 +97,20 @@ component is "or-later" and no incorporated component is v3-only.
emulation-core code change, and the release checks verify it: AccuracyCoin passes
141/141 and nestest is 0-diff. `docs/STATUS.md` is authoritative for the pass
counts.
+
+## Amendment (2026-10-06, v3.0.1): the terms of earlier releases
+
+The first Consequences bullet says distributors who relied on the permissive terms
+of prior tagged releases "keep those terms *for those releases*". That asserts the
+earlier `MIT OR Apache-2.0` grant was effective for those releases, which sits
+uneasily with this ADR's own Context: the prior dual license "was not a license the
+project was entitled to offer". Whether those terms held for the GPL-derived code is a
+legal question this ADR does not decide, and the sentence should not have read as if
+it did.
+
+The position of record is the neutral wording `docs/originality-and-provenance.md`
+already uses: source released in prior tagged releases remains under whatever terms
+accompanied it at the time, and that history cannot be retroactively changed, but the
+current tree and every release from v2.2.9 onward is GPL-3.0-or-later. Nothing here
+states whether the earlier permissive terms were valid for the GPL-derived portions.
+The bullet above is left as written, as the record of what was decided on 2026-08-04.
diff --git a/docs/originality-and-provenance.md b/docs/originality-and-provenance.md
index 5754d097..5a30c41a 100644
--- a/docs/originality-and-provenance.md
+++ b/docs/originality-and-provenance.md
@@ -75,7 +75,7 @@ LGPL-2.1-or-later ones may be incorporated into a GPL-3.0-or-later work.
| `crates/rustynes-mappers/src/m019_namco163.rs` | Mesen2 | `NesSoundMixer::GetOutputVolume`: the N163 `* 20` output weight (recorded v2.9.9, NC-17) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m016_bandai_fcg.rs` | Mesen2 | `Eeprom24C01` / `Eeprom24C02`, `Core/NES/Mappers/Bandai/` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m035_jy_asic.rs` | Mesen2 | `JyCompany` register decode, `InvertPrgBits` | GPL-3.0-or-later |
-| `crates/rustynes-mappers/src/m069_sunsoft_fme7.rs` | Mesen2 / Nestopia | Sunsoft 5B audio + FME-7 | GPL-3.0-or-later / GPL-2.0-or-later |
+| `crates/rustynes-mappers/src/m069_sunsoft_fme7.rs` | Mesen2 / Nestopia | Sunsoft 5B audio + FME-7; the 5B level target from Mesen2's `NesSoundMixer::GetOutputVolume` (`* 15` weight) over `Sunsoft5bAudio::_volumeLut`, as this file's pre-v2.2.5 "derived from Mesen2" comment recorded (recorded here v3.0.1); no Nestopia file is recorded | GPL-3.0-or-later / GPL-2.0-or-later |
| `crates/rustynes-mappers/src/m085_vrc7.rs` | Mesen2 | `Vrc7Audio.h`: the `$9010`/`$9030` register-write path and the `$E000` silence flag (classified v2.7.1, core audit §6.2) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m099_vs_system.rs` | Mesen2 | `VsSystem.h`: DualSystem sub-console `chrOuter` / `prgOuter` banking (classified v2.7.1, core audit §6.2) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m176_bmc_fk23c.rs` | Mesen2 | `Waixing/Fk23C.h`, `Mmc3Variants/MMC3_Coolboy.h` | GPL-3.0-or-later |
@@ -85,7 +85,7 @@ LGPL-2.1-or-later ones may be incorporated into a GPL-3.0-or-later work.
| `crates/rustynes-mappers/src/m513_sachen_9602.rs` | Mesen2 | `Sachen/Sachen9602.h`, `Txc/TxcChip.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/mmc3_clones.rs` | Mesen2 | `Waixing/Mapper253.h`, `Sachen/Sachen8259.h`, `InvertPrgBits`, MMC3 variants | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/multicart_discrete.rs` | Mesen2 | `Ntdec/Mapper221.h`, `Txc/Bmc11160.h` | GPL-3.0-or-later |
-| `crates/rustynes-mappers/src/ntdec.rs` | Mesen2 | NTDEC boards, `Txc/Bmc11160.h` | GPL-3.0-or-later |
+| `crates/rustynes-mappers/src/ntdec.rs` | Mesen2 | NTDEC boards, `Txc/Bmc11160.h`; also `Ntdec/NtdecTc112.h` (mapper 193) and `Unlicensed/Mapper204.h` (mapper 204), named by this file's own pre-v2.2.5 "Ported from" comments (recorded v3.0.1) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/sachen_discrete.rs` | Mesen2 | `Sachen/Sachen8259.h`, `Txc/TxcChip.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/kaiser.rs` | Mesen2 | Kaiser boards, `Waixing/Mapper253.h` | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/fds.rs` | puNES | `fds.c` per-CRC drive-timing table | GPL-2.0-or-later |
diff --git a/docs/provenance-failure-postmortem.md b/docs/provenance-failure-postmortem.md
index 32d8060a..09814530 100644
--- a/docs/provenance-failure-postmortem.md
+++ b/docs/provenance-failure-postmortem.md
@@ -25,7 +25,8 @@ FCEUX). The AI that wrote them **labeled them honestly at the time** ("Faithful
1. **The port itself** (May 2026, in the predecessor project `RustyNES_v2` core-work): the reference
emulators' full GPL **source** was cloned in the workspace and set as the "accuracy bar," with
enforced guardrails forbidding reading or reproducing it not followed. The LLM decided to match
- Mesen2 exactly, with Mesen2's source right there, it did the obvious thing and partially-ported.
+ Mesen2 exactly, with Mesen2's source right there, it did the obvious thing and partially-ported
+ (the model's reasoning is inferred from the result, not quoted from a transcript; see §5).
2. **The laundering** (v2.2.5 "Colophon," 2026-08-03, in this public project): when the licensing
implication surfaced, the honest "port of" comments were **reworded** into "oracle
cross-checks," `NOTICE` was rewritten to assert "No GPL-licensed emulator source is
@@ -50,7 +51,7 @@ that engine by transplant on 2026-06-13.
|---|---|---|---|
| **2026-05-10** | RustyNES_v2 | Project "bootstrapped **from a deep-research workflow**." The Mesen2/higan/ares "accuracy bar" framing and the reference-emulator source tree (`ref-proj/`) entered here. Phases 1–2 (6502, nestest pass, first mappers, PPU) landed the same day. | `3ec2230 chore: bootstrap RustyNES v2 from deep-research workflow`; `4d3cf47`, `b386595`, `69e9373` |
| **~2026-05-10 → 05-25** | RustyNES_v2 | The cycle-accurate chip core built in phases. With the GPL **source** on disk and an accuracy-matching goal, code was **ported** from it and labeled as such: CPU SH\*/unstable stores from Mesen2 `NesCpu.h`; PPU sprite-eval/OAM from Mesen2 `NesPpu.cpp:1015-1141`; mappers from Mesen2; JV001/FDS from puNES; UNIF from FCEUX. | `9e00032 fix(cpu): SH* unstable stores` (2026-05-23); `941d448 fix(ppu): Phase 3b — OAM-corruption row tracking` (2026-05-23) |
-| **2026-06-13** | RustyNES → | The "**v2.8.0 engine stack**" was **transplanted** into the public repo as the `rustynes-*` crates. The honest "port of" comments came along verbatim. The "oracle / do NOT port" framing was written into the docs **for the first time** on this same day — *after* the porting was already done. | `dba2e75c feat(synthesis): Phase A — transplant v2.8.0 engine stack as rustynes-*`; `4e1844f7 docs(synthesis): Phase C` (first "do NOT port" text) |
+| **2026-06-13** | RustyNES → | The "**v2.8.0 engine stack**" (the private `RustyNES_v2` project's internal v2.8.0 — engine lineage, not RustyNES's own v2.8.0 release) was **transplanted** into the public repo as the `rustynes-*` crates. The honest "port of" comments came along verbatim. The "oracle / do NOT port" framing was written into the docs **for the first time** on this same day — *after* the porting was already done. | `dba2e75c feat(synthesis): Phase A — transplant v2.8.0 engine stack as rustynes-*`; `4e1844f7 docs(synthesis): Phase C` (first "do NOT port" text) |
| **2026-06-19 →** | RustyNES | The public-era sessions and maintainer guidance repeatedly asserted the code used the emulators "**as oracle**" only and "**NEVER lift**" — a framing that directly contradicted the "port of Mesen2" comments sitting in the same tree. The tension was left unresolved for weeks. | Public session logs, maintainer instructed: "as oracle" ×165, "NEVER lift" ×58, "reference only" ×41, "do not copy" ×36 |
| **2026-08-03** | RustyNES | **v2.2.5 "Colophon."** Prompted by NESdev scrutiny of the project's AI-assisted origins, the honest "port of X" comments were **reworded** to "oracle cross-checks," `NOTICE` was rewritten to claim "No GPL-licensed emulator source is incorporated," and the MIT/Apache license was kept. The evidence was scrubbed rather than acted on - the LLM should not have done this. | `0265b3bd release: v2.2.5 "Colophon"` |
| **2026-08-04** | RustyNES | NESdev reviewer (**Fiskbit**) publicly identified that the code — bugs, constants, variable names, code ordering, and file/function/line comments — goes well beyond oracle use, and that scrubbing the comments looked like concealment. **Correct.** v2.2.9 relicenses to GPL-3.0-or-later, restores honest attribution, and writes this post-mortem. | `ec26e229 license: relicense to GPL-3.0-or-later …`; this document |
From 8a8d42074620faae17e11637fa9101509431f858 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:37:48 -0400
Subject: [PATCH 15/44] docs(cpu): a taken branch defers NMI dispatch too, not
only IRQ
Since v2.6.7 `Cpu::handle_interrupts` freezes the NMI dispatch copy
`mc_prev_need_nmi` while `skip_irq_sample && skip_irq_sample_q`
(crates/rustynes-cpu/src/cpu.rs, the "taken-branch poll rule applies
to NMI too" block), following nesdev's CPU_interrupts, which says
*interrupts* are not polled before the third cycle of a taken branch.
Three records still described the pre-v2.6.7 behaviour:
- docs/cpu-6502.md's branch_delays_irq section and the rustdoc on
`skip_irq_sample` both said "NMI sampling is unaffected -- the quirk
is IRQ-only". Both now say the dispatch is deferred while the NMI edge
latch (`mc_need_nmi`) keeps running, so an edge is never lost; the
spec names `skip_irq_sample_q` and CPU snapshot version 4.
- The v4 note on `CPU_SNAPSHOT_VERSION` said a restore that dropped
`skip_irq_sample_q` would resume with "the NMI edge detector re-armed
a cycle early". The edge latch runs every cycle; what the field gates
is the dispatch copy. It now says "the NMI dispatch gate".
- The published v2.6.7 release notes said "The emulation core is
unchanged" and that AccuracyCoin and nestest held "by construction",
while the same notes describe this CPU change and say the gates were
re-run. Published notes are not rewritten: a dated
"Correction (v3.0.1)" line follows the false sentence.
Comment and documentation changes only; no behaviour changes. The edits
to cpu.rs are outside its SH-group provenance region.
Verified: RUSTDOCFLAGS="-D warnings" cargo doc -p rustynes-cpu
--no-deps clean; cargo clippy -p rustynes-cpu --all-targets -D warnings
clean; RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p
rustynes-libretro clean; cargo test -p rustynes-test-harness --test
release_notes_render_audit --test snapshot_schema_audit pass;
markdownlint passes.
Bot findings RustyNES#477/T/PRRT_kwDOQrRows6dnsnR,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnW,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnd.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.github/release-notes/v2.6.7.md | 2 ++
crates/rustynes-cpu/src/cpu.rs | 6 +++++-
crates/rustynes-cpu/src/snapshot.rs | 6 ++++--
docs/cpu-6502.md | 15 +++++++++++++--
4 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/.github/release-notes/v2.6.7.md b/.github/release-notes/v2.6.7.md
index 4a658880..f0f0c25b 100644
--- a/.github/release-notes/v2.6.7.md
+++ b/.github/release-notes/v2.6.7.md
@@ -6,6 +6,8 @@ A detent is the notch that holds a mechanism at an exact position and makes that
**The emulation core is unchanged.** AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction; the work is in the sibling repository and in the release machinery.
+> Correction (v3.0.1): the sentence above is wrong. The emulation core changed in one place in this release, the taken-branch NMI poll in `Cpu::handle_interrupts` (`skip_irq_sample_q`, `CPU_SNAPSHOT_VERSION` 3 to 4; see "An interrupt polled on a cycle the documentation forbids" below), and AccuracyCoin 141/141 and nestest 0-diff were re-run against it rather than holding by construction.
+
## The gate
| # | criterion | result |
diff --git a/crates/rustynes-cpu/src/cpu.rs b/crates/rustynes-cpu/src/cpu.rs
index 17c31873..76dfed21 100644
--- a/crates/rustynes-cpu/src/cpu.rs
+++ b/crates/rustynes-cpu/src/cpu.rs
@@ -254,7 +254,11 @@ pub struct Cpu {
/// taken / page-cross cycles do *not* re-sample IRQ. The branch
/// dispatch sets this flag *before* the operand fetch and `step()`
/// clears it at the top of every instruction.
- /// NMI sampling is unaffected — the quirk is IRQ-only.
+ /// Since v2.6.7 the same rule defers NMI *dispatch*: while this flag and
+ /// `skip_irq_sample_q` are both set, `handle_interrupts` freezes the
+ /// dispatch copy `mc_prev_need_nmi`. The NMI edge latch (`mc_need_nmi`)
+ /// keeps running every cycle, so an edge is never lost, only recognised
+ /// after the branch.
pub(crate) skip_irq_sample: bool,
/// `skip_irq_sample` as it stood on the PREVIOUS cycle. The NMI dispatch
/// gate freezes on this rather than on the live flag, because the two
diff --git a/crates/rustynes-cpu/src/snapshot.rs b/crates/rustynes-cpu/src/snapshot.rs
index f1b47ef8..fedb438c 100644
--- a/crates/rustynes-cpu/src/snapshot.rs
+++ b/crates/rustynes-cpu/src/snapshot.rs
@@ -48,8 +48,10 @@ use crate::status::Status;
/// BRANCH, so suppressing the poll needs the flag's value on the previous
/// cycle as well as this one. It is genuine emulation state read back on
/// the next tick, not derivable from the rest of the blob — a restore that
-/// dropped it would resume with the NMI edge detector re-armed a cycle
-/// early on any snapshot landing inside a taken branch. Serialized rather
+/// dropped it would resume with the NMI dispatch gate (the
+/// `mc_prev_need_nmi` copy; the edge latch `mc_need_nmi` runs every cycle
+/// regardless) re-opened a cycle early on any snapshot landing inside a
+/// taken branch. Serialized rather
/// than allowlisted, which `snapshot_schema_audit` says has been the right
/// answer every time it has come up.
pub const CPU_SNAPSHOT_VERSION: u8 = 4;
diff --git a/docs/cpu-6502.md b/docs/cpu-6502.md
index b46704c3..81390830 100644
--- a/docs/cpu-6502.md
+++ b/docs/cpu-6502.md
@@ -168,8 +168,19 @@ Implementation: `Cpu` carries a `skip_irq_sample` flag. The branch
dispatch arms set it before the operand fetch (the opcode fetch in
`step()` has already performed the canonical poll on cycle 1); for
the rest of the instruction, `idle_tick` no longer updates
-`irq_first_tick`. NMI sampling is unaffected — the quirk is IRQ-only.
-The flag is reset at the top of every `step()`.
+`irq_first_tick`. The flag is reset at the top of every `step()`.
+
+**A taken branch also defers NMI dispatch (since v2.6.7).** nesdev's
+`CPU_interrupts` says *interrupts* are not polled before the third cycle of a
+taken branch, not only IRQs. `handle_interrupts` therefore freezes the NMI
+dispatch copy `mc_prev_need_nmi` while both `skip_irq_sample` and its
+one-cycle delay `skip_irq_sample_q` are set. The NMI edge latch
+(`mc_need_nmi`) keeps running every cycle — it is hardware's always-on edge
+detector — so an /NMI edge inside a taken branch is never lost, only
+dispatched after the branch. `skip_irq_sample_q` is genuine emulation state
+and is serialized: it is the field CPU snapshot version 4
+(`CPU_SNAPSHOT_VERSION`) appended. (Until v3.0.1 this section said the quirk
+was IRQ-only, which stopped being true at v2.6.7.)
Covered by the `cpu_interrupts_v2/5-branch_delays_irq` ROM, which passes
strictly. A mock-bus unit pin, `branch_taken_no_cross_delays_irq_one_instruction`
From 7827c02be87d1bc93b593abc622ca0e61e4422a7 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:38:03 -0400
Subject: [PATCH 16/44] docs(mister): date the rung-0 text, name both rbf
files, narrow gate claims
Five bot findings on the MiSTer records, re-verified on v3.0.0.
- docs/mister.md "What this is, and what it is not" said in the present
tense that the sibling holds "the harness at rung 0 and no RTL". It
has carried RTL since v2.4.4 and holds a complete core. The sentence
is now dated history (written 2026-08-20, first shipped in v2.4.2,
per git log -S and git tag --contains fbf5364e), and the licence
paragraph's "all 57 files" is qualified as the v2.4.3 upstream-clone
count, with the v2.6.6 re-verification against the vendored tree
(40 HDL files, 4 GPL-3.0-or-later, 9 GPL-2.0-or-later, 0 GPL-2.0-only)
cited from the sibling's licence audit.
- docs/mister.md and to-dos/mister/TASKS.md named
`releases/RustyNES_MiSTer-vX.Y.Z.rbf` as the committed file. Since
v2.6.15 the sibling commits `releases/RustyNES_YYYYMMDD.rbf`, the only
form both MiSTer parsers accept, and attaches the version-named file
to the GitHub releases. Both sites now name the two files separately,
as the sibling's docs/bitstream-release.md does.
- docs/mister.md said all 148 gates carry "a mutation record apiece".
The five blargg cpu_interrupts_v2 verdict gates v2.6.15 added have
none (no rung document, and neither tb/mutate.sh nor
tb/mutate_apu.sh names them; checked by grep in the sibling). The
sentence now names that exception.
- to-dos/mister/contribution-checklist.md cited "the 142-gate suite";
it now gives v3.0.0's ladder (199/0/1 on-die, 200/0/1 off-die, as
README.md states).
- The v2.6.16 plan's acceptance line gave no off-die total; it now says
147/147 on the die and 148/148 off-die (the SDRAM latency gate is N/A
on the die).
Verified: cargo test -p rustynes-test-harness --test
contribution_checklist_audit --test mister_source_map_audit pass;
markdownlint passes on the changed files.
Bot findings RustyNES#434/A/5363668968/2,
RustyNES#477/T/PRRT_kwDOQrRows6dnsnY,
RustyNES#495/B/5119186180/README.md:686-686:266,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcT,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcX.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
docs/mister.md | 35 ++++++++++++++++---------
to-dos/mister/TASKS.md | 17 +++++++-----
to-dos/mister/contribution-checklist.md | 3 ++-
to-dos/plans/v2.6.16-interlock-plan.md | 4 ++-
4 files changed, 38 insertions(+), 21 deletions(-)
diff --git a/docs/mister.md b/docs/mister.md
index f2252898..e8816070 100644
--- a/docs/mister.md
+++ b/docs/mister.md
@@ -31,9 +31,12 @@ reviewer can **check** about it.
The contributing page states the bar for AI-assisted code in one sentence --
*"Fully AI generated code should meet a minimum reasonable bar for readability
and include some evidence of quality and accuracy testing."* This programme's
-evidence is 148 co-simulation gates with a mutation record apiece -- 142 at the
-start of v2.6.15, plus the five it added and the one v2.6.16 added. 147 of them
-run on the die; the 148th measures the off-die memory system and reports N/A
+evidence is 148 co-simulation gates -- 142 at the start of v2.6.15, plus the five
+it added and the one v2.6.16 added. Every gate but five carries a mutation
+record; the exceptions are the five blargg `cpu_interrupts_v2` verdict gates
+v2.6.15 added, which are independent-oracle verdicts with no mutation recorded
+against them (no rung document and neither `tb/mutate.sh` nor
+`tb/mutate_apu.sh` names them; noted at v3.0.1). 147 of them run on the die; the 148th measures the off-die memory system and reports N/A
there, because a gate on the SDRAM path cannot exist in a build with no SDRAM. And
`tb/regress.sh` says in its own header that it *"is NOT a CI gate and cannot
be"*, because it needs the oracle's goldens and a cargo build of a crate in
@@ -251,8 +254,11 @@ gate can adjudicate. It is allowed and named, not resolved.
repositories. This reverses v2.6.6, which produced a bitstream and deliberately
did not publish it.
-**Why the reversal is right.** The MiSTer distribution mechanism reads
-`releases/RustyNES_MiSTer-vX.Y.Z.rbf` out of the *repository*, so an empty
+**Why the reversal is right.** The MiSTer distribution mechanism reads the
+`.rbf` out of the *repository*'s `releases/` — since v2.6.15 as
+`releases/RustyNES_YYYYMMDD.rbf`, the only form both MiSTer parsers accept, with
+the version-named `RustyNES_MiSTer-vX.Y.Z.rbf` attached to the GitHub releases
+for people (the sibling's `docs/bitstream-release.md`) — so an empty
`releases/` does not describe a cautious core — it describes an undistributable
one, withheld from exactly the people who own the boards this project does not.
And a claim nobody made is not the same as a claim marked unverified: only the
@@ -600,10 +606,12 @@ written from public hardware documentation**, in a sibling repository
private), with **RustyNES as its verification oracle**. This document specifies
the boundary between the two - the one part that lives in this repository.
-The sibling repository holds the harness at rung 0 and **no RTL**, which is the
-ladder's design rather than a gap: the testbench must be shown able to recognise
-agreement before anything is compared. Two of its files matter to readers of
-this document, because they are the other half of what is specified here.
+When this section was written (2026-08-20, first shipped in v2.4.2), the sibling repository held only the
+rung-0 harness and **no RTL**, which was the ladder's design rather than a gap:
+the testbench had to be shown able to recognise agreement before anything was
+compared. It has carried RTL since v2.4.4 and now holds a complete core (see the
+release sections above). Two of its harness files matter to readers of this
+document, because they are the other half of what is specified here.
`tb/checkpoint.h` reimplements this repository's checkpoint encoding in C++, and
`tb/checkpoint_selftest.cpp` asserts it against **the same hardcoded vector**
@@ -617,9 +625,12 @@ first thing to run after touching either.
Its licence audit also settled a question this side had left open. ADR 0037
recorded that a GPL-2.0-**only** file anywhere in the MiSTer framework's `sys/`
-would force the RTL to GPL-2.0-or-later. All 57 files were read: **there is no
-such file**, and four are GPL-3.0-or-later - including `hps_io.sv`, which no core
-functions without. GPL-2.0-or-later combines upward and GPL-3.0-or-later does not
+would force the RTL to GPL-2.0-or-later. All 57 files of an upstream clone were
+read at v2.4.3: **there is no such file**, and four are GPL-3.0-or-later -
+including `hps_io.sv`, which no core functions without. v2.6.6 re-verified this
+against the tree actually vendored (`Template_MiSTer@3ea1134c`): **40** HDL files,
+the same four GPL-3.0-or-later, nine GPL-2.0-or-later, **zero** GPL-2.0-only (the
+sibling's `docs/licence-audit-2026-08-20.md`, "Re-verified at v2.6.6"). GPL-2.0-or-later combines upward and GPL-3.0-or-later does not
reduce, so the combined bitstream must be **GPL-3.0-or-later**, which is already
this project's licence. The hedge is inverted by the evidence rather than
confirmed by it.
diff --git a/to-dos/mister/TASKS.md b/to-dos/mister/TASKS.md
index 914060ae..77d7faff 100644
--- a/to-dos/mister/TASKS.md
+++ b/to-dos/mister/TASKS.md
@@ -449,13 +449,16 @@ something as blocked, check the blocker applies to the WHOLE item.**
artifact at v2.6.15. Re-measure it rather than quoting this line; every
unticked box carries a named verdict and a gate
(`contribution_checklist_audit.rs`) keeps it that way.
-- [x] `releases/RustyNES_MiSTer-vX.Y.Z.rbf` — **since v2.6.7**, committed to the
- sibling's `releases/` and attached to the GitHub release on both
- repositories, produced by `scripts/release-rbf.sh`, which refuses a
- compile with errors or negative slack at any corner. The **filename**
- remains a known divergence from MiSTer's date convention — see the
- checklist, where it is measured against `Main_MiSTer/file_io.cpp` rather
- than the wiki's paraphrase.
+- [x] A release `.rbf` — **since v2.6.7**, produced by `scripts/release-rbf.sh`,
+ which refuses a compile with errors or negative slack at any corner.
+ **Since v2.6.15 it carries two names:** `releases/RustyNES_YYYYMMDD.rbf`
+ is committed to the sibling's `releases/` (the date form is the only one
+ `Distribution_MiSTer`'s builder and the `Main_MiSTer` core menu accept,
+ measured against `Main_MiSTer/file_io.cpp` rather than the wiki's
+ paraphrase), and `RustyNES_MiSTer-vX.Y.Z.rbf` is attached to the GitHub
+ release on both repositories. Through v2.6.14 the version-named file was
+ the one committed, a divergence from MiSTer's date convention that
+ v2.6.15 closed (the sibling's `docs/bitstream-release.md`).
- [x] Unique Home folder chosen — **since v2.6.7**, resolved against
`Main_MiSTer`'s own parser rather than an example: `CONF_STR`'s opening
`"RustyNES;;"` gives `/media/fat/games/RustyNES`, and it is unique because
diff --git a/to-dos/mister/contribution-checklist.md b/to-dos/mister/contribution-checklist.md
index 8088c921..2abe8f25 100644
--- a/to-dos/mister/contribution-checklist.md
+++ b/to-dos/mister/contribution-checklist.md
@@ -234,7 +234,8 @@ Settled at **v2.6.6**, except the one item that needs a board.
against the repository while there was still time to find one that does
not hold. The evidence exists and is
linkable today (`docs/rung1-6502.md` through `docs/rung7-mappers.md`, the
- 142-gate suite, the mutation records). What is missing is the act of
+ co-simulation ladder — 199 passed, 0 failed, 1 expected failure on-die and
+ 200 / 0 / 1 off-die at v3.0.0 — and the mutation records). What is missing is the act of
pointing a reviewer at it, which happens in the submission email.
**Unblocks at the hardware-verification release (v3.x)**, which is the
submission (ADR 0043; it was v3.0.0 under ADR 0041).
diff --git a/to-dos/plans/v2.6.16-interlock-plan.md b/to-dos/plans/v2.6.16-interlock-plan.md
index af933c26..787ab1f5 100644
--- a/to-dos/plans/v2.6.16-interlock-plan.md
+++ b/to-dos/plans/v2.6.16-interlock-plan.md
@@ -88,7 +88,9 @@ would be the same error in the opposite direction from the one being corrected.
- Worst PRG latency **under 24 cycles** with stated margin, measured by
`tb/sdram_arb_main.cpp` against synthetic worst-case traffic — not by the
console gates, which have now been shown not to reach it.
-- The suite stays **147 of 147 on the die**, which is what ships, and off-die.
+- The suite stays **147 of 147 on the die**, which is what ships, and reaches
+ **148 of 148 off-die** (the same 147 plus this version's SDRAM latency gate,
+ which reports N/A on the die).
- Every new or changed gate demonstrated to fail by mutation.
- Bitstream rebuilt, timing at all four corners, and **`BUILD_DATE` matching the
filename datecode** — v2.6.15's own check.
From 19750b73bcdf921c2e0590e61bdf55a81fb81cfd Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:38:04 -0400
Subject: [PATCH 17/44] docs(agents): stage only intended paths; CodeRabbit
plan and status
Five bot findings on the agent notes (PRs #529 and #534), re-verified
on v3.0.0.
docs/agents/tooling-traps.md recommended `git add -A && pre-commit run
` as the safe way to lint changed files. `git add -A` stages every
modified and untracked file, so the next commit takes unrelated work
with it. The bullet now recommends staging only the intended paths
(`git add -- `) or a NUL-delimited list
(`git diff -z --name-only -- '*.md' | xargs -0 pre-commit run
--files`), and records the old advice as superseded. It also names the
condition the trailing-space hazard needs: the list must reach argv
intact, which happens in zsh (no word splitting of an unquoted
parameter) or when the variable is quoted; bash or sh splitting an
unquoted `$FILES` drops the space.
The same file said the `gh api -F body=@file` form appears in the
PR-ceremony rule in review-bots.md; no file under docs/agents/ other
than tooling-traps.md contains `body=@`. The rule is now stated where
the pointer was: `-F body=@reply.md`, never `-F body=-`.
docs/agents/review-bots.md said CodeRabbit reports `Plan: Pro Plus` and
that its check "never resolves". Its comments on #583 and #589 report
`Plan: Advanced`, and its status context on #589 is SUCCESS once a
review is requested (gh pr view 589 --json statusCheckRollup). The
bullet now dates the plan and says the context stays pending only until
a review is requested.
Verified: markdownlint passes on both files; gh api
repos/doublegate/RustyNES/issues/589/comments shows "Plan: Advanced".
Bot findings RustyNES#529/A/5744324402/1, RustyNES#529/A/5744324402/2,
RustyNES#529/A/5744324402/3, RustyNES#534/T/PRRT_kwDOQrRows6kG_Mv,
RustyNES#534/T/PRRT_kwDOQrRows6kG_M4.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
docs/agents/review-bots.md | 2 +-
docs/agents/tooling-traps.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/agents/review-bots.md b/docs/agents/review-bots.md
index 5afb418b..2e8ea5ec 100644
--- a/docs/agents/review-bots.md
+++ b/docs/agents/review-bots.md
@@ -11,7 +11,7 @@
> written to be re-checkable, not to be taken on trust.
- **CodeRabbit is configured as a 3rd PR review bot** (`.coderabbit.yaml`, added 2026-07-20 in PR #316), alongside gemini-code-assist and copilot-pull-request-reviewer — the reply-and-resolve-every-thread ceremony applies to its threads too. **It does NOT review automatically on this repository — see the next bullet.** This one said "automated" and "applies before any merge" from #316 until v2.3.9, which read as a promise that its findings were arriving; they were not. Configured `profile: assertive` (not the "chill" default) and a `tools{}`/`path_instructions`/custom-checks set audited against this repo's actual file footprint, not guessed. `tone_instructions` has a hard 250-character schema limit that fails validation silently on the CodeRabbit side — after editing `.coderabbit.yaml`, verify with a `@coderabbitai configuration` PR comment and confirm every changed field shows `Source: Repository YAML (base)`.
-- **CodeRabbit does NOT auto-review this repository, and its check never resolves.** Its own comment states the reason: *"This repository does not receive automatic reviews because it has fewer than 10 stars."* The configuration from #316 is loaded and correct (it reports `Path: .coderabbit.yaml`, `Review profile: ASSERTIVE`, `Plan: Pro Plus`) — only the automatic trigger is absent. Two consequences, both of which have been operated wrongly. **First, the ceremony below silently covers two bots, not three.** AGENTS.md credits CodeRabbit with catching a critical fast-forward defect (#358) and a use-after-free in the v2.3.5 libretro tables; none of that arrives unless a review is *requested*. Post `@coderabbitai review` on any PR whose diff warrants it. **Second, waiting for zero pending checks never terminates**: the `CodeRabbit` context sits with a null status and a null conclusion forever, so a healthy PR here reads as ~27 checks with exactly one permanently pending. The merge criterion is `CI success` = SUCCESS with every OTHER check complete — never "nothing pending", and never a bare check count either: a **CONFLICTING** PR also shows a short list, because GitHub cannot build a merge ref so CI never runs at all, and it is otherwise indistinguishable from a healthy PR whose jobs are still queuing. Gate on all three together — `CI success` green, every non-`CodeRabbit` check complete, and `mergeable == MERGEABLE`. **Also note the check is not required and removing it is not the fix**: `main`'s ruleset requires exactly one context, `CI success` (verified via `gh api repos/OWNER/REPO/rules/branches/main`), so the pending `CodeRabbit` context blocks nothing — and suppressing its status reporting would also lose the check on the PRs where a review IS triggered, which is the one time it carries information. And CodeRabbit rate-limits manual triggers per developer, so a batch of `@coderabbitai review` comments can silently produce no reviews at all.
+- **CodeRabbit does NOT auto-review this repository, and its check stays pending until a review is requested.** Its own comment states the reason: *"This repository does not receive automatic reviews because it has fewer than 10 stars."* The configuration from #316 is loaded and correct (it reports `Path: .coderabbit.yaml`, `Review profile: ASSERTIVE`, and `Plan: Advanced` on #583 and #589 as of 2026-10-06; it read `Plan: Pro Plus` when this was first written) — only the automatic trigger is absent. Two consequences, both of which have been operated wrongly. **First, the ceremony below silently covers two bots, not three.** AGENTS.md credits CodeRabbit with catching a critical fast-forward defect (#358) and a use-after-free in the v2.3.5 libretro tables; none of that arrives unless a review is *requested*. Post `@coderabbitai review` on any PR whose diff warrants it. **Second, waiting for zero pending checks never terminates on a PR nobody has asked CodeRabbit to review**: the `CodeRabbit` context sits pending until a review is requested (on #586 and #589 it reported SUCCESS once one was), so a healthy PR here reads as ~27 checks with exactly one pending. The merge criterion is `CI success` = SUCCESS with every OTHER check complete — never "nothing pending", and never a bare check count either: a **CONFLICTING** PR also shows a short list, because GitHub cannot build a merge ref so CI never runs at all, and it is otherwise indistinguishable from a healthy PR whose jobs are still queuing. Gate on all three together — `CI success` green, every non-`CodeRabbit` check complete, and `mergeable == MERGEABLE`. **Also note the check is not required and removing it is not the fix**: `main`'s ruleset requires exactly one context, `CI success` (verified via `gh api repos/OWNER/REPO/rules/branches/main`), so the pending `CodeRabbit` context blocks nothing — and suppressing its status reporting would also lose the check on the PRs where a review IS triggered, which is the one time it carries information. And CodeRabbit rate-limits manual triggers per developer, so a batch of `@coderabbitai review` comments can silently produce no reviews at all.
- **The bot-comment ceremony must read the review BODIES, not just the resolvable threads.** CodeRabbit posts "Outside diff range" and other suppressed findings **inside the review body**, where they are invisible to a resolve-every-thread sweep — and Copilot does the same. This has now cost the project three times: issue #360 (an untested attestation path) reached `main` unaddressed; two findings of the same class on #357 were genuine defects, **one critical** (two threads producing frames during fast-forward under threaded display-sync, fixed in #358); and a **use-after-free** in the v2.3.5 libretro controller tables was caught only because the review body was read. A green "all threads resolved" is not evidence the review was addressed. Fetch the bodies explicitly — `gh pr view --json reviews --jq '.reviews[].body'` — and triage every finding in them before merging.
- **TWO KNOBS CAN BE ONE DECISION, AND A REVIEW THAT CORRECTS PROSE MAY CORRECT THE CODE IN THE WRONG DIRECTION (v2.6.20).** v2.6.19 narrowed the OAM2 increment window from 256 to 258 AND deleted the dot-257 freeze latch. Both changes came from review, both were self-consistent, and **both were wrong** — because they are not two changes. The 33rd increment candidate is suppressed by `!oam2_overflowed`, so 256 and 258 perform the same 32 increments and differ only in PHASE; and the latch deletion's stated justification rested explicitly on the 258 window ("with the wrap corrected to dot 320, reading the live flag cannot disturb a fetch that has already concluded"). So there are two coherent packages — 258-without-latch and 256-with-latch — and only the second is the hardware's. v2.6.19 passed both OAM2 entries with the first, **by cancellation**, and the cancellation was invisible because **nothing read the counter**: `oam2_fetch_addr` was write-only for a whole release. The moment `$2004`'s post-fetch rest value reads it, restoring 256 alone breaks sprite rendering by **6 of 61,440 pixels** in two gates, because `oam2_overflowed` is also consumed by the four sprite-fetch sites and the wrap moves from dot 320 to dot 318, INSIDE the fetch. **Before adopting a review's fix, check what its justification depends on** — here the sentence named its own premise, and the premise was the defect. And v2.6.19's comment predicted the blind spot in its own words: "mostly invisible ... nothing downstream can tell 318 from 320." A difference called invisible is a statement about the instruments that exist, not about the hardware.
diff --git a/docs/agents/tooling-traps.md b/docs/agents/tooling-traps.md
index 373a892d..2bb26354 100644
--- a/docs/agents/tooling-traps.md
+++ b/docs/agents/tooling-traps.md
@@ -10,10 +10,10 @@
> Every bullet is a measured finding with its evidence attached; they are
> written to be re-checkable, not to be taken on trust.
-- **AND `--files` IS THE QUIET FAILURE MODE OF THE SAME TOOL — `pre-commit run --files ` SILENTLY DROPS A PATH THAT DOES NOT EXIST (2026-09-19).** It prints `markdownlint.....(no files to check)Skipped`, which reads as a pass, while the hook never ran. Isolated at the argv level with `subprocess.run` and an explicit list, so no shell is involved: `'docs/STATUS.md'` -> **Passed**, `'docs/STATUS.md '` (one trailing space) -> **Skipped**. The bad path comes from the idiom that looks careful — `FILES=$(git diff --name-only | grep '\.md$' | tr '\n' ' ')` — where the `tr` appends a trailing space; without it the same pipeline passes. So the bullet above is right about the hazard it names and is the *source* of this one. **The safe form is neither: stage the changes and run the bare hook** (`git add -A && pre-commit run `), which takes its file set from the index and cannot be handed a malformed path. Treat `no files to check` / `Skipped` as a **failure to verify**, never a clean result — the same shape as a test filter matching nothing, and as `grep -c` returning 0 for a pattern that cannot match.
+- **AND `--files` IS THE QUIET FAILURE MODE OF THE SAME TOOL — `pre-commit run --files ` SILENTLY DROPS A PATH THAT DOES NOT EXIST (2026-09-19).** It prints `markdownlint.....(no files to check)Skipped`, which reads as a pass, while the hook never ran. Isolated at the argv level with `subprocess.run` and an explicit list, so no shell is involved: `'docs/STATUS.md'` -> **Passed**, `'docs/STATUS.md '` (one trailing space) -> **Skipped**. The bad path comes from the idiom that looks careful — `FILES=$(git diff --name-only | grep '\.md$' | tr '\n' ' ')` — where the `tr` appends a trailing space; without it the same pipeline passes. The trailing space survives only when the list reaches argv intact — in zsh (this harness's shell, which does not word-split an unquoted `$FILES`) or when the variable is quoted (`"$FILES"`); bash or sh splitting an unquoted `$FILES` drops it (qualified at v3.0.1). So the bullet above is right about the hazard it names and is the *source* of this one. **The safe form is neither, and it is NOT `git add -A`** (which this bullet recommended until v3.0.1: it stages every modified and untracked file in the tree, so the next commit takes unrelated work with it). Either **stage only the intended paths and run the bare hook** (`git add -- && pre-commit run `), which takes its file set from the index and cannot be handed a malformed path, or pass a **NUL-delimited** list that no shell splits: `git diff -z --name-only -- '*.md' | xargs -0 pre-commit run --files`. Treat `no files to check` / `Skipped` as a **failure to verify**, never a clean result — the same shape as a test filter matching nothing, and as `grep -c` returning 0 for a pattern that cannot match.
- **DO NOT EDIT A SHELL SCRIPT WHILE BASH IS EXECUTING IT (v2.6.7).** bash reads a script incrementally from a byte offset, so editing `tb/regress.sh` mid-run made the running instance resume mid-token and die with `syntax error near unexpected token )` at a line that parses perfectly — `bash -n` on the same file is clean. The whole ~50-minute run was wasted and, worse, every result before the error came from a file that was changing underneath it, so it is mixed-provenance evidence rather than merely incomplete. **Run long suites from a frozen copy** — but freeze it **IN PLACE**, not elsewhere. `f=$(mktemp --suffix=-frozen.sh tb/.ladder-XXXXXX)`, then `cp tb/regress.sh "$f" || exit 1`, then `( trap 'rm -f "$f"' EXIT; bash "$f" )`. **This bullet has now been corrected five times across three review rounds, and each correction was a measurement.** A fixed name lets two runs overwrite the copy the other is executing. `;` after `cp` runs a STALE copy from a previous run when the copy fails, which is worse than not running. An `EXIT` trap set at an interactive prompt replaces whatever the shell had, so it belongs in a subshell. **`$$` does not make the name unique**: it is the SHELL's pid, so two runs backgrounded from one interactive shell collide — measured, both subshells saw `$$ = 3300706` while their `BASHPID` differed. And `mktemp` is NOT ruled out by the ignore pattern, which is what an earlier revision here claimed: `tb/*-frozen.sh` anchors on the end, and `--suffix` puts the random part BEFORE it — `mktemp --suffix=-frozen.sh tb/.ladder-XXXXXX` yields `tb/.ladder-M5hUHb-frozen.sh`, verified gitignored, unique within a shell, and created atomically. (`--suffix` is GNU coreutils; on a BSD `mktemp` fall back to `$BASHPID`, not `$$`.) `tb/scratch/` is ignored but unusable, since `dirname/..` from it resolves to `tb/` rather than the repository root. Measured 2026-09-20: `regress.sh` locates the repository from its own path (`_REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"`), so a copy under the scratchpad resolved `_REPO_ROOT` to the scratchpad and **all 74 gates failed rc=2** — a result that reads exactly like a mass regression and was nearly reported as one. The advice as it stood named a scratch path and was wrong for any self-locating script; check what `$0` buys a script before relocating it. Same family as the mutation harness overwriting live edits, but the failure surfaces at the END rather than the start.
- **`cargo test` prints `error:` when a TEST FAILS, not only when the build breaks.** A mutation classifier keyed on `^error` reported BUILD-FAILED for three mutations that were all CAUGHT — the inverse of the usual trap, discarding evidence rather than manufacturing it. Classify from the runner's own vocabulary in order: `could not compile`/`error[E` -> BUILD-FAILED, `test result: FAILED` -> CAUGHT, `test result: ok` -> NOT CAUGHT, anything else -> investigate. And **run the baseline through the same classifier**: a baseline that does not land on the "tests pass" branch means the classifier is broken before any mutant has run.
-- **Never pass a prose body as a CLI argument in this harness — `git commit -m`, `gh pr create --body`, `gh pr comment --body`, `gh release create --notes`.** The shell is zsh, so **backticks are command substitution** and `` is an input redirect. A v2.4.0 message documenting three `gh` invocations lost all three to substitution and emitted `no such file or directory: owner` from a literal `repos///...` — the commit succeeded with a mangled body reading "recorded, because it is reusable: RETURNS THE PULL REQUEST". This project's house style puts command examples in commit bodies routinely, so `-m` is structurally the wrong tool: write the message to a file and use `git commit -F`. After amending, grep the message for each phrase that was supposed to survive. **The rule is about the SHELL, not about git, so it covers `gh` too** — hit again on 2026-09-18 with a `gh pr create --body` whose text contained a backtick-quoted filename, which published a body reading "Post-release. only" plus a `command not found: AGENTS.md` on stderr that is easy to miss beside the PR URL the same call prints on success. Read the body from a file every time — `-F` is the short flag on all three, and the long names DIFFER: `gh pr create --body-file`, `gh pr comment --body-file`, `gh release create --notes-file` (there is no `--body-file` on `release create`; verified from `--help`). **`gh api -F` is a DIFFERENT flag** — a typed field, which is why the `-F body=@file` form appears in the PR-ceremony rule — which the v2.6.23 split moved into [`review-bots.md`](review-bots.md), so "elsewhere in this file" (as this sentence read until review on #534) now names the wrong file. Then read back what was actually published (`gh pr view N --json body`) rather than assuming the text you wrote is the text that landed.
+- **Never pass a prose body as a CLI argument in this harness — `git commit -m`, `gh pr create --body`, `gh pr comment --body`, `gh release create --notes`.** The shell is zsh, so **backticks are command substitution** and `` is an input redirect. A v2.4.0 message documenting three `gh` invocations lost all three to substitution and emitted `no such file or directory: owner` from a literal `repos///...` — the commit succeeded with a mangled body reading "recorded, because it is reusable: RETURNS THE PULL REQUEST". This project's house style puts command examples in commit bodies routinely, so `-m` is structurally the wrong tool: write the message to a file and use `git commit -F`. After amending, grep the message for each phrase that was supposed to survive. **The rule is about the SHELL, not about git, so it covers `gh` too** — hit again on 2026-09-18 with a `gh pr create --body` whose text contained a backtick-quoted filename, which published a body reading "Post-release. only" plus a `command not found: AGENTS.md` on stderr that is easy to miss beside the PR URL the same call prints on success. Read the body from a file every time — `-F` is the short flag on all three, and the long names DIFFER: `gh pr create --body-file`, `gh pr comment --body-file`, `gh release create --notes-file` (there is no `--body-file` on `release create`; verified from `--help`). **`gh api -F` is a DIFFERENT flag** — a typed field, and the rule for it lives here: post a reply body with `gh api ... -F body=@reply.md`, which reads the field from a file, and **never `-F body=-`**. (Until v3.0.1 this sentence pointed at the PR-ceremony rule in [`review-bots.md`](review-bots.md), which does not carry the form.) Then read back what was actually published (`gh pr view N --json body`) rather than assuming the text you wrote is the text that landed.
- **Never byte-slice in a panic or format path.** `&text[at..(at + 24).min(text.len())]` panics when the offset lands inside a multi-byte character, and these documents are full of em-dashes and arrows — so the audit would crash *while formatting the diagnostic*, replacing the message explaining the real failure with a char-boundary error about the reporting code. **A diagnostic that can crash the diagnosis is worse than none**, because the failure it exists to explain becomes harder to read than if the excerpt were omitted. Use a char-boundary walk that KEEPS the offset: `s[at..].chars().take(n).collect::()` once `at` is known to be a boundary, or derive it with `char_indices()`. A bare `s.chars().take(n)` is NOT the fix — it returns the string's prefix and silently drops the failing region the excerpt exists to show, which is how a diagnostic becomes confidently wrong instead of merely absent. Caught in review on #534.
- **`make` COLLAPSES EVERY RECIPE FAILURE TO EXIT 2, so a harness cannot discriminate failure modes through it (2026-09-20).** Measured directly: a recipe exiting `1`, `2` or `139` all give `make` exit **2**. This matters because the diff tools here draw a careful three-way distinction — `fb_diff.py`'s own header says "Exit codes, three of them and never two: 0 identical / 1 differ / **2 the comparison could NOT be made**" — and every gate is invoked through `make`, which destroys it one layer up. A `run_xfail` that graded on the exit code was therefore **structurally incapable** of telling "the documented divergence" from "the gate never reached its comparison", which is the one thing an expected-failure gate must distinguish. The fix is to grade on **output evidence** — a required regex the gate's output must carry — because output is the one thing `make` does not rewrite. Generalises: before keying any logic on a subprocess's exit code, check what the layers between you and it do to that code.
From 1da5e491a623586bf9767ef6e4c4e653e175663e Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:38:05 -0400
Subject: [PATCH 18/44] docs: correct stale figures, signatures and history
across the records
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Seventeen bot findings plus four drift items found during adjudication,
each re-verified on v3.0.0 against the code or git history.
Code-vs-doc:
- `Mapper::mix_audio` returns `i32` since v2.2.3 (mapper.rs); both
docs/apu-2a03.md and docs/expansion-audio.md still printed `-> i16`.
- docs/frontend.md described v2.2.9's embedded `show_viewport_immediate`
detach with real OS windows "tracked as follow-up"; v2.3.0's
detached.rs gives each panel its own OS window (docs/STATUS.md says
so). The paragraph now describes detached.rs and keeps v2.2.9 as
history.
- ios/RustyNES/GameView.swift's comments named only Movies/TAStudio and
Cheats as non-pausing; `updateMenuPaused()` pauses only for States,
Settings and the Debugger, so Netplay, Achievements and Lua also keep
the core running. Both comments now state the rule directly.
- docs/STATUS.md: the implied dummy read covers 22 official opcodes plus
the six one-byte unofficial NOPs (28 opcode values, 23 match arms
calling `Cpu::implied_dummy_read`), not "23 opcodes"; and one row used
`->` where its neighbour uses the arrow.
- debugger/mod.rs rustdoc on `detachable_window` still showed the old
`⧉` glyph and an `egui::CentralPanel`; the UI uses the Font Awesome
EXPAND/COMPRESS glyphs and an `Area` + central-panel `Frame`.
Counts and figures:
- tests/roms/LICENSES.md: 33 AccuracyCoin sub-tests (git ls-files), not
26, twice; 338 committed `.nes`, not 328; AccuracyCoin 144/144, not
141/141.
- docs/performance.md: the F19 table's mmc3 saving now prints 7.5 us,
the difference of its rounded columns (the unrounded means were not
recorded), with a dated note; the memcpy estimate states its
~10-20 GB/s assumption (also in benches/snapshot_restore.rs); the
5/5 sign-test p is written 1/32 = 0.03125 here and in CHANGELOG.md
(the published v2.3.3 notes stay as recorded).
- docs/scheduler.md quoted ~3.9/2.5 ms and ~3.8/2.6 ms for the same
thing; both now give the shipped fast-path figures from
docs/performance.md "Current figures" (~3.95 / ~2.65 ms).
History:
- CHANGELOG [2.4.1] said the release-anchor audit and backlog sweep
landed "between v2.4.0 and v2.4.1"; both landed in #427 (a1dd5fd0,
09:32) before v2.4.0 merged (b67c4f94, 13:20; merge-base confirms
ancestry). CHANGELOG and VERSION-PLAN.md's v2.4.0 row now agree.
- VERSION-PLAN.md: the v2.3.0 row no longer calls it the head of the
v2.x line; the v2.6.6 row marks its +0.363/+0.245 ns slack as
withdrawn at v2.6.7 with the re-measured pair; the v2.6.16 row says
three passes followed by a failure at +3 are the evidence.
- The v2.0.x mobile-finalization plan gains a banner marking its store
line superseded by ADR 0035, and its iOS lines name the v1.9.x
TestFlight train.
- ROADMAP.md / OVERVIEW.md "Last Updated" set to 2026-10-06, the date
both were last edited (ad32ce3a, 9c23715b).
- SUPPORT.md called the root ROADMAP.md a pre-1.0 snapshot; it is the
maintained project roadmap.
Verified: cargo fmt --all --check; cargo clippy -p rustynes-core -p
rustynes-frontend --all-targets -D warnings; RUSTDOCFLAGS="-D warnings"
cargo doc -p rustynes-frontend --no-deps; cargo test -p
rustynes-test-harness --test release_anchor_audit --test
release_state_prose_audit --test feature_flag_audit pass; markdownlint
passes on every changed Markdown file.
Bot findings RustyNES#194/B/4568745536/to-dos/plans/v2.0.x-mobile-finalization-plan.md:30:159,
RustyNES#201/B/4576269787/ios/RustyNES/GameView.swift:120:162,
RustyNES#342/T/PRRT_kwDOQrRows6WKQcA, RustyNES#344/T/PRRT_kwDOQrRows6WZY5g,
RustyNES#346/A/5186922238/1, RustyNES#347/T/PRRT_kwDOQrRows6Woict,
RustyNES#365/A/5286477079/2, RustyNES#367/A/5287157195/1,
RustyNES#372/A/5293088673/3, RustyNES#430/T/PRRT_kwDOQrRows6a8ZeA,
RustyNES#439/T/PRRT_kwDOQrRows6bbFw-, RustyNES#477/T/PRRT_kwDOQrRows6dnsnk,
RustyNES#495/T/PRRT_kwDOQrRows6ffGcV, RustyNES#507/A/5643265643/4,
RustyNES#507/T/PRRT_kwDOQrRows6hs6U8.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
CHANGELOG.md | 11 +++---
OVERVIEW.md | 2 +-
ROADMAP.md | 2 +-
SUPPORT.md | 2 +-
VERSION-PLAN.md | 8 ++---
.../rustynes-core/benches/snapshot_restore.rs | 5 +--
crates/rustynes-frontend/src/debugger/mod.rs | 8 +++--
docs/STATUS.md | 2 +-
docs/apu-2a03.md | 2 +-
docs/expansion-audio.md | 2 +-
docs/frontend.md | 34 ++++++++++++-------
docs/performance.md | 13 +++++--
docs/scheduler.md | 13 ++++---
ios/RustyNES/GameView.swift | 15 ++++----
tests/roms/LICENSES.md | 8 ++---
.../plans/v2.0.x-mobile-finalization-plan.md | 17 +++++++---
16 files changed, 88 insertions(+), 56 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0af1ee8a..8d68388b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8316,8 +8316,9 @@ against the sweep: five CAUGHT, two NOT CAUGHT and both explained.
This release also carries **v2.4.0 "Concordance"**, which merged to `main` and was never
tagged; entries below marked *(v2.4.0 item)* belong to it. Two further entries — the
-standing release-anchor audit and the deferred-backlog sweep — belong to neither, having
-landed between the two, and are called out where they appear.
+standing release-anchor audit and the deferred-backlog sweep — belong to neither: they
+landed separately in #427, after v2.3.9 and before v2.4.0 merged, and ship in v2.4.1.
+They are called out where they appear.
### Added
@@ -8526,7 +8527,7 @@ landed between the two, and are called out where they appear.
that diagnostic.
- **A standing release-anchor audit — the drift v2.3.9 corrected by hand cannot
- recur silently.** (Landed between v2.4.0 and v2.4.1; part of neither.) `crates/rustynes-test-harness/tests/release_anchor_audit.rs`
+ recur silently.** (Landed separately in #427, after v2.3.9 and before v2.4.0 merged; part of neither, shipped in v2.4.1.) `crates/rustynes-test-harness/tests/release_anchor_audit.rs`
pins **15 anchors across 10 documents** against `[workspace.package] version`:
the README badge and Current Release section, `docs/STATUS.md`, both `AGENTS.md`
anchors plus its "never claim a later version" guard, `VERSION-PLAN.md` (header
@@ -8598,7 +8599,7 @@ landed between the two, and are called out where they appear.
immediately** — the rule that incident produced in the first place.
- **`to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md` swept entry by entry** (landed
- between v2.4.0 and v2.4.1; part of neither), against
+ separately in #427, after v2.3.9 and before v2.4.0 merged; part of neither), against
`main` @ `fdfb2c04`. Eleven entries struck, each carrying its evidence inline —
a file that exists, a workflow line number, a test that says so — rather than a
bare tick, so a closure can be disagreed with.
@@ -10028,7 +10029,7 @@ optimization campaign is closed on the strength of three measured rejections.
where the prediction fits, which a test pins directly. Measured at
`run_ahead = 3` over five paired, Latin-square rounds: convergence **12.12 s →
2.80 s**, frames held for the wrong duration **4.82% → 2.24%**, 5/5 pairs on
- both, exact one-sided sign p = 0.0312. An alternative arm that cleared the
+ both, exact one-sided sign p = 1/32 = 0.03125. An alternative arm that cleared the
produce-cost ring on each depth change converged in 4.0 s and matched on
cadence but produced an audio underrun in **every** capture, and was rejected.
Additive-only (65 insertions, 0 deletions) inside the engage branch, which the
diff --git a/OVERVIEW.md b/OVERVIEW.md
index 4a407826..10a88be3 100644
--- a/OVERVIEW.md
+++ b/OVERVIEW.md
@@ -1,7 +1,7 @@
# RustyNES Overview
**Document Version:** 2.1.0
-**Last Updated:** 2026-09-25
+**Last Updated:** 2026-10-06
**Applies to:** RustyNES v3.0.0
---
diff --git a/ROADMAP.md b/ROADMAP.md
index 76e23aab..6cb670b6 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,7 +1,7 @@
# RustyNES Development Roadmap
**Document Version:** 2.0.4
-**Last Updated:** 2026-10-04
+**Last Updated:** 2026-10-06
**Project Status:** v3.0.0 "Cornerstone" released — the API major: every break since v2.x in one place, a core timing epoch for movies and netplay, the last MMC3 timing gap closed in both cores, and a release-candidate MiSTer core. Built on **v2.9.9 "Ballast"** (the release candidate for v3.0.0: all four audit scopes re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it with the release-candidate bitstream pair; the tenth release of the v2.9.x line and the sixth of the line to v3.0.0 (ADR 0043, amended)) and **v2.9.8 "Vanguard"** (v3.0.0's breaking changes landed early) and **v2.9.7 "Tandem"** (the desktop's features on the web and on phones). **No hardware has run any bitstream**; the mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29).
---
diff --git a/SUPPORT.md b/SUPPORT.md
index 7609d599..38018151 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -9,7 +9,7 @@ Thank you for using RustyNES! This document provides guidance on how to get help
1. **Check the Documentation**
- [README.md](README.md) - Project overview and quick start
- [docs/](docs/) - Comprehensive documentation
- - [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) - Current development status (the root `ROADMAP.md` is a pre-1.0 historical snapshot)
+ - [`to-dos/ROADMAP.md`](to-dos/ROADMAP.md) - Current development status (the root `ROADMAP.md` is the project-level roadmap, updated with each release)
- [FAQ](#frequently-asked-questions) - Common questions (below)
2. **Search Existing Resources**
diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md
index 8125b9f1..34b20f89 100644
--- a/VERSION-PLAN.md
+++ b/VERSION-PLAN.md
@@ -85,7 +85,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.2.1 – v2.2.5** | Housekeeping (v2.2.1); build / distribution / CI-integrity — libretro buildbot + supply-chain hardening (v2.2.2 "Conduit"); performance + accuracy-closure (v2.2.3 "Datum"); libretro/RetroArch distribution (v2.2.4 "Cartridge"); provenance / licensing / documentation integrity (v2.2.5 "Colophon") |
| **v2.2.6 – v2.2.9** | The **de-monetization + NESdev-remediation** line — RustyNES made permanently open-source and income-free (v2.2.6 "Almanac", ADR 0035); expansion-audio fidelity (v2.2.7 "Timbre II"); gamma-correct presentation (v2.2.8 "Aperture II"); TAS/movie wiring + detachable tool windows + the **relicense to GPL-3.0-or-later** (v2.2.9 "Studio II", ADR 0036) |
| **v2.2.9 "Studio II"** | TAS/movie wiring + the GPL-3.0-or-later relicense — see `CHANGELOG.md` `[2.2.9]` |
-| **v2.3.0 "Datum II"** | Head of the v2.x line; **closes** the v2.2.6 → v2.3.0 remediation line. PPU-accuracy capstone — SMB left-edge + hybrid-address (Rad Racer) verified already-correct against the AccuracyCoin oracle and locked with an exact-141/141 regression gate; hybrid-address provenance finalized (doc/oracle-derived); true multi-viewport OS-window detach; the emulator-lock frame-pacing fix; a −5.1% byte-identical PPU optimization — see `CHANGELOG.md` `[2.3.0]` |
+| **v2.3.0 "Datum II"** | **Closes** the v2.2.6 → v2.3.0 remediation line. PPU-accuracy capstone — SMB left-edge + hybrid-address (Rad Racer) verified already-correct against the AccuracyCoin oracle and locked with an exact-141/141 regression gate; hybrid-address provenance finalized (doc/oracle-derived); true multi-viewport OS-window detach; the emulator-lock frame-pacing fix; a −5.1% byte-identical PPU optimization — see `CHANGELOG.md` `[2.3.0]` |
| **v2.3.1 "Plumb Line"** | Measurement apparatus made trustworthy, then used: a harness-free frame probe, per-source-file subsystem attribution (which recovers the **APU at 18.7% of frame**, invisible in the symbol profile), an adoption A/B with an A/B/A order-bias control, and a contention-aware relative gate. **Ten core hot-path candidates measured, all ten rejected** via six distinct mechanisms — **no emulation-core changes**, AccuracyCoin exactly 141/141 — see `CHANGELOG.md` `[2.3.1]` |
| **v2.3.2 "Lucid"** | Pixel provenance — click any pixel for its full causal chain, down to **the CPU instruction and cycle that last wrote each byte** — plus deterministic replay attestation (`rustynes verify`). All `debug-hooks`-gated and output-only, so AccuracyCoin holds exactly 141/141 — see `CHANGELOG.md` `[2.3.2]` |
| **v2.3.3 "Cadence"** | Display pacing. The run-ahead throttle oscillation attributed to a **stale median** — the gate counted 120 frames of a 600-sample ring, so a p50 at index 300 could not leave the previous depth; **6-7 transitions per 24 s → 1**, spurious releases **2 → 0**. The engage arm now predicts instead of waiting (`run_ahead = 3` converges in **2.8 s vs 12.1 s**, 5/5 paired rounds, p = 0.0312) while releasing still demands a real measurement. Compositor refresh via `wp_presentation`, divisor display-sync, and a validity gate that fails closed; dropped frames **135-254 → 1-9**. Two arms measured and **rejected** with their numbers. No emulation-core changes — see `CHANGELOG.md` `[2.3.3]` |
@@ -95,7 +95,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.3.7 "Overtone"** | Audio provenance — the APU counterpart of Pixel Provenance: a per-register write attribution (*what wrote this, and from which instruction*) and a per-CPU-cycle mix trace carrying **raw** pre-mix channel values, so a record describes the chip rather than the user's mixer sliders. Its real subject is the trap it inherited: the v2.3.6 fix had enumerated **one caller** rather than the mechanism, and `rustynes-probe` had **three more** same-timeline restores, so running the Latency Oracle or the RAM Atlas emptied both provenance panels for the session. The test named for the contract compared `nes.snapshot()`, and provenance is deliberately not in the snapshot — it asserted something strictly weaker than its own name and passed throughout. A randomized sweep of the save-state parse boundary found **four** panics in VRC7's OPLL where hand-tracing found one; the all-`0xFF` payload *concealed* one. Both core crates change, so AccuracyCoin 141/141 is **verified** — see `CHANGELOG.md` `[2.3.7]` |
| **v2.3.8 "Parallax"** | The **Divergence Lens** — which pixels differ, not just which frame. A trial reduces each frame to one `u64`: the right shape for *detecting* a difference and the wrong shape for *explaining* one. `divergence::localise` keeps the full output and reports the *shape* — population count, first pixel in raster order, inclusive bounding box — on the **index** framebuffer (the PPU's own per-pixel output before the palette lookup), then hands the located pixel to Pixel Provenance so the answer is a cause rather than a coordinate. Three verdicts, and the third is the point: **`Inconclusive`** never arrives wearing the shape of `Identical`. Cut from its own boundary commit rather than from `main`, so its artifacts contain exactly the Lens — see `CHANGELOG.md` `[2.3.8]` |
| **v2.3.9 "Crucible"** | What the gates actually cover. **The docs-only CI skip had never worked** — `predicate-quantifier` defaults to `some`, so the `code` filter's leading `'**'` matched everything and all seven `!` exclusions were dead from the day they were written; fixed with **two** filter steps because `accuracy` is a list of *alternatives* that becomes unsatisfiable under `every`. **`test-roms` now runs at review time** (path-filtered; 11 of the last 40 merged PRs). **A freeze from one cartridge kept writing into the next**, closed by a ROM-transition sweep under one rule: derived output discarded, user-authored input kept, and only input that actively *writes* neutralised. **The config file is written atomically and durably** (seven properties, five from review). 257 lines of dead code removed; 25 of 29 `#[allow(dead_code)]` attributes suppressed nothing; `undocumented_unsafe_blocks` made a gate. Both core crates change, so AccuracyCoin 141/141 is **verified** — see `CHANGELOG.md` `[2.3.9]` |
-| **v2.4.0 "Concordance"** (merged, never tagged; shipped inside v2.4.1) | Atomic, durable writes on every path that persists user data — the plan named three call sites and there were **four**, the fourth being `save_state.rs`, where a truncated write is a user's game progress, while `per_game.rs` was not in the plan at all because it *looks* correct and held two of seven properties. Review found **four more silent successes**, each an error discarded under a comment explaining the rest of the operation. Plus `Nes::timeline_generation()` (session-local, deliberately not in the save state) and `release_anchor_audit.rs`, pinning **15 release anchors across 10 documents**. |
+| **v2.4.0 "Concordance"** (merged, never tagged; shipped inside v2.4.1) | Atomic, durable writes on every path that persists user data — the plan named three call sites and there were **four**, the fourth being `save_state.rs`, where a truncated write is a user's game progress, while `per_game.rs` was not in the plan at all because it *looks* correct and held two of seven properties. Review found **four more silent successes**, each an error discarded under a comment explaining the rest of the operation. Plus `Nes::timeline_generation()` (session-local, deliberately not in the save state). (`release_anchor_audit.rs`, pinning **15 release anchors across 10 documents**, is not part of v2.4.0: it landed separately in #427, after v2.3.9 and before v2.4.0 merged, and ships in v2.4.1.) |
| **v2.4.1 "Fabric"** | RustyNES as the oracle a new implementation is verified against, opening the v2.4.1 → v2.5.0 "Fabric" line. `crates/rustynes-cosim` exposes the emulator through a narrow C ABI a Verilator testbench links, plus `nes_golden_export`. **Excluding it from the workspace is the load-bearing detail**: cargo unifies features, `irq-timing-trace` selects a *different* per-dot loop in `Bus::tick_one_cpu_cycle`, and the accuracy battery had therefore been validating a scheduler no user runs (+1.2% to +1.9%, below the 3% bar — the percentage was never the argument). Also: the first `run_frame()` after power-on advances **zero** cycles, and two trace-gated core modules had never been linted. Carries v2.4.0. AccuracyCoin 141/141 verified. |
| **v2.4.2 "Cairn"** | The **rung-0 compare surface**. Rolling per-cycle hash checkpoints, measured at **15,263x** smaller than the equivalent CSV (89,343 cycles: 5,372,427 bytes of `irq.csv` against 352 bytes of `ckpt.bin`), so the first mismatch names a 4096-cycle window and only that window is re-run with full capture. **What is hashed is a decision about hardware, not about convenience**: `Observable` is the subset of `CycleRecord`'s 29 fields a device can genuinely produce, the IRQ pair is OR'd before hashing because hardware has one wire-OR’d /IRQ pin, and `pc` is labelled DUT-observable rather than pin-observable. The v2.4.2 acceptance gate is executable and found a defect: a divergence at cycle 0 was reported in a window that did not contain it. The emulation core is untouched. |
| **v2.4.3 "Touchstone"** | The two Fabric-plan risks that had to be settled **before any RTL exists**, both answered by evidence contradicting the plan. **Risk 4, the Quartus subset, is FITTED**: 17.0.2 Build 602 on a 5CSEBA6U23I7 produced a placed-and-routed netlist with **0 errors and 0 synthesis warnings**, and the 2 KiB array inferred as **2 M10K blocks with 29 total registers** — not 16,413 — from the source style alone, no `ramstyle`. The `initial` block produced a real MIF and the `enum` was one-hot encoded. Nine constructs promoted to *fitted*; plain `case`, `priority case` and `$bits` deliberately left *documented* because the kitchen sink does not exercise them. **Risk 1, the `sys/` licence, inverts the hedge**: 57 files, **zero GPL-2.0-only**, and a non-optional GPL-3.0-or-later `hps_io.sv` forces the combined bitstream **up** to GPL-3.0-or-later — already RustyNES's licence, so no relicensing. The emulation core is untouched. |
@@ -121,7 +121,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.6.3 "Mainspring"** | The DUT runs on one master clock, and four enables that were never enabling. A mainspring is the single wound source that drives a clock's whole train, which is what `nes_top` becomes here: it took its clock enables as INPUTS and the testbench generated the dot phase; it now takes a single 21.477272 MHz master clock and derives `ce`, `ppu_ce` and `ppu_access` itself -- the shape Quartus compiles. It is built in RustyNES's own v2.0.0 "Timebase" shape, **two independent accumulators in master-clock units, never reset to one another**, and that is not stylistic: a modulo-`CPU_DIV` phase counter looks equivalent on NTSC and cannot express PAL at all, where 16 master clocks per CPU cycle and 5 per dot is 3.2 dots per cycle. `ACCESS_MC` and the PPU phase offset are DERIVED from the oracle's `read_split`/`write_split` rather than swept, and **five testbench phase knobs are retired** -- they existed to find this phase, and the answer is now compiled into the core. **It found four enables that were never enabling.** The old testbench tied `ce` high and pulsed the clock once per CPU cycle, so the clock did the gating the enable was supposed to do and any ungated `always_ff` was correct only by accident; under a real master clock each fires twelve times. Two were already known (the PPU register block at v2.5.7, the open-bus decay reload) and **two were not**: the DMC's DMA acknowledge, where the sample pointer advanced by TWELVE per byte and 324,182 of 357,360 cycles diverged, and the frame-counter IRQ set points, where the IRQ line rose eleven master clocks early so the CPU took the interrupt one instruction sooner -- caught by blargg's `08.irq_timing`, a third-party ROM rather than our own trace agreeing with itself. A **compensating** fix was found and REJECTED: delaying the APU's IRQ by one cycle also gave 66 of 66 and is indistinguishable from the real fix by gate result; `cpu6502.sv` already implements the oracle's second-to-last-cycle recognition, correctly gated, so a second delay would have cancelled an APU-side error. Looking for a cause AFTER the fix worked is what separated them. **blargg's `instr_test-v5` battery becomes a standing gate** -- sixteen third-party ROMs, ~2.68 M cycles each, compared per cycle, **16 of 16 exact**, taking the suite from 50 gates to **66 green, 0 failed**. Every rung-1 ROM before these was written inside the project, so the rung could only ask questions someone there thought to ask; these found **three defects the entire self-written corpus had missed**, none of them in the opcodes the battery was run to validate: `RRA` fed its `ADC` stage the carry from BEFORE the instruction (its bus trace was identical on both sides and only the accumulator differed, by one, surfacing nine cycles later), the 8-cycle indirect read-modify-write forms addressed the indexed target during their POINTER fetch cycles, and the PPU I/O-bus latch never decayed -- a 2C02 defect reached from a CPU ROM, three rungs after rung 3 closed. The five `SH`-group stores close the decoder at **256 of 256** opcodes. **The decay constant is where documentation and oracle contradict each other on a quantity a gate depends on.** The wiki says 3-30 ms; RustyNES uses 558.7 ms. Swept against the full 66-gate suite rather than argued: 30 ms fails 9 gates, 50 ms fails 5, 100 ms 3, 200 ms 2, 300 ms 1, and 558.7 ms is the first value failing none. The binding constraint is one measurable property of one ROM -- `10-branches` has a longest gap between group-0 refreshes of 936,697 CPU cycles, or 2,810,091 dots -- and that prediction was TESTED: 2,809,000 dots leaves 52 divergences and 2,811,000 is exact, so the corpus demands >= 523.4 ms. Documentation and corpus are incompatible by a factor of ~17, this rung has no independent oracle to adjudicate, and the constant stays the oracle's, stays labelled **fitted**, and stays a `localparam` so it can move when something can decide. That is Fabric risk 6 -- the oracle can be wrong -- arriving as a measurement rather than a caveat. **Rung 5 reaches an end-to-end AccuracyCoin run**, and the oracle gains `accuracycoin_status`: a status vector decoded against the 146-entry catalog and comparable **entry for entry**, including `Skipped` and `NotRun`, naming every disagreement by test rather than by address. First measurement: **137 of 146 entries agree, 9 differ**, six sharing one failure code -- a pattern a pass count of 137 would have hidden. Producing the vector is this release's deliverable; making the two agree is v2.6.4. Also: an Android dependency refresh (AGP 9.2.1 -> 9.3.2, Compose compiler 2.3.10 -> 2.3.21, `compose-bom` 2026.08.00) with the Gradle 10 deprecations cleared and the AGP/Kotlin interlock measured out of the published POMs rather than assumed; a Rust and Actions refresh; and `markdownlint-cli` v0.39.0 -> v0.49.1, where the pin held since v2.3.9 as a hazard is finally MEASURED -- `MD060/table-column-style` reads this corpus as `compact` and reports 1,936 findings across 122 files, so the style already in use is pinned instead, measuring zero and rewriting no document. **No `rustynes-{cpu,ppu,apu,mappers,core}` changes**, so AccuracyCoin **141/141 (100.00%, RAM decoder)** and nestest 0-diff hold by construction -- and were run anyway. |
| **v2.6.4 "Rubric"** | OAM DMA lands, all nine AccuracyCoin disagreements close, and then the gate that certified them is measured to cover 88 of 146 entries. **`$4014` was a register the console decoded and did nothing with** -- the DUT had never spent the 513 cycles an OAM DMA costs. It lands as a real bus master (halt on a read cycle, optional alignment, 256 read/write pairs) from `nesdev_wiki/DMA.xhtml`, with the documented DMC-get precedence costing OAM its alignment as well as its slot; its halt and alignment were fitted to the oracle first and corrected from the wiki, recorded rather than squashed. The **`SH` group** closes in two steps, the second named by the residual of the first: the AND with the address high byte is RDY-conditional, and the dummy-read cycle is addressing-mode dependent, so `SHA (d),Y`'s `tcyc==3` is a pointer-high fetch. Those took the vector 9 -> 3. A rubric is the authoritative statement of the rules, written by whoever set the test -- which is literally where all three fixes came from. **AccuracyCoin is MIT-licensed and its assembly source is one `curl` away**; this plan's own note that it "is not vendored" is true of this repository and had been read as unavailable. The source explains every assertion, and settled three entries in minutes. **`Open Bus`**: a read of `$4015` does not drive the data bus and its D5 is open bus -- rules the nesdev pages do not state at all, and its stimulus (`LDX #$16 / LDA $40FF,X`) is the exact instruction the trace divergence had been localised to independently. **`Interrupt flag latency`**: branches poll before cycles 2 and 4 and NEVER before 3, so a taken branch that does not cross a page has no poll at its last cycle. **`NMI Overlap BRK`**: an interrupt sequence does not poll (stated in the wiki, missed here), and the hijack window was one CPU cycle narrow at its late edge -- where the comment above the line had argued at length for the wrong version and named this very entry as the test that would catch it. **A `Fail(N)` names test N, one-based**, and decoding it as an index had made a REGRESSION (test 7 -> test 5) read as progress; v2.6.3's reading that six entries "shared one failure code" and therefore one cause is retracted with it. **A fix that closes one gate and opens another is a scope measurement**: the first poll fix moved the poll for EVERY instruction, closed the entry and regressed `apupulse026` and `blargg08`; narrowed to branches alone, nothing regresses. Three AccuracyCoin sub-test ROMs become standing **verdict** gates (69 -> **72 green, 0 failed**), verdict rather than bus by measurement -- their per-cycle surfaces are dominated by the open PPU I/O-latch item at 2,331,867 of 4,467,082 cycles. **7 of 8 RTL mutations CAUGHT**, the eighth classified INERT by byte-comparing 14,294,736 bytes of trace. **Then the coverage finding.** The vector reported identical across all 146 with **58 entries `NotRun` on BOTH sides**: the 600-frame window reaches the CPU catalog and asks nothing about the APU, PPU, sprite-evaluation or PPU-misc suites. 4500 frames executes all 146 (134,012,761 cycles). `accuracycoin_status` now prints coverage and REFUSES when any entry is unrun on both sides (3 of 3 mutations caught). Widening it found a real defect on its first run, at cycle 20,636,325: a halted CPU mid-`LDA $2007` held `ppu_sel` high through a DMC steal, so the sample fetch got the PPU read buffer instead of the cartridge -- the comment above the line stated the intent it violated. **Rung 5 does NOT close**: measured on both sides, the oracle climbs 88 -> 95 -> 117 -> 120 -> 146 while the DUT goes 88 -> 5 -> 5 -> 5, flat for 74 M cycles. The DUT sits in a three-cycle self-loop at `$80DF` = `INC $EC` / `JMP $80DF`, AccuracyCoin's MENU IDLE LOOP, with only the five results the power-on path writes -- so it RESET and returned to the menu rather than hanging inside a test, a reading published from catalog order and retracted after one PC probe. Reported as unavailable rather than as "141 of 146 differ", which is one defect and not 141. **No `rustynes-{cpu,ppu,apu,mappers,core}` changes**, so AccuracyCoin **141/141 (RAM decoder)** and nestest 0-diff hold by construction. |
| **v2.6.5 "Muster"** | Rung 5 closes. The AccuracyCoin status vector is **IDENTICAL entry for entry across all 146 entries**, with **146 of 146 executed on both sides** and none `NotRun` -- measured over the 4500-frame golden, 134,012,761 cycles, where the same gate read **5 of 146 executed and 22 differing** at the version's start. A muster is a roll call where every name is called AND answered, which is this release's two-clause acceptance exactly; the second clause is v2.6.4's addition, and without it an identical vector over entries that never executed is a pass, and was one. **Five PPU defects close the last six differing entries, and four were invisible to every gate that existed when the version opened.** The recurring shape is a gate agreeing about a question it was never asked. **The background shift registers' RELOAD and their shift clock need SEPARATE gates**: with one shared gate `BG Serial In` was not merely failing but ARITHMETICALLY UNREACHABLE -- reload dots are absolute, so on a re-enable the next reload is at most seven dots away and the reload takes `hi[14:7]`, discarding every serial-in one, so bit 7 can never hold one on any alignment for any stimulus. Modelling both structures reproduces BOTH measured shifter values, the oracle's `F807` falling out of the split model unfitted and the common gate's repeating `0001 0003 ... 007F 0000` containing the `0003` the DUT reported. **That fix alone left the gate red**, which is why it is recorded separately: **the sprite X counters are NOT gated on rendering**, and AccuracyCoin's `Stale Sprite Shift Registers` test 2 says so outright -- "Rendering was disabled for 18 ppu cycles, but the sprite counters were NOT halted during that time". This core froze them, so a disable/enable pair pushed every sprite right by the width of the window; the probe showed `bopq=1` with sprite zero's counter still reading **18**, the exact width of the test's own off-window. **The ROM that STATES the rule passes either way** -- it expects no hit at X=`$FE`, and a sprite shoved 18 dots further right is also off the end of the line. **The PPUADDR second-write `v <- t` copy is DELAYED**, and `nesdev_wiki/PPU_scrolling.xhtml` says so inside the write sequence itself ("wait 1 to 1.5 dots after the write completes"); this core committed it in the write's own edge. Swept 1 to 4 dots, all of which close `Hybrid Addresses`, against a control at 8 and 12 that fails -- the control is the load-bearing half, since four consecutive passes is also what a build ignoring the override would produce. **The pre-render line CLEARS secondary OAM**: the whole evaluation block INCLUDING the clear was gated on `scanline < 240`, so the pre-render line kept what scanline 239's evaluation had left and the next frame's scanline 0 drew it. **No sprite can ever render on scanline 0**, because OAM Y is stored one less than the display row. A sprite-0 probe over the full battery named it in one run: 24 hits in 134 M cycles, four of them at scanline 0, one per frame, immediately after the single legitimate scanline-239 hit. The fifth, the octal latch holding across the read dot, is verified by exactly ONE gate and was unverifiable until the fourth landed -- the two compose the hybrid address together and neither produces it alone. **A DIAGNOSIS IS RETRACTED.** The residual was read as a two-dot CPU/PPU alignment error, from comparing per-dot record spans across two instruments. Three configurations refute it: at the committed alignment the two consoles execute identical `pc`, `bus_addr` and `bus_access` for **1,695,131 cycles** and issue both PPUADDR writes at the same cycles, while a two-dot power-on shift moves the first divergence back to 593,228 and takes the differing share from **5.13% to 66.80%**, and `LEAD_CPU_CYCLES` 1 -> 2 diverges by scanline 8. The "two dots" was two instruments stamping their records at different points in the cycle -- the v2.5.7 lesson, third occurrence. Also: `rustynes-cosim`'s `state_trace_records_carry_their_cpu_cycle` was gated on a feature no CI step enabled, so it **ran nowhere** -- a regression test the gate could not reach, which is the shape the surrounding CI steps exist to prevent; it runs now, and the test genuinely inapplicable under that feature is gated out with its reason rather than left failing. **The oracle changes on the DEFAULT path** (a `Controller::write_strobe` owed-shift fix), so **AccuracyCoin 141/141 (RAM decoder)** and nestest 0-diff are **VERIFIED, not asserted**. |
-| **v2.6.6 "Chassis"** | The console becomes a MiSTer core. `sys/` is vendored **byte-identical** to `Template_MiSTer@3ea1134c` -- 57 files, 0 content differences, 0 files on one side only, which is acceptance clause 1 measured rather than asserted -- and `rtl/emu.sv`, `rtl/pll.v`, `rtl/palette.sv`, `rtl/video_timing.sv`, `rtl/apu_mixer.sv` and `rtl/audio_dc_block.sv` make the verified console into something Quartus 17.0.2 compiles for a 5CSEBA6U23I7: **0 errors, timing CLOSED**, and the warning count taken from **111 to 3** -- all three inside `sys/` or Quartus's own megafunction, none citing this project's RTL -- worst setup **+0.363 ns**, worst hold **+0.245 ns**, **TNS 0.000 on every clock**, the console's own clock at **+13.514 ns** and an Fmax of **30.26 MHz** against the 21.477272 MHz it needs. **The emulation core is unchanged, so the co-simulation suite is an ACCEPTANCE CRITERION rather than a formality** (87 passed, 0 failed) -- and it earned that immediately. **An M10K read is REGISTERED**: `cart_nrom.sv` read its arrays asynchronously under a comment claiming that inferred block RAM "from the source style alone", so 40 KiB of cartridge was **393,216 registers against roughly 166,000 available** and Quartus refused it outright. The project had already written the rule down -- the README has said since v2.4.3 that the core would use "synchronous read with a registered address, no asynchronous read anywhere" -- and nothing before this rung had ever asked Quartus about the cartridge, because v2.4.3 fitted a 2 KiB probe and 2 KiB fits either way. The one-clock latency was **probed rather than argued**: `bus_addr` is stable for all twelve clocks of a CPU cycle and the access lands at the seventh, so the console never sees it; the HARNESS did, and its device cross-checks moved to where the CPU actually samples. **Two defects were found only by asking whether the outputs would work on real hardware.** The audio would have been a full-scale DC rail -- the mixer is unipolar with silence at zero, and the framework maps unsigned silence to **-32768**, an offset into the DAC and into HDMI audio rather than a quiet channel; `audio_dc_block` is the coupling capacitor real hardware has, cornered at the console's own documented ~90 Hz. And two OSD entries did nothing, "CRT 25%" and "CRT 50%" advertised while `VGA_SL` was tied to zero. **The finding worth keeping: a convention enforced by a glob has no error message.** `sys/sys_top.sdc` groups the core's clock by matching the hierarchical name pattern `*\|pll\|pll_inst\|altera_pll_i\|*`, so a differently-named PLL matched **no group at all** and every crossing to the framework's audio, HDMI and HPS domains was analysed as synchronous -- **-13.901 ns** of slack and **-422,601 ns** of TNS on a design whose Fmax was already above requirement, with the compile succeeding and the Assembler reporting 0 errors and 0 warnings throughout. Reading Fmax and slack TOGETHER is what separates a missing constraint from a slow design. Nine such findings are recorded in the sibling's `docs/rung6-integration.md`, including a Quartus **internal error** on the 256-entry decode table and the three fixes for it that were measured and failed. **Not claimed:** no DE10-Nano and no SuperStation One are attached to this machine, so a booting core, a synced display, audible sound and a working pad are NOT established -- rung 6 did NOT close at v2.6.7 either, because no board was attached there, and it moves to the first release after one exists. **Zero emulation-core changes**, so **AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction**, and were re-run. |
+| **v2.6.6 "Chassis"** | The console becomes a MiSTer core. `sys/` is vendored **byte-identical** to `Template_MiSTer@3ea1134c` -- 57 files, 0 content differences, 0 files on one side only, which is acceptance clause 1 measured rather than asserted -- and `rtl/emu.sv`, `rtl/pll.v`, `rtl/palette.sv`, `rtl/video_timing.sv`, `rtl/apu_mixer.sv` and `rtl/audio_dc_block.sv` make the verified console into something Quartus 17.0.2 compiles for a 5CSEBA6U23I7: **0 errors, timing CLOSED**, and the warning count taken from **111 to 3** -- all three inside `sys/` or Quartus's own megafunction, none citing this project's RTL -- worst setup **+0.363 ns**, worst hold **+0.245 ns** (both **withdrawn at v2.6.7**: no clean rebuild reproduces them, and the re-measured pair is +0.108 ns setup / +0.042 ns hold; see the next row), **TNS 0.000 on every clock**, the console's own clock at **+13.514 ns** and an Fmax of **30.26 MHz** against the 21.477272 MHz it needs. **The emulation core is unchanged, so the co-simulation suite is an ACCEPTANCE CRITERION rather than a formality** (87 passed, 0 failed) -- and it earned that immediately. **An M10K read is REGISTERED**: `cart_nrom.sv` read its arrays asynchronously under a comment claiming that inferred block RAM "from the source style alone", so 40 KiB of cartridge was **393,216 registers against roughly 166,000 available** and Quartus refused it outright. The project had already written the rule down -- the README has said since v2.4.3 that the core would use "synchronous read with a registered address, no asynchronous read anywhere" -- and nothing before this rung had ever asked Quartus about the cartridge, because v2.4.3 fitted a 2 KiB probe and 2 KiB fits either way. The one-clock latency was **probed rather than argued**: `bus_addr` is stable for all twelve clocks of a CPU cycle and the access lands at the seventh, so the console never sees it; the HARNESS did, and its device cross-checks moved to where the CPU actually samples. **Two defects were found only by asking whether the outputs would work on real hardware.** The audio would have been a full-scale DC rail -- the mixer is unipolar with silence at zero, and the framework maps unsigned silence to **-32768**, an offset into the DAC and into HDMI audio rather than a quiet channel; `audio_dc_block` is the coupling capacitor real hardware has, cornered at the console's own documented ~90 Hz. And two OSD entries did nothing, "CRT 25%" and "CRT 50%" advertised while `VGA_SL` was tied to zero. **The finding worth keeping: a convention enforced by a glob has no error message.** `sys/sys_top.sdc` groups the core's clock by matching the hierarchical name pattern `*\|pll\|pll_inst\|altera_pll_i\|*`, so a differently-named PLL matched **no group at all** and every crossing to the framework's audio, HDMI and HPS domains was analysed as synchronous -- **-13.901 ns** of slack and **-422,601 ns** of TNS on a design whose Fmax was already above requirement, with the compile succeeding and the Assembler reporting 0 errors and 0 warnings throughout. Reading Fmax and slack TOGETHER is what separates a missing constraint from a slow design. Nine such findings are recorded in the sibling's `docs/rung6-integration.md`, including a Quartus **internal error** on the 256-entry decode table and the three fixes for it that were measured and failed. **Not claimed:** no DE10-Nano and no SuperStation One are attached to this machine, so a booting core, a synced display, audible sound and a working pad are NOT established -- rung 6 did NOT close at v2.6.7 either, because no board was attached there, and it moves to the first release after one exists. **Zero emulation-core changes**, so **AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff hold by construction**, and were re-run. |
| **v2.6.7 "Detent"** | The bitstream becomes a **published, reproducible** release artifact, and a one-cycle disagreement is located to the cycle it happens on. Every release from here ships a `.rbf`, committed to the sibling's `releases/` and attached to the GitHub release on **both** repositories -- reversing v2.6.6, because the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one. Reproducibility is **measured**: a from-scratch compile and an incremental one produce a byte-identical bitstream. The same measurement **withdrew v2.6.6's published slack figures** -- no corner of a clean rebuild reproduces them, the innocent explanation was checked first and refuted, and the correct pair is **+0.108 ns setup / +0.042 ns hold** at the binding corner. **The release gate had been reading the wrong corner**, and its checker was wrong twice before mutation found both: it extracted ZERO rows and called that "no negative slack", then reported FOURTEEN clocks from a report emptied of data. Caveat C2 **CLOSES**: the first residual was a trace observation point (3 to 11 checkpoint comparisons passing), and the second was real -- its FIRST fix was refuted, blargg dropping 11/11 to 4 of 11 including the ROM written to probe that timing, and **that refutation found the right fix**, since it proved the sequencer's maturation was correctly placed. Separating only the interrupt clear lands it at **write+3**, the documented cycle. Checkpoint streams 11 identical to **52 of 58**, suite 87 to **122**, with **51 checkpoint comparisons**. The gate states its blind spot: no gated golden raises an NMI. The emulation core is unchanged. **Rung 6 does not close** -- no board is attached to this machine. |
| **v2.6.8 "Arrears"** | The gates v2.6.7 fixed and never widened. **A deny list is an assertion about the THING UNDER TEST**, so v2.6.7 changing both the DUT (the `$4017` interrupt-clear split) and the harness (the `Observable` completed at end-of-cycle, `nmi_line` wired to the effective line) re-opened every exclusion -- and nothing re-measured one. **Four of the six denied checkpoint streams were passing**: `irqlat048` (22 checkpoints identical), `ppuvbl023` (175, through cycle 714,737), `ppuvbl024` (291) and `ppuvbl025` (175); `apuconflict039` and `ppuoamcorrupt052` genuinely differ and are restated from current measurements. **Three of them were not run by the suite AT ALL** -- committed golden, ROM and manifest, named nowhere in `regress.sh` except the deny list, which only ever governed the auto-attach, so removing them from it was **INERT** until they were also iterated. **The nestest gate compared 265,000 cycles against a 5,062,688-cycle golden**: correct while caveat C6 was open, left behind the moment it closed, and now **all 5,062,680 overlapping cycles match** with the window DERIVED from the manifest rather than written down -- a literal is how this gate went wrong twice. Coverage **19x**. **Caveat C4 CLOSES, by demonstration**: nestest raises `nmi_line` on **3,592** cycles and had no nine-field comparison at all, because it uses `nestest-gate` so the auto-attach could not reach it; wiring it makes a mutation reverting `o.nmi_line` to its pre-v2.6.7 constant **CAUGHT** at checkpoint 28 of 1,237, **while the bus gate passed the identical run**. `ppuspr0` was the last uncovered golden, so the set closes: 59 = 40 loop + 16 auto + nestest + 2 denied, none unreached. Suite **128 passed, 0 failed, 0 skipped** (from 123), nine-field comparisons **51 -> 57**. The widened gate's first catch was **this release's own change** (a window derived from `obs.bin`'s size, 8 short because the stream starts at cycle 8), and a "harness path bug" was **refuted** before a fix was written. The emulation core is untouched, so AccuracyCoin 141/141 holds by construction. **Rung 6 does not close** -- no board, confirmed by checking. |
| **v2.6.9 "Abeyance"** | An exclusion hides improvement as well as regression. Abeyance is a state of temporary suspension in which a right still exists and nobody exercises it -- which is what a deny-list entry is: the coverage is still there, still computable, and nothing looks at it. **Both remaining denied co-simulation streams close, and the larger one was never the console.** `apuconflict039` had been carried for seven releases as a declared diagnostic whose bus surface "carries nine divergences BY DESIGN"; the nine were a defect in the HARNESS -- on a cycle the CPU is held, the testbench built its record's bus data from a stale local rather than from the RTL's own latch -- and taking it from the latch makes the stream identical on **all 357,361 overlapping cycles and all 88 checkpoints**. "By design" is the phrase that stopped anyone re-checking it, because it reads as a property of the thing under test when it was a property of the instrument reading it. The other stream differs on **exactly one cycle**, a documented and attributed OAM-corruption asymmetry, and carrying that needed an instrument the suite did not have: **the PLANNED mechanism was refuted by its own mutation pass**, because an allowance by checkpoint index cannot work on a rolling hash -- one divergent cycle poisons every checkpoint after it, so allowing the first differing window simply moved the failure to the next one, and allowing the rest is the all-or-nothing deny it was meant to replace. `obs_diff9.py --allow-cycle` puts the allowance on the per-cycle comparison instead, where an attributed difference costs **one cycle rather than seventy-one checkpoints** -- 357,360 of 357,361, against nothing at all before -- and it fails **both ways**, so a DUT that improves cannot leave a stale allowance quietly hiding coverage. Six mutations confirm it, including a cycle outside the compared window being refused rather than allowed to match nothing. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction and were re-run anyway. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. |
@@ -131,7 +131,7 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide
| **v2.6.13 "Slack"** | The cartridge outgrows the die, and three consumers want the same bus. An SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge, written from the AS4C32M16SB-7 datasheet revision 1.4 -- no third-party controller read, ADR 0037 applying. The budget the previous step worked to was **one figure read off the fetch structure and never measured**; `nes_top`'s `CHR_LAT` sweep asks the console directly and gets **three answers**: a background or sprite fetch tolerates 28 cycles and uses 17, the `$2007` data port tolerates 8, and the CPU sampling at mc7 has 24 -- four cycles of every budget going to the console-domain crossing, which is not optional because publishing a runtime modulo combinationally into an 11.64 ns domain costs **-24.769 ns** of setup. The `$2007` port could never fit, so it leaves the shared bus entirely through a `BUFFER_HANDSHAKE` on a port of its own on the arbiter -- affordable because the CPU does not read that buffer until its next `$2007` access. **That fix shipped a defect only a banked cartridge could see**: the request carried the RAW fourteen bits the PPU presents, which is right by coincidence on NROM because the mapper's translation is the identity there and reads **bank zero** on everything else, so `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read `$00` where the oracle reads `$01`, `$01`, `$02`. The fix **removes** the address rather than correcting it -- `cart.sv` already publishes the translation for the fetch path, so there is one source of truth for where CHR lives instead of two that agree only on NROM. **A deletion was then refuted by one cycle**: with the address fixed, a control issuing at a flat +7 passed both gates, which read as the anti-contention deferral buying nothing, so it was removed -- and the deployed code issued at +6, and both gates failed again. That single cycle is the measurement of how thin the CPU's off-die deadline is, and the concrete argument for scheduling the bus rather than arbitrating it. **Two inferred latches Verilator cannot see**: `ppu2c02` assigned two signals only under `BUFFER_HANDSHAKE`, so in the shipped on-die build the only assignment either reached was the reset branch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue. **And a defect in the harness**: `USE_SDRAM` reaches Verilator as `-G` rather than as a file, so make ran ONE binary under both configurations' names and a log labelled on-die was the off-die build, reproducing its failures exactly; closed with a stamp-file prerequisite, demonstrated by mutation. The **open row** lands (a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM respected by an early close in idle), and two MiSTer tickets close: `sdram_sz` consumed **validity bit first**, so a power-on `0x0000` cannot read as "no board", and `status_menumask` computed rather than tied off. **Off the die the console passes 142 of 142**, every gate the on-die build passes, with better timing margin and 384 fewer M10K blocks -- and it still **ships on** the die, because an off-die core cannot run at all without the add-on while rung 7's five mapper families fit at 468 of 553 blocks. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
| **v2.6.14 "Docket"** | The submission checklist becomes auditable, and auditing it finds five boxes already true, two ticked on evidence that expired, and one that **could never have been ticked honestly**. v2.7.0 IS the submission, so `to-dos/mister/contribution-checklist.md` decides readiness -- and it had 30 boxes, 16 unticked, and **fourteen of those sixteen saying nothing about why**. An unticked box with no reason cannot be told apart from work outstanding, work blocked elsewhere, and **work already done and never ticked**; the third case occurred **five times**, so the list reported the project as further from submission than it is by a fifth of its own length. **The impossible box** asked that `docs/provenance.md` state "that no NES core was ever opened" -- which that document's own *Do not self-certify* section forbids, so satisfying it required writing the one sentence the provenance rules exist to prevent. **Re-measuring the ticked half** found two more, v2.6.9's lesson in a new document: `RustyNES.sdc` still claimed "exactly one core clock", true of v2.6.3 and false since v2.6.13; and the `.qsf` entry's "all 109 pin assignments" is 145 from two scripts. **The alarming reading of the first was checked before it was written down** -- `set_clock_groups -exclusive` cuts paths BETWEEN groups, all three core-PLL outputs match one glob, and v2.6.13's own -24.769 ns crossing measurement is only observable because those paths ARE analysed. **The naming divergence is measured from `Main_MiSTer/file_io.cpp`** rather than the wiki: `get_display_name` truncates the display name at the literal `_20`, so a version-named bitstream makes every release a SEPARATE core entry, ordered alphabetically -- putting v2.6.9 after v2.6.13. Not fixed: version-naming is a maintainer decision. **The task board had the same defect and one row worse** -- four delivered items never ticked, and an SDRAM row whose precondition "after a board exists" v2.6.13 did not follow, having accepted the controller against a datasheet-derived model; rung 7's own lesson repeating one row below where it is written. **A v2.6.13 claim is retracted**: MMC1 was written up as unimplemented and is decoded at three sites with a gate green since rung 7. The gate asserts a SHAPE rather than a judgement, with five mutations. Bitstream **byte-identical** to v2.6.13's, which is the point -- the only sibling change is a comment. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
| **v2.6.15 "Warrant"** | The claims the submission will make become checkable, and the instrument pays the oracle back. **The `.rbf` name this core shipped would have distributed NOTHING** -- carried since v2.6.7 as a style item awaiting a decision, and it is not one: `Distribution_MiSTer`'s builder strips a datecode by taking the stem's last nine characters, requires `_` plus exactly eight digits, and SKIPS OUTRIGHT any file that yields none, so an accepted core would appear in the wiki's Cores table and ship nothing with no error raised anywhere. A second parser in the firmware has a different rule again, and `_YYYYMMDD` is the only form satisfying both -- so two names, because there are two audiences and only one is a parser. **Two of the four R1/R2 residuals were never IRQ-timing residuals**: `mmc3_test` and `mmc3_test_2` are the same suite twice, and sub-test 2 of `5-MMC3` carries the IDENTICAL `set_test` string in both while differing by one instruction -- the successor inserts a second `clock_counter` before the first assertion, so blargg WITHDREW the verdict this emulator fails. Adopting it anyway makes one ROM pass and regresses both `scanline_timing` ROMs from sub-test 3 to 2; reverted, with the numbers. **Three gates that did not exist**: `sys/` verbatim rested on one measurement taken eight releases earlier and now pins all 57 files, catching stray ones too; the `.qsf` published TWO seed tables disagreeing about the pinned seed's margin by 0.155 ns; and `bump_release.py`, which dropped a release from the chain in two consecutive releases, turns out to RELOCATE it thirty entries away rather than drop it. **An accuracy gate someone else can run** -- nine rung-1 ROMs export from a PINNED oracle commit and compare in CI, so the evidence stops being a document describing a check a reader cannot run, which is the bar the contributing page sets for AI-assisted code. **`cpu_interrupts_v2` on the DUT**, five of five, the first INDEPENDENT interrupt oracle -- and `5-branch_delays_irq` adjudicates the exact behaviour v2.6.7 changed in the emulator from documentation reasoning alone. **`T-ORACLE-001`'s opening claim is RETRACTED**: this emulator does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 -- "Counter should be clocked 241 times in PPU frame" -- has asserted every release. The claim came from a trace that carries no CHR address column and therefore cannot see an A12 rise at all. **One flagged stale claim was not stale**, and saying so is the discipline: the sibling's ledger changelog was correct, and needed only for its silence to be legible. The emulation core is unchanged, so **AccuracyCoin 141/141** and nestest 0-diff hold by construction. |
-| **v2.6.16 "Interlock"** | The arbiter's numbers describe a stimulus, not the console. `tb/sdram_arb_main.cpp` has reported the CHR fetch with seven cycles of margin and the CPU short of its deadline by four since v2.6.13, under a stimulus that issues both requests on the SAME cycle. Measured at the arbiter's own ports on off-die builds of three workloads, the console produces **zero** such coincidences over fifteen million SDRAM cycles and a **minimum separation of two**, because both requesters are locked to one master clock at a fixed alignment. So both figures are wrong about the console in **opposite directions**: the CPU **meets** its deadline, 24 against 24, on all 240,303 requests, at **zero margin**; the fetch reaches **23 against a derived 22**. That exceedance is not a defect, and the control is why — `CHR_LAT` adds four SDRAM cycles per step, and off-die `ppurender` matches the oracle on all 61,440 pixels at +0, +1 and +2 and **fails at +3**, so four passes are evidence rather than a knob that never reached the compiler, and the derived budget understates the real tolerance by at least nine. The slot-scheduled arbiter this version was scoped around is therefore **not built** — the shortfall decomposes to contention alone (PRG 15 against 24 with CHR silent) and the console's own phase already separates the requesters — and is still worth building, because zero margin means one added cycle anywhere breaks the CPU. A prediction that was refuted is kept with its measurement: moving the stimulus onto the real bases, four mebibytes apart, was expected to worsen the worst case and left both figures byte-identical, because refresh precharges every bank. Two new gates, eight mutations, all CAUGHT. Suite **147 on the die, 148 off**. No Rust changed, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. |
+| **v2.6.16 "Interlock"** | The arbiter's numbers describe a stimulus, not the console. `tb/sdram_arb_main.cpp` has reported the CHR fetch with seven cycles of margin and the CPU short of its deadline by four since v2.6.13, under a stimulus that issues both requests on the SAME cycle. Measured at the arbiter's own ports on off-die builds of three workloads, the console produces **zero** such coincidences over fifteen million SDRAM cycles and a **minimum separation of two**, because both requesters are locked to one master clock at a fixed alignment. So both figures are wrong about the console in **opposite directions**: the CPU **meets** its deadline, 24 against 24, on all 240,303 requests, at **zero margin**; the fetch reaches **23 against a derived 22**. That exceedance is not a defect, and the control is why — `CHR_LAT` adds four SDRAM cycles per step, and off-die `ppurender` matches the oracle on all 61,440 pixels at +0, +1 and +2 and **fails at +3**, so three passes followed by a failure at +3 are evidence rather than a knob that never reached the compiler, and the derived budget understates the real tolerance by at least nine. The slot-scheduled arbiter this version was scoped around is therefore **not built** — the shortfall decomposes to contention alone (PRG 15 against 24 with CHR silent) and the console's own phase already separates the requesters — and is still worth building, because zero margin means one added cycle anywhere breaks the CPU. A prediction that was refuted is kept with its measurement: moving the stimulus onto the real bases, four mebibytes apart, was expected to worsen the worst case and left both figures byte-identical, because refresh precharges every bank. Two new gates, eight mutations, all CAUGHT. Suite **147 on the die, 148 off**. No Rust changed, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. |
| **v2.6.17 "Terminus"** | A write lands where the cycle ENDS, and this core does not move to meet it. The AccuracyCoin and TriCNES oracles re-sync to upstream and the battery grows **141 -> 144 assigned tests** across two new pages that RE-HOME eleven existing PPU tests -- so a re-sync is not an append, and a suite-keyed baseline has to be regenerated rather than extended. `OAM2Address` becomes a live counter through sprite fetch and the frozen fetch reads index 0 for every byte, closing two of the three new tests and carrying an approved **SAVE-STATE EPOCH** (`PPU_SNAPSHOT_VERSION` 8 -> 9; pre-v9 `.rns` states no longer load). The third new test names the dots its two `$2001` writes land on, which is how the core came to be measured **two dots early** on both: a 6502 commits a write at phi2, the LAST of a CPU cycle's three PPU dots, and this core applies PPU register writes at M2-low, the first. The move was then made, measured against a first-difference control captured beforehand, and **NOT ADOPTED** -- the write alone reads 141/144 and breaks the independent `ppu_vbl_nmi/10-even_odd_timing`, and the best combination found reads 142/144 against the **143/144** that ships, for a save-state epoch and six re-baselined goldens. Re-deriving that ROM under phi2 PROVED `mask_for_skip_check` is a compensation for the placement (one delay stage instead of two, identical `08 08 09 07`), and two of its three dependants are still un-re-derived, so adopting the mechanism now would replace a documented compensation with an undocumented one. `Misaligned OAM2 Address` was also found to be passing via **two cancelling errors**, both since fixed independently. Ships one core fix nothing in the corpus could see -- the misaligned-OAM `+4 & $FC` realignment, reached 114 times per battery run out of 56,953,944 out-of-range branches and pinned by a targeted test because no ROM's verdict moves -- plus `terminus_control.rs` as a standing first-difference gate. **The planned v2.6.18 is folded here**: that split existed because Terminus was to be a scheduler change with its own ADR and a full re-baseline, and the refutation means it is not. Plan: [`v2.6.18-terminus-plan.md`](to-dos/plans/v2.6.18-terminus-plan.md). |
| **v2.6.18 "Errata"** | The recorded cause was wrong in three ways, and the last AccuracyCoin entry closes -- **144/144, 100.00%, RAM decoder**. `Frozen OAM2 Increment` had a diagnosis pinned in `KNOWN_FAILING` saying a `$2001` enable on dot 256 "takes effect a dot early"; measured per dot, all four writes the ROM names (242, 256, 325, 340) take effect during dot N-1 and are therefore in force from the START of dot N -- exactly where the ROM says, so the core was never early. The failing sub-test was **4**, the false-positive guard, not 2 or 3: `TEST_FrozenOAM2Inc` has no `INC Option<(u32, u32)> {
///
/// - **Main-window pass** (`target == None`): if `id` is docked, render the normal
/// [`egui::Window`] seeded with `cfg` (first-open position / size / resizability)
-/// plus a small "⧉ Detach" button that adds `id` to `detached`. If `id` is
+/// plus a small Detach button (the Font Awesome `icons::glyph::EXPAND` glyph)
+/// that adds `id` to `detached`. If `id` is
/// detached, render NOTHING here — it now lives in its own OS window, which the
/// [`crate::detached::DetachedManager`] created when `App` reconciled the
/// `detached` set.
/// - **Detached pass** (`target == Some(t)`): render content ONLY when `id == t`,
-/// as a [`egui::CentralPanel`] filling that OS window, with a "⧉ Reattach"
-/// button (the OS window's close button reattaches too, handled in `App`).
+/// as an [`egui::Area`] + central-panel [`egui::Frame`] filling that OS window,
+/// with a "Reattach to main window" button (the `icons::glyph::COMPRESS`
+/// glyph; the OS window's close button reattaches too, handled in `App`).
/// Non-matching panels early-return so one dispatch paints exactly one panel.
///
/// **Native-only** detach — on wasm (`target` always `None`, no `detached` inserts)
diff --git a/docs/STATUS.md b/docs/STATUS.md
index cd59d845..050bf849 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -2107,7 +2107,7 @@ pirate carts, niche boards) is documented in `docs/compatibility.md`.
| `ppu-state-trace` | `rustynes-ppu`, `rustynes-core`, `rustynes-test-harness` | off | Session-10 per-PPU-dot state-tracing fixture. See `crates/rustynes-ppu/src/state_trace.rs`, ADR-0005, `docs/ppu-trace-tooling.md`. When OFF, every byte of overhead is gone via `#[cfg]` gates inside `Ppu::tick` and on the storage field. CI does not enable it; the fixture is heavy (180 MB / 10-frame visible-only window). Enabled by `cargo test --features test-roms,ppu-state-trace --test ppu_state_trace_fixture`. |
| `ppu-idle-line-fast` | `rustynes-ppu`, `rustynes-core`, `rustynes-test-harness` | off | v2.2.3 P2. The specialized **idle-line** PPU dot path (`Ppu::tick_idle_line_fast`) for the post-render line + vblank lines 242..=260 — 6,820 of the 89,342 NTSC dots, where the per-dot body provably reduces to three rendering-flag assignments. **Byte-identical** (pinned by `fast_dotloop_diff`, incl. a vblank-I/O torture ROM that forces the guard's fall-through arms), but measured **below the >3% adoption bar** — a same-session A/B against a ±0.7% noise floor gives −1.3%/−1.5% on rendering-*disabled* content and +0.2%/+0.4% on the rendering-heavy case that dominates real play. Kept behind a flag rather than deleted; compile-time rather than runtime because the cost *is* the per-dot guard. With it off the field, guard and handler are all absent. See `docs/performance.md` §P2. |
| `commercial-roms` | `rustynes-test-harness` | off | 60-ROM regression bisect harness against user-supplied dumps at `tests/roms/external/`. Snapshots committed; ROM dumps gitignored. Enables `cargo test --features test-roms,commercial-roms --test external_real_games`. |
-| `cpu-implied-dummy-reads` *(removed)* | — | **removed -> default** | **Historical (Sprint 2.3, v1.2.0); the flag no longer exists and the behaviour is UNCONDITIONAL.** It gated canonical cycle-2 PC dummy reads for the 23 implied/accumulator/transfer/flag opcodes per the nesdev 6502 cycle reference, default-off pending the DMC get/put scheduler co-fix (ADR 0007), because with the flag on in isolation the `Implied Dummy Reads` AccuracyCoin test did not flip to PASS. Both the flag and the `cfg` on `Cpu::implied_dummy_read` are gone -- every build performs the dummy read, and that entry passes on the current default build. **This row said `off` for releases after the promotion**, which is the worse direction for a stale row to drift in: it described shipped default behaviour as disabled and invited someone to enable a fix already on. Now gated by `feature_flag_audit.rs`, which found it. |
+| `cpu-implied-dummy-reads` *(removed)* | — | **removed → default** | **Historical (Sprint 2.3, v1.2.0); the flag no longer exists and the behaviour is UNCONDITIONAL.** It gated canonical cycle-2 PC dummy reads for the implied/accumulator/transfer/flag opcodes (22 official opcodes plus the six one-byte unofficial NOPs: 28 opcode values across the 23 match arms that call `Cpu::implied_dummy_read`) per the nesdev 6502 cycle reference, default-off pending the DMC get/put scheduler co-fix (ADR 0007), because with the flag on in isolation the `Implied Dummy Reads` AccuracyCoin test did not flip to PASS. Both the flag and the `cfg` on `Cpu::implied_dummy_read` are gone -- every build performs the dummy read, and that entry passes on the current default build. **This row said `off` for releases after the promotion**, which is the worse direction for a stale row to drift in: it described shipped default behaviour as disabled and invited someone to enable a fix already on. Now gated by `feature_flag_audit.rs`, which found it. |
| `dmc-get-put-scheduler` *(removed)* | — | **removed** | **Historical (v2.0.0-era); the flag was deleted.** Engine-lineage Phase 8 Sprint 3 (v1.2.0). Replaces the v1.1.0 phase-agnostic "noop loop + compensating delays" DMC scheduler in `rustynes-core::bus::service_dmc_dma` with Mesen2's canonical get/put cycle alternation model (`NesCpu.cpp:399-447`). Default-off via parallel-implementation pattern (ADR 0007). This parallel experiment reached only **6/10** on the AccuracyCoin DMA cluster (4 failures in the DMC abort path) and was **superseded and removed**: the default master-clock core closes that DMA cluster **10/10** (see the AccuracyCoin 100% breakdown above). |
| `mc-r1-full-cpu` *(removed)* | — | **removed → default** | **Historical (v2.0.0-era); the flag no longer exists.** This was the v2.0 master-clock umbrella (W3-Stage-4 promotion, 2026-06-10) that reached **AccuracyCoin 100.00% (139/139)** on this one flag; it has since been **promoted to the default core and deleted** — RustyNES v1.0.0 ships this behaviour unconditionally (it is the only scheduler). The composition it bundled, for the record: composes the R1 floor substrate (substrate + dmc-idle-halt + unified APU clock + one-clock parity + stack/implied dummy reads + the promoted DMC-abort stack) PLUS the Stage-4 fold: `mc-r1-branch-poll-points` (Interrupt-flag-latency), `mc-ppu-2007-render-buffer` (`$2007` Stress), `mc-r1-dmc-delayed-4015` (the delayed-`$4015` status on the unified single-driver DMA engine at the breakthrough parity — DMC+OAM / Explicit + Implicit Abort / Delta-Mod / Implied-Dummy), and `mc-r1-oam-dma-reg-window` (APU Register Activation). nestest 0-diff; cpu_interrupts_v2 5/5 strict; SH\* 6/6; save-state round-trips hold (the gated state is serialized). Scope: NTSC-only (PAL/Dendy frame-structure tests ignored under the flag); the `audio_tests` corpus is default-build-only (R1 changes DMC audio timing by design). Default-build promotion is the later Phase-7/F program. See `docs/audit/v2.0-stage4-promotion-2026-06-10.md`. |
diff --git a/docs/apu-2a03.md b/docs/apu-2a03.md
index e49a9025..29aa4561 100644
--- a/docs/apu-2a03.md
+++ b/docs/apu-2a03.md
@@ -452,7 +452,7 @@ AccuracyCoin stays 144/144 and nestest 0-diff with the change.
## Expansion-chip audio
-Six on-cart expansion sound chips are synthesized and summed into the external-audio mix via the `Mapper::mix_audio(&mut self) -> i16` hook (default 0). Each synth core lives in the owning mapper crate, **not** the 2A03 APU crate, because they are cartridge hardware:
+Six on-cart expansion sound chips are synthesized and summed into the external-audio mix via the `Mapper::mix_audio(&mut self) -> i32` hook (default 0; `i16` until v2.2.3). Each synth core lives in the owning mapper crate, **not** the 2A03 APU crate, because they are cartridge hardware:
| Chip | Mapper(s) | Synth core | Clock cadence |
|------------|------------------|-------------------------------------------------------|--------------------------------|
diff --git a/docs/expansion-audio.md b/docs/expansion-audio.md
index bb7368a5..1bd11396 100644
--- a/docs/expansion-audio.md
+++ b/docs/expansion-audio.md
@@ -5,7 +5,7 @@
Several NES cartridge boards carry their **own** sound hardware that mixes into
the console's external-audio input alongside the 2A03. RustyNES synthesizes six
such expansion chips and sums each into the mix through the
-`Mapper::mix_audio(&mut self) -> i16` hook (default `0` for boards with no audio
+`Mapper::mix_audio(&mut self) -> i32` hook (default `0` for boards with no audio
hardware). Each synth core lives in the **owning mapper crate**, not the 2A03
APU crate, because it is cartridge hardware.
diff --git a/docs/frontend.md b/docs/frontend.md
index 5ebcb860..46119a8a 100644
--- a/docs/frontend.md
+++ b/docs/frontend.md
@@ -2156,19 +2156,27 @@ All additive + frontend-only; the core stays byte-identical.
subtitle track at the region's frame rate (NTSC's 60.0988 fps stays
drift-free), for muxing into an A/V dump (`movie_srt::markers_to_srt`).
-A detach / pop-out affordance for tool windows shipped in **v2.2.9 "Studio II"** —
-the shared `detachable_window` helper (`debugger/mod.rs`) pops any of 18 tool
-panels out via `ctx.show_viewport_immediate`, with a Reattach affordance and its
-prior first-open geometry (a `WindowCfg`); native-only (wasm keeps the docked
-`egui::Window`). **Honest scope:** the frontend is currently a single-viewport
-`egui_winit` integration (one `take_egui_input` / `handle_platform_output` for the
-main window, no `viewport_output` handling, `embed_viewports` left at its default
-`true`), so `show_viewport_immediate` renders the panel **embedded in the main
-window** rather than a separate OS window. True OS-window detach — the Windows-10
-trapped-window fix — requires wiring multi-viewport into the render loop
-(`set_embed_viewports(false)`, per-`ViewportId` winit windows + egui states +
-wgpu surfaces, and routing their events); the affordance and geometry plumbing are
-in place for when that lands. Tracked as follow-up.
+**Detached tool windows (v2.3.0 "Datum II").** Every tool panel can be detached
+into its **own real OS window** (`crates/rustynes-frontend/src/detached.rs`) —
+the fix for the Windows-10 "every tool window is trapped inside the main window"
+report. RustyNES does not use egui's native multi-viewport path: each detached
+window owns its own `egui::Context`, `egui_winit::State`, `egui_wgpu::Renderer`
+and `wgpu::Surface`, shares the main `Gfx`'s one instance / adapter / device /
+queue, and is rendered on its own `RedrawRequested`, where the panel's borrows
+(`&mut Nes`, panel state) are re-acquired by re-running the panel dispatch under a
+thread-local render-target filter (`DebuggerOverlay::render_detached_body`).
+Nothing is stashed across frames, so no `unsafe` and no lifetime erasure are
+needed; the module's preamble gives the full reasoning. Native-only: wasm is
+single-canvas and keeps every tool panel docked as an `egui::Window`.
+
+*History:* the detach affordance first shipped in **v2.2.9 "Studio II"** as the
+shared `detachable_window` helper (`debugger/mod.rs`), which popped any of 18 tool
+panels out via `ctx.show_viewport_immediate` with a Reattach affordance and its
+prior first-open geometry (a `WindowCfg`). Because the frontend was then a
+single-viewport `egui_winit` integration (`embed_viewports` left at its default
+`true`), that rendered the panel **embedded in the main window** rather than in a
+separate OS window; v2.3.0's `detached.rs` replaced that embedding (the
+`detachable_window` helper itself remains in `debugger/mod.rs`).
**Deferred (noted for a follow-up):** Virtual Pad (clickable on-screen
controller → `SharedInput`), input Macros feeding the piano-roll pattern-paint,
diff --git a/docs/performance.md b/docs/performance.md
index 30ff1f08..1008e335 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -3367,12 +3367,18 @@ ring — and nothing else uses them. Free win, apparently.
| bench | full | slim | saving |
| --- | ---: | ---: | ---: |
| `nes_restore_quiet_flowing_palette` | 122.8 µs | 115.9 µs | **6.9 µs** |
-| `nes_restore_quiet_mmc3` | 123.7 µs | 116.2 µs | **7.4 µs** |
+| `nes_restore_quiet_mmc3` | 123.7 µs | 116.2 µs | **7.5 µs** |
Against the 2.802 ms `nes_runahead_budget` increment that is **0.25%** — an
order of magnitude under the project's >3% bar. **Rejected before
implementation.**
+*Corrected at v3.0.1:* the `nes_restore_quiet_mmc3` saving was first printed
+as 7.4 µs, but the rounded columns subtract to 7.5 µs and the unrounded
+criterion means were not recorded (commit `697495c3` gives the same rounded
+figures), so the table now shows the difference of what it prints. The 0.25%
+conclusion is unchanged either way.
+
> The first version of this table read 8.4 µs, from a **confounded** probe: it
> booted a fresh `Nes` and ran one frame, while every other bench here uses
> `warmed_nes` (60 frames, rendering enabled, OAM and palette populated). That
@@ -3384,7 +3390,8 @@ implementation.**
It came from the project's own (correct) statement that the framebuffer is **94%
of the snapshot BYTES**, and that was carried silently into a claim about
-**TIME**. 245,760 bytes is ~12-25 µs of memcpy at ordinary bandwidth, so it could
+**TIME**. 245,760 bytes is ~12-25 µs of memcpy at an ordinary ~10-20 GB/s
+(245,760 B / 20 GB/s = 12.3 µs; / 10 GB/s = 24.6 µs), so it could
never have been 94% of a 122 µs restore. The measured share is ~7%. The estimate
was off by 13x.
@@ -3705,7 +3712,7 @@ square. Five paired rounds for the two live arms:
| reset — rejected | 4.00 s | 2.02% | **1.00 (3 of 3)** |
**5/5 paired rounds favour `predict` on both convergence and cadence, exact
-one-sided sign p = 0.0312** — at the floor for n = 5, which is why five rounds
+one-sided sign p = 1/32 = 0.03125** — at the floor for n = 5, which is why five rounds
were run rather than three (three floors at 0.125 and could not have reached
significance whatever it showed).
diff --git a/docs/scheduler.md b/docs/scheduler.md
index 6e050657..f3d520c9 100644
--- a/docs/scheduler.md
+++ b/docs/scheduler.md
@@ -157,11 +157,14 @@ This guarantees that save/load round-trips and a re-played input sequence produc
> These are the original **design-phase aspirations**, not gates. The frame-cost
> figure was not met and is knowingly accepted — the implemented cycle-accurate
-> core measures **~3.9 ms** (nestest) / **~2.5 ms** (flowing palette), ~23% of the
-> 16.639 ms NTSC budget. See `docs/performance.md` §Targets for the measured
-> numbers and why the main optimization levers were measured and rejected.
-
-- Frame cost (single-thread, headless core — no frontend, no present): ≤ 2 ms on a 2018-era laptop x86_64 (Skylake-era) — **aspirational; ~3.8 ms measured and accepted** (`nes_run_frame_nestest`, which renders; the render-light `flowing_palette` workload measures ~2.6 ms).
+> core measures **~3.95 ms** (nestest) / **~2.65 ms** (flowing palette) on the
+> shipped fast dot path, ~23% of the 16.639 ms NTSC budget (the v2.7.0 core,
+> 2026-09-23; v2.9.7's A12 change added about 1.9% on nestest). See
+> `docs/performance.md` §"Current figures" for the measured numbers, the
+> exact-path pair, and why the main optimization levers were measured and
+> rejected.
+
+- Frame cost (single-thread, headless core — no frontend, no present): ≤ 2 ms on a 2018-era laptop x86_64 (Skylake-era) — **aspirational; ~3.95 ms measured and accepted** (`nes_run_frame_nestest_fast`, the shipped fast dot path, which renders; the render-light `flowing_palette` workload measures ~2.65 ms).
- Frame cost including wgpu present + cpal callback: ≤ 5 ms (well under the 16.67 ms budget for 60 fps NTSC).
- Audio callback: lock-free SPSC ring buffer; never block the audio thread.
diff --git a/ios/RustyNES/GameView.swift b/ios/RustyNES/GameView.swift
index 63c7c554..ac8caed7 100644
--- a/ios/RustyNES/GameView.swift
+++ b/ios/RustyNES/GameView.swift
@@ -153,11 +153,12 @@ struct GameView: View {
.sheet(isPresented: $showingDebugger) {
DebuggerView()
}
- // Pause the emulator while a menu/sheet is open so the player doesn't lose
- // progress or hear audio behind it; resume once all are dismissed. The TAS /
- // Movies + Cheats panels are exceptions: recording / playback / live cheats
- // must keep the core running, so they do NOT pause emulation. The read-only
- // debugger DOES pause (its "Step" button advances exactly one frame).
+ // Pause the emulator while one of three sheets is open -- Save States,
+ // Settings, or the read-only Debugger (its "Step" button advances exactly
+ // one frame) -- and resume once all three are dismissed. Every other sheet
+ // (Movies, TAStudio, Cheats, Netplay, Achievements, Lua) keeps the core
+ // running (recording / playback / live cheats need it, which is why those
+ // panels were made exceptions in the first place).
.onChange(of: showingStates) { _ in updateMenuPaused() }
.onChange(of: showingSettings) { _ in updateMenuPaused() }
.onChange(of: showingDebugger) { _ in updateMenuPaused() }
@@ -172,8 +173,8 @@ struct GameView: View {
}
/// Recompute whether a modal that must pause emulation is open (Save States,
- /// Settings, or the read-only Debugger). Cheats / TAStudio / Movies keep the
- /// core running by design.
+ /// Settings, or the read-only Debugger). These three are the only sheets that
+ /// pause; every other sheet keeps the core running by design.
private func updateMenuPaused() {
model.setMenuPaused(showingStates || showingSettings || showingDebugger)
}
diff --git a/tests/roms/LICENSES.md b/tests/roms/LICENSES.md
index 073f46c0..b216ee82 100644
--- a/tests/roms/LICENSES.md
+++ b/tests/roms/LICENSES.md
@@ -162,7 +162,7 @@ deterministic frame-hash visual smoke (no input) in
| `AccuracyCoin/sub-tests/frame-counter-irq.nes` | derived from `AccuracyCoin.asm` (suite 13 / test 2 — `TEST_FrameCounterIRQ`) | NROM (0) | derivative of Chris Siebert | MIT (inherits) |
| `AccuracyCoin/sub-tests/apu-reg-activation.nes` | derived from `AccuracyCoin.asm` (suite 13 / test 6 — `TEST_APURegActivation`) | NROM (0) | derivative of Chris Siebert | MIT (inherits) |
-The sub-test ROMs under `AccuracyCoin/sub-tests/` (26 in total; the four
+The sub-test ROMs under `AccuracyCoin/sub-tests/` (33 in total; the four
with dedicated Rust regression tests are tabulated above, and the rest follow
the identical build + MIT-inheritance pattern) are derivative
works produced by patching the upstream `AccuracyCoin.asm` source to
@@ -188,7 +188,7 @@ AccuracyCoin is a single-NROM-cartridge battery of NES accuracy tests. The ROM i
plus hex error codes) with no `$6000` status protocol. The integration test in
`crates/rustynes-test-harness/tests/accuracycoin.rs` decodes the per-test result
state from RAM and asserts the measured pass rate, which RustyNES holds at
-**141/141 (100.00%)** (see `docs/STATUS.md`).
+**144/144 (100.00%)** (see `docs/STATUS.md`).
## "full palette" ROMs
@@ -280,10 +280,10 @@ none is commercial software.
domain), Damian Yerrick Holy Mapperel variants (zlib, including the mapper 28
/ 78.3 / 118 / 180 boards), and related homebrew — kept out of the
actively-gated set but retained for manual investigation.
-- `AccuracyCoin/sub-tests/` (26 `.nes`): the boot-into-one-test derivatives of
+- `AccuracyCoin/sub-tests/` (33 `.nes`): the boot-into-one-test derivatives of
`AccuracyCoin.asm` described above, all MIT (inheriting upstream).
-The authoritative running total is 328 committed `.nes` files under
+The authoritative running total is 338 committed `.nes` files under
`tests/roms/` (per `git ls-files`; excluding the gitignored `tests/roms/external/`
and any untracked clone contents); no commercial
ROM is among them.
diff --git a/to-dos/plans/v2.0.x-mobile-finalization-plan.md b/to-dos/plans/v2.0.x-mobile-finalization-plan.md
index e6e643fc..12718489 100644
--- a/to-dos/plans/v2.0.x-mobile-finalization-plan.md
+++ b/to-dos/plans/v2.0.x-mobile-finalization-plan.md
@@ -1,5 +1,14 @@
# v2.0.x Mobile Finalization & v2.1.0 Joint Store Launch — plan
+> **Historical record (status as of v3.0.1).** The v2.1.0 joint store launch
+> planned below did not happen, and its dated store line is **superseded by
+> [ADR 0035](../../docs/adr/0035-rustynes-is-permanently-non-commercial.md)**
+> (v2.2.6): RustyNES is permanently open-source and income-free, the apps ship as
+> GitHub-Releases sideload (Android) and TestFlight (iOS) builds, and a free store
+> listing is an unversioned later step with no monetization attached. The plan is
+> kept as written for the record; the section-level notes below mark the parts
+> ADR 0035 removed.
+
> **Maintainer decision, 2026-06-23.** The Android (v1.8.x) and iOS (v1.9.0) apps
> will **not** ship to their app stores on their original, independent timelines.
> Both store launches are **held until after v2.0.0 "Timebase"** so the shipped
@@ -26,7 +35,7 @@ ships and the **phasing** of its final polish.
| Interim iOS | **v1.9.0 → v1.9.9** | iOS/iPadOS built across the **v1.9.x TestFlight train** (foundation → power-user → connectivity → store-readiness → creator tools; mirrors Android v1.8.0–v1.8.9); **TestFlight** | none — **no App Store** |
| Timebase | **v2.0.0** | The one-clock + every-cycle-bus-access master-clock rewrite (ADR 0002). The core the apps re-port onto. | none (desktop release) |
| Android final | **v2.0.1 → v2.0.4** | Final Android additions / modifications / enhancements / fixes, re-ported onto the v2.0.0 core | none (still pre-launch) |
-| iOS final | **v2.0.5 → v2.0.8** | iOS finalization on the v2.0.0 core, building on the v1.9.0 foundation | none (still pre-launch) |
+| iOS final | **v2.0.5 → v2.0.8** | iOS finalization on the v2.0.0 core, building on the v1.9.x TestFlight train | none (still pre-launch) |
| Readiness | **v2.0.9** | True correctness checks + ready-for-release verification for **both** apps + last-minute fixes / testing | both stores brought to review-ready |
| **Joint launch** | **v2.1.0** | Set **all** release flags; **Google Play production + Apple App Store** go live **together** | **both apps released** |
@@ -55,10 +64,10 @@ Nothing freezes. Between now and v2.0.0:
The PGS / Play-Integrity / Chromecast features stay **default-off behind their
build flags** (`PGS_ENABLED`, `PLAY_INTEGRITY_ENABLED`, `CHROMECAST_ENABLED`) —
they are flipped on only at **v2.1.0**, not now.
-- **iOS (v1.9.0):** build the **interim foundation** per
+- **iOS (v1.9.0 → v1.9.9):** build the **interim foundation** per
[`v1.9.0-ios-plan.md`](v1.9.0-ios-plan.md) — the SwiftUI shell + wgpu→Metal
- render path + the shared `rustynes-mobile` bridge — distributed by **sideload /
- TestFlight** only. No App Store submission.
+ render path + the shared `rustynes-mobile` bridge — across the **v1.9.x
+ TestFlight train**, distributed by **TestFlight** only. No App Store submission.
The Google Play **production** launch that was the old v1.8.x "Workstream P"
(maintainer task) is **deferred into this train** and becomes part of **v2.1.0**.
From 97eda4230d917dbdb0f4223cb928dbb46e2e719b Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:41:18 -0400
Subject: [PATCH 19/44] docs(provenance): restore "derived from Mesen2" at the
5B mix-level comment
Found by the v3.0.1 bot-sweep docs agent and confirmed by history: v2.2.5
(0265b3bd), the relicensing release, reworded the Sunsoft 5B mix-level
comment in m069_sunsoft_fme7.rs from
**Target, derived from Mesen2 (the project's accuracy bar) rather than
from our own prior numbers.**
to "**Target, calibrated against Mesen2 ... as an oracle rather than against
our own prior numbers.**" -- in the same commit that added the file's
`// Provenance: ... derived from Mesen2` header. The derivation was therefore
never hidden (header, docs/originality-and-provenance.md section 1, NOTICE),
but the comment AT the derived value described a black-box comparison, which
is the wording this project reserves for genuinely independent code. Under
the never-launder rule that is the wrong direction, and the maintainer chose
to restore it (2026-10-06).
The comment now says "derived from Mesen2", points at the header and section
1, and records that v2.2.5 had reworded it and v3.0.1 restored it, so the
history is at the site rather than only in git.
Checked the rest of that release for the same pattern: v2.2.5 replaced ten
other "Ported from Mesen2 " / "Ported from puNES " site comments
with NESdev-wiki wording plus "no third-party emulator code is incorporated".
All ten have since been re-attributed ("the implementation is derived from
Mesen2's `` (GPL-3.0-or-later). See NOTICE + ... section 1"), and every
cited upstream file is named in a header and in section 1. This comment was
the only one left.
Not changed, raised to the maintainer: rustynes-gfx-shaders/src/lib.rs:311
describes the Bisqwit-style NTSC pass as "an independent implementation ...
no third-party emulator code is incorporated". That is a self-certification
the provenance guardrails forbid, and the NESdev "NTSC video" page it cites
carries Bisqwit's published code, so whether the pass derives from it is an
open question for review, not one to settle by editing the sentence.
Comment-only; cargo fmt and clippy for rustynes-mappers clean.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
CHANGELOG.md | 7 +++++++
crates/rustynes-mappers/src/m069_sunsoft_fme7.rs | 8 ++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d68388b..77290e1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,13 @@ cycle-accurate core later replaced.
no longer apply to it. The mapper 45 page says nothing about CHR-RAM; the
GA23C variant with a ROM/RAM switch (mapper 372) documents its RAM as
unbanked. The four mapper 45 carts with CHR-ROM are unchanged.
+- **Provenance: a softened comment restored.** In the Sunsoft 5B mixer
+ (`m069_sunsoft_fme7.rs`), v2.2.5 had reworded "Target, derived from Mesen2"
+ to "calibrated against Mesen2 ... as an oracle", which reads as a black-box
+ comparison. The derivation was always disclosed in the file's `Provenance`
+ header and in `docs/originality-and-provenance.md`, but the site said
+ otherwise; it says "derived from" again (maintainer, 2026-10-06). The ten
+ other site comments that release reworded had already been restored.
### Changed
diff --git a/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs b/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
index 0c09e5b7..b35ef679 100644
--- a/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
+++ b/crates/rustynes-mappers/src/m069_sunsoft_fme7.rs
@@ -154,8 +154,12 @@ const SUNSOFT5B_DC_BIAS: i32 = 0;
/// lets the shape stay pinned by its own unit test while the level is pinned
/// by a ROM oracle.
///
-/// **Target, calibrated against Mesen2 (the project's accuracy bar) as an
-/// oracle rather than against our own prior numbers.** Using the standard
+/// **Target, derived from Mesen2 (the project's accuracy bar) rather than from
+/// our own prior numbers** (see this file's `Provenance` header and
+/// `docs/originality-and-provenance.md` section 1). v2.2.5 reworded this to
+/// "calibrated against Mesen2 ... as an oracle", which describes a black-box
+/// comparison and so understated a derivation; v3.0.1 restored it
+/// (maintainer, 2026-10-06). Using the standard
/// blargg nonlinear-mixer approximation (nesdev "APU Mixer"), a full-volume
/// 2A03 square is `(95.88 * 5000) / (8128/15 + 100) = 746.9` units, and the 5B
/// is summed with weight `* 15` over the documented 5B log-DAC volume table
From 8a28faf320eaea8f77e04508a39f1c0886d625e6 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:41:59 -0400
Subject: [PATCH 20/44] docs(roadmap): open T-NTSC-PROVENANCE for the
Bisqwit-style pass
The Bisqwit-style NTSC pass documents itself as independent and says no
third-party emulator code is incorporated: a self-certification the
provenance guardrails forbid, about a pass named after the author whose code
the cited NESdev page publishes. Recorded as an open question for the
maintainer, with what would settle it, rather than decided by editing the
sentence.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
to-dos/ROADMAP.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md
index 76d3a0da..ae5f683e 100644
--- a/to-dos/ROADMAP.md
+++ b/to-dos/ROADMAP.md
@@ -1233,6 +1233,27 @@ A10-A12, bypassing the MMC3's CHR banks. The mapper 45 page does not mention
CHR-RAM at all. Open: fix only from a document or a hardware measurement, as
for T-GA23C-POWERON. (The document turned out to be the mapper 372 page.)
+## T-NTSC-PROVENANCE — is the Bisqwit-style NTSC pass derived from Bisqwit's published code? (raised v3.0.1, OPEN for the maintainer)
+
+`crates/rustynes-gfx-shaders/src/lib.rs` (around line 311) documents the
+Bisqwit-style composite NTSC post-pass as "an independent implementation of
+the NES composite signal model documented at the NESdev wiki ('NTSC video');
+no third-party emulator code is incorporated". Two problems, found while
+restoring the softened Sunsoft 5B comment in v3.0.1:
+
+- The sentence is a self-certification, which
+ `docs/ai-emulator-provenance-guardrails.md` forbids as a finished claim.
+- The NESdev "NTSC video" page carries Bisqwit's own published code, and the
+ pass is named after him, so whether the WGSL derives from that code is a
+ real question. If it does, it needs the same treatment as every other
+ derived region (a site note, a `// Provenance:` header, a section 1 row in
+ `docs/originality-and-provenance.md`, `NOTICE`), and the code's licence on
+ that page decides compatibility.
+
+Not changed in v3.0.1: deciding it needs a careful comparison of the pass
+against the published code (the wiki page is public documentation, so reading
+it is permitted) and a maintainer decision, not an edit to the sentence.
+
## T-SPECTATOR-HISTORY — the spectator's input history grows without bound (found v2.9.9, FIXED v3.0.0)
**Fixed for v3.0.0.** `history` is a `VecDeque` whose front is the frame
From d76295360a6e854db1fb4be1bf7f67865fcdf7cd Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:17:14 -0400
Subject: [PATCH 21/44] fix(cosim): checkpoint parsing, overflow CSV and FFI
docs from review
Seven bot findings against crates/rustynes-cosim that were still valid on
v3.0.0.
checkpoint::from_bytes checked only that the length was a multiple of 16,
so a corrupt or non-monotonic .ckpt.bin given to checkpoint_diff reached
Divergence::window_len, whose `through_cycle - after` underflowed (a panic
in debug, a wrapped length in release). from_bytes now refuses a stream
whose through_cycle does not strictly increase (Hasher cannot produce one)
and window_len uses checked_sub. Red first: the two new tests failed with
"attempt to subtract with overflow" and an Ok parse of a backwards stream.
(Bot finding RustyNES#435/T/PRRT_kwDOQrRows6bAg8w)
nes_golden_export wrote .irq.csv before matching the checkpoint
result, so an overflowed trace left a truncated CSV with no overflow
marker on disk and then panicked. The CSV is now written in the Ok arm
only; obs.bin keeps its documented keep-on-overflow behaviour. Red first:
an_overflowed_trace_writes_no_irq_csv saw the CSV written and failed.
(Bot finding RustyNES#433/T/PRRT_kwDOQrRows6a_ssH)
Observable::from_cycle_record open-coded the bus-access numbering that
access_code already defines; it now calls access_code, and a new test maps
every BusAccess variant through both. (RustyNES#433/A/5363505721/3)
localisation_is_consistent had its two `false` arm comments swapped:
(Identical, Some) is the false negative that matters, (Diverged |
Inconclusive, None) the false positive. (RustyNES#435/T/PRRT_kwDOQrRows6bAg82)
Docs: take_irq_artifacts and take_checkpoints gain `# Panics` sections
for a zero interval (RustyNES#433/A/5363505721/2); the three rn_write_*
FFI writers replace "SAFETY: as above." with the invariant they rely on
(RustyNES#433/T/PRRT_kwDOQrRows6a_ssT) and each now says it consumes the
trace (RustyNES#436/A/5364286445/1). rn_write_irq_trace_csv's advice to
write the checkpoints first was wrong, since that writer consumes the
trace too; it now says so.
Verified: cargo fmt --check, cargo clippy --all-targets -D warnings and
cargo test, all with --manifest-path crates/rustynes-cosim/Cargo.toml.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.../src/bin/nes_golden_export.rs | 45 +++++++-
crates/rustynes-cosim/src/checkpoint.rs | 106 ++++++++++++++++--
crates/rustynes-cosim/src/lib.rs | 47 +++++++-
3 files changed, 179 insertions(+), 19 deletions(-)
diff --git a/crates/rustynes-cosim/src/bin/nes_golden_export.rs b/crates/rustynes-cosim/src/bin/nes_golden_export.rs
index 3ff7c610..d76fd832 100644
--- a/crates/rustynes-cosim/src/bin/nes_golden_export.rs
+++ b/crates/rustynes-cosim/src/bin/nes_golden_export.rs
@@ -410,7 +410,6 @@ fn write_irq_artifacts(o: &mut Oracle, base: &Path, interval: u64) -> (usize, us
eprintln!(" WARNING: irq trace was armed but returned nothing");
return (0, 0);
};
- write(&suffixed(base, "irq.csv"), a.csv.as_bytes());
// Written BEFORE the checkpoints, and outside the `Err` arm below, on
// purpose. This is the full-capture stream: it is the only artifact the
@@ -427,6 +426,10 @@ fn write_irq_artifacts(o: &mut Oracle, base: &Path, interval: u64) -> (usize, us
match a.checkpoints {
Ok(ck) => {
+ // The CSV is written only here, unlike `obs.bin` above: it carries
+ // no overflow marker, so a CSV from an overflowed trace would be a
+ // truncated file indistinguishable from a complete one.
+ write(&suffixed(base, "irq.csv"), a.csv.as_bytes());
write(
&suffixed(base, "ckpt.bin"),
&rustynes_cosim::checkpoint::to_bytes(&ck),
@@ -1112,6 +1115,46 @@ mod tests {
);
}
+ /// An overflowed trace must not leave a truncated `irq.csv` behind.
+ ///
+ /// The CSV carries no overflow marker (`IrqTrace::to_csv` has no such
+ /// field), so a CSV written before the refusal below is indistinguishable
+ /// from a complete one. `obs.bin` is kept on overflow deliberately and says
+ /// why; the CSV has no such rationale, so it is written only once the
+ /// checkpoints are known to be good.
+ #[test]
+ fn an_overflowed_trace_writes_no_irq_csv() {
+ // A 16 KiB NROM whose reset vector points at `JMP $8000`.
+ let mut rom = vec![0u8; 16 + 16384 + 8192];
+ rom[..4].copy_from_slice(b"NES\x1a");
+ rom[4] = 1;
+ rom[5] = 1;
+ rom[16..19].copy_from_slice(&[0x4C, 0x00, 0x80]);
+ let vec_base = 16 + 16384 - 6;
+ rom[vec_base..vec_base + 6].copy_from_slice(&[0x00, 0x80, 0x00, 0x80, 0x00, 0x80]);
+
+ let mut o = rustynes_cosim::Oracle::new(&rom, 0).expect("rom");
+ o.enable_irq_trace(64); // far below one frame of CPU cycles
+ o.advance_frames(1);
+
+ let dir = std::env::temp_dir().join(format!(
+ "rustynes-cosim-overflow-csv-{}",
+ std::process::id()
+ ));
+ std::fs::create_dir_all(&dir).expect("temp dir");
+ let base = dir.join("overflow");
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+ super::write_irq_artifacts(&mut o, &base, 4096)
+ }));
+ let csv_exists = suffixed(&base, "irq.csv").exists();
+ let _ = std::fs::remove_dir_all(&dir);
+ assert!(result.is_err(), "an overflowed trace must still refuse");
+ assert!(
+ !csv_exists,
+ "a truncated irq.csv with no overflow marker was left on disk"
+ );
+ }
+
/// Pinned against an independently-known digest, not against our own output.
#[test]
fn sha256_matches_the_known_empty_digest() {
diff --git a/crates/rustynes-cosim/src/checkpoint.rs b/crates/rustynes-cosim/src/checkpoint.rs
index b811d41f..b29d5d99 100644
--- a/crates/rustynes-cosim/src/checkpoint.rs
+++ b/crates/rustynes-cosim/src/checkpoint.rs
@@ -174,13 +174,12 @@ impl Observable {
pc: r.pc,
bus_addr: r.bus_addr,
bus_data: r.bus_data,
- bus_access: match r.bus_access {
- BusAccess::Idle => 0,
- BusAccess::Read => 1,
- BusAccess::Write => 2,
- BusAccess::DmaRead => 3,
- BusAccess::DmaWrite => 4,
- },
+ // Through `access_code`, so the numbering lives in one place.
+ bus_access: Self::access_code(
+ matches!(r.bus_access, BusAccess::Idle),
+ matches!(r.bus_access, BusAccess::Write | BusAccess::DmaWrite),
+ matches!(r.bus_access, BusAccess::DmaRead | BusAccess::DmaWrite),
+ ),
put_cycle: r.put_cycle_post,
nmi_line: r.nmi_line,
irq_line_at_low: r.irq_pending_mapper_at_low || r.irq_pending_apu_at_low,
@@ -480,11 +479,16 @@ impl Divergence {
/// `None` rather than an assumed `through_cycle + 1`: that answer is only
/// right if the run started at cycle 0, and a checkpoint stream carries no
/// evidence that it did.
+ ///
+ /// Also `None` for an inverted window (`after_cycle > through_cycle`),
+ /// which no [`Hasher`] stream can produce; [`from_bytes`] refuses the
+ /// corrupt stream that would, and this guard keeps a hand-built
+ /// `Divergence` from underflowing.
#[must_use]
pub const fn window_len(&self) -> Option {
match self.after_cycle {
None => None,
- Some(after) => Some(self.through_cycle - after),
+ Some(after) => self.through_cycle.checked_sub(after),
}
}
}
@@ -606,9 +610,9 @@ pub const fn localisation_is_consistent(
) -> bool {
match (comparison, first_difference) {
(Comparison::Identical { .. }, None) => true,
- // A false positive: the streams agree and the gate says otherwise.
- (Comparison::Identical { .. }, Some(_)) => false,
// The failure that matters: a real difference reported as agreement.
+ (Comparison::Identical { .. }, Some(_)) => false,
+ // A false positive: the streams agree and the gate says otherwise.
(Comparison::Diverged(_) | Comparison::Inconclusive { .. }, None) => false,
(Comparison::Diverged(d), Some(k)) => d.contains(k),
// Honest refusal on a real difference.
@@ -640,6 +644,10 @@ pub fn to_bytes(checkpoints: &[Checkpoint]) -> Vec {
/// producer was interrupted, and a truncated stream that parses is a truncated
/// comparison that passes.
///
+/// Also if `through_cycle` does not strictly increase from one record to the
+/// next. [`Hasher`] cannot produce such a stream, and [`compare`] given one
+/// reports a [`Divergence`] whose window ends before it starts.
+///
/// # Panics
///
/// Never in practice: the `expect`s convert 8-byte subslices of a
@@ -650,7 +658,7 @@ pub fn from_bytes(bytes: &[u8]) -> Result, &'static str> {
if !bytes.len().is_multiple_of(16) {
return Err("checkpoint stream length is not a multiple of 16 bytes");
}
- Ok(bytes
+ let stream: Vec = bytes
.as_chunks::<16>()
.0
.iter()
@@ -658,7 +666,14 @@ pub fn from_bytes(bytes: &[u8]) -> Result, &'static str> {
through_cycle: u64::from_le_bytes(c[0..8].try_into().expect("8 bytes")),
hash: u64::from_le_bytes(c[8..16].try_into().expect("8 bytes")),
})
- .collect())
+ .collect();
+ if stream
+ .windows(2)
+ .any(|w| w[1].through_cycle <= w[0].through_cycle)
+ {
+ return Err("checkpoint stream through_cycle does not strictly increase");
+ }
+ Ok(stream)
}
#[cfg(test)]
@@ -1009,6 +1024,49 @@ mod tests {
assert!(from_bytes(&bytes).is_err());
}
+ /// A stream whose `through_cycle` does not strictly increase cannot have
+ /// come from [`Hasher`], and fed to [`compare`] it produces a
+ /// [`Divergence`] whose `after_cycle` exceeds its `through_cycle`.
+ #[test]
+ fn a_non_increasing_serialised_stream_is_rejected() {
+ let backwards = [
+ Checkpoint {
+ through_cycle: 8191,
+ hash: 1,
+ },
+ Checkpoint {
+ through_cycle: 4095,
+ hash: 2,
+ },
+ ];
+ assert!(from_bytes(&to_bytes(&backwards)).is_err());
+ let repeated = [
+ Checkpoint {
+ through_cycle: 4095,
+ hash: 1,
+ },
+ Checkpoint {
+ through_cycle: 4095,
+ hash: 2,
+ },
+ ];
+ assert!(from_bytes(&to_bytes(&repeated)).is_err());
+ }
+
+ /// `window_len` must not underflow on an inverted window, which only a
+ /// corrupt stream (or a hand-built `Divergence`) can produce.
+ #[test]
+ fn an_inverted_window_has_no_length() {
+ let d = Divergence {
+ index: 1,
+ after_cycle: Some(8191),
+ through_cycle: 4095,
+ reference_hash: 1,
+ candidate_hash: 2,
+ };
+ assert_eq!(d.window_len(), None);
+ }
+
#[test]
#[should_panic(expected = "checkpoint interval must be non-zero")]
fn a_zero_interval_is_rejected() {
@@ -1332,4 +1390,28 @@ mod tests {
assert_eq!(Observable::access_code(false, false, true), 3);
assert_eq!(Observable::access_code(false, true, true), 4);
}
+
+ /// The projection and [`Observable::access_code`] must agree for every
+ /// [`BusAccess`] variant. Until v3.0.1 `from_cycle_record` carried its own
+ /// copy of the numbering, which only `access_codes_are_stable` pinned.
+ #[test]
+ fn every_bus_access_projects_to_its_access_code() {
+ for (access, idle, write, dma, code) in [
+ (BusAccess::Idle, true, false, false, 0),
+ (BusAccess::Read, false, false, false, 1),
+ (BusAccess::Write, false, true, false, 2),
+ (BusAccess::DmaRead, false, false, true, 3),
+ (BusAccess::DmaWrite, false, true, true, 4),
+ ] {
+ let mut r = record(7);
+ r.bus_access = access;
+ let projected = Observable::from_cycle_record(&r).bus_access;
+ assert_eq!(projected, code, "{access:?}");
+ assert_eq!(
+ projected,
+ Observable::access_code(idle, write, dma),
+ "{access:?}"
+ );
+ }
+ }
}
diff --git a/crates/rustynes-cosim/src/lib.rs b/crates/rustynes-cosim/src/lib.rs
index 7ce5cb21..7df9cf84 100644
--- a/crates/rustynes-cosim/src/lib.rs
+++ b/crates/rustynes-cosim/src/lib.rs
@@ -468,6 +468,14 @@ impl Oracle {
///
/// Pinned by `tests::taking_the_csv_first_leaves_no_trace_for_checkpoints`,
/// so the hazard is a documented behaviour rather than a surprise.
+ ///
+ /// # Panics
+ ///
+ /// If `interval` is zero and the trace did not overflow, through
+ /// `checkpoint::Hasher::new`'s `interval > 0` assert. The trace has
+ /// already been taken by then, so it is lost with the panic. Callers that
+ /// take the interval from outside validate it first: the C ABI returns
+ /// `-6` and `nes_golden_export` rejects `0` on the command line.
pub fn take_irq_artifacts(&mut self, interval: u64) -> Option {
let trace = self.nes.bus_mut().take_irq_trace()?;
let dropped = trace.overflow();
@@ -514,6 +522,13 @@ impl Oracle {
/// # Errors
///
/// [`CheckpointError::TraceOverflowed`] if the trace dropped any record.
+ ///
+ /// # Panics
+ ///
+ /// If `interval` is zero and the trace did not overflow, through
+ /// `checkpoint::Hasher::new`'s `interval > 0` assert. As with
+ /// [`Self::take_irq_artifacts`], the trace is consumed before the assert
+ /// fires.
pub fn take_checkpoints(
&mut self,
interval: u64,
@@ -863,6 +878,12 @@ pub unsafe extern "C" fn rn_write_cpu_boot_trace(
/// records themselves are simply the records, and are worth having for a
/// full-capture re-run.
///
+/// **Consumes the trace**, as do [`rn_write_checkpoints`] and
+/// [`rn_write_irq_trace_csv`]: whichever of the three is called first gets the
+/// trace, and the other two return `-4` ("never armed") afterwards. A C
+/// testbench can therefore obtain only one of the three artifacts per run;
+/// the Rust-side [`Oracle::take_irq_artifacts`] yields all three from one take.
+///
/// # Safety
///
/// `handle` must come from [`rn_open`] and not have been closed. `path` must be
@@ -870,7 +891,9 @@ pub unsafe extern "C" fn rn_write_cpu_boot_trace(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rn_write_observables(handle: *mut c_void, path: *const c_char) -> c_int {
let oracle = oracle!(handle, -1);
- // SAFETY: as above.
+ // SAFETY: `cstr_to_path` returns `None` for a null `path`; a non-null
+ // `path` is NUL-terminated and valid for the duration of this call, per
+ // this function's `# Safety` contract. The borrow ends before it returns.
let Some(p) = (unsafe { cstr_to_path(path) }) else {
return -2;
};
@@ -892,6 +915,10 @@ pub unsafe extern "C" fn rn_write_observables(handle: *mut c_void, path: *const
/// divergence -- raise the capacity and re-run), `-6` interval was zero.
/// Anything at or below `-100` is `-(100 + errno)` from the write itself.
///
+/// **Consumes the trace** (unless `interval` is zero, which returns `-6`
+/// before touching it), so a later [`rn_write_observables`] or
+/// [`rn_write_irq_trace_csv`] returns `-4` ("never armed").
+///
/// # Safety
///
/// `handle` must come from [`rn_open`] and not have been closed. `path` must be
@@ -909,7 +936,9 @@ pub unsafe extern "C" fn rn_write_checkpoints(
return -6;
}
let oracle = oracle!(handle, -1);
- // SAFETY: as above.
+ // SAFETY: `cstr_to_path` returns `None` for a null `path`; a non-null
+ // `path` is NUL-terminated and valid for the duration of this call, per
+ // this function's `# Safety` contract. The borrow ends before it returns.
let Some(path) = (unsafe { cstr_to_path(path) }) else {
return -2;
};
@@ -925,9 +954,13 @@ pub unsafe extern "C" fn rn_write_checkpoints(
/// Write the IRQ/bus trace CSV to `path`. Returns 0 on success, negative on error.
///
-/// **Consumes the trace**, so a subsequent [`rn_write_checkpoints`] returns
-/// `-4` ("never armed"). A testbench wanting both must write the checkpoints
-/// first, or use the Rust-side [`Oracle::take_irq_artifacts`].
+/// **Consumes the trace**, so a subsequent [`rn_write_checkpoints`] or
+/// [`rn_write_observables`] returns `-4` ("never armed"). The checkpoint and
+/// observable writers consume it too, so calling them first does not help: a
+/// testbench gets one of the three per run, and only the Rust-side
+/// [`Oracle::take_irq_artifacts`] yields all three from one take. (Until
+/// v3.0.1 this said to write the checkpoints first, which leaves no trace for
+/// the CSV.)
///
/// # Safety
///
@@ -935,7 +968,9 @@ pub unsafe extern "C" fn rn_write_checkpoints(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rn_write_irq_trace_csv(handle: *mut c_void, path: *const c_char) -> c_int {
let o = oracle!(handle, -1);
- // SAFETY: as above.
+ // SAFETY: `cstr_to_path` returns `None` for a null `path`; a non-null
+ // `path` is NUL-terminated and valid for the duration of this call, per
+ // this function's `# Safety` contract. The borrow ends before it returns.
let Some(p) = (unsafe { cstr_to_path(path) }) else {
return -2;
};
From 8149a3542e2f65a47e4ed8a6fce27407ec3266f3 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:22:43 -0400
Subject: [PATCH 22/44] style: four small leftovers from review (cpu, config,
two probes)
Four low-severity bot findings, each still present on v3.0.0. None changes
emulation behaviour.
- rustynes-cpu: implied_dummy_read wrapped its single read1 call in a
bare block left over from the removed cfg branches. The braces are gone.
(Bot finding RustyNES#507/A/5643265643/1)
- rustynes-frontend config.rs: an empty line directly after
`mod tests {`. Removed. (RustyNES#414/A/5349340796/3)
- fds_trace: the headered-.fds check compared only the first three bytes
to "FDS" while the comment above it names the four-byte "FDS\x1a" magic.
It now uses `disk.starts_with(b"FDS\x1a")` with the same 16-byte length
guard, so code and comment agree. (RustyNES#39/B/4493453078/...fds_trace.rs:62:156)
- zapper_light_probe: `let bright = bright_px >= 2;` was never read and
was discarded with `let _ = bright;`; the output prints bright_px
directly. Both lines removed. (RustyNES#383/A/5309752445/8)
Verified: cargo fmt --all --check; cargo clippy -p rustynes-cpu
-p rustynes-test-harness --all-targets -D warnings; cargo test -p
rustynes-cpu; RUSTUP_TOOLCHAIN=1.96.0 cargo check --release -p
rustynes-libretro.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
crates/rustynes-cpu/src/cpu.rs | 4 +---
crates/rustynes-frontend/src/config.rs | 1 -
crates/rustynes-test-harness/src/bin/fds_trace.rs | 2 +-
crates/rustynes-test-harness/src/bin/zapper_light_probe.rs | 2 --
4 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/crates/rustynes-cpu/src/cpu.rs b/crates/rustynes-cpu/src/cpu.rs
index 76dfed21..ba443ce6 100644
--- a/crates/rustynes-cpu/src/cpu.rs
+++ b/crates/rustynes-cpu/src/cpu.rs
@@ -855,9 +855,7 @@ impl Cpu {
#[inline(always)]
#[allow(clippy::inline_always)]
fn implied_dummy_read(&mut self, bus: &mut B) {
- {
- let _ = self.read1(bus, self.pc);
- }
+ let _ = self.read1(bus, self.pc);
}
/// Read a byte at `addr` *and* consume one CPU cycle (with bus tick
diff --git a/crates/rustynes-frontend/src/config.rs b/crates/rustynes-frontend/src/config.rs
index 8c30e4c8..a71ed259 100644
--- a/crates/rustynes-frontend/src/config.rs
+++ b/crates/rustynes-frontend/src/config.rs
@@ -2291,7 +2291,6 @@ fn canonicalize_pad(pad: &PadBindings) -> PadBindings {
#[cfg(test)]
mod tests {
-
use super::*;
use tempfile::TempDir;
diff --git a/crates/rustynes-test-harness/src/bin/fds_trace.rs b/crates/rustynes-test-harness/src/bin/fds_trace.rs
index 58317fa7..de7a8de4 100644
--- a/crates/rustynes-test-harness/src/bin/fds_trace.rs
+++ b/crates/rustynes-test-harness/src/bin/fds_trace.rs
@@ -55,7 +55,7 @@ fn main() {
// from the raw .fds (header-less form; each side is FDS_SIDE_LEN bytes).
// A headered `.fds` starts with "FDS\x1a" + a 16-byte header; require the
// full header before slicing it off so a short/corrupt file can't panic.
- let body = if disk.len() >= 16 && &disk[..3] == b"FDS" {
+ let body = if disk.len() >= 16 && disk.starts_with(b"FDS\x1a") {
&disk[16..]
} else {
&disk[..]
diff --git a/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs b/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
index 4a57bf89..56ad9873 100644
--- a/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
+++ b/crates/rustynes-test-harness/src/bin/zapper_light_probe.rs
@@ -232,7 +232,6 @@ fn main() {
let _ = nes.drain_audio_into(&mut discard);
let (centre, bright_px) = aperture_stats(nes.framebuffer(), aim_x_runtime, aim_y_runtime);
- let bright = bright_px >= 2;
let reads: Vec = nes
.accesses()
.iter()
@@ -250,7 +249,6 @@ fn main() {
.map(|p| u32::from(p[1]))
.sum::()
/ u32::try_from(fb.len() / 4).unwrap_or(1);
- let _ = bright;
if let Ok(dir) = std::env::var("ZAPPER_PROBE_PNG_DIR") {
write_png(
&Path::new(&dir).join(format!("f{f:03}.png")),
From e26d3438d5f770c27901fb7516726ed1396daca2 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:26:35 -0400
Subject: [PATCH 23/44] fix(gamedb): keep a mapper-0 override saved to the user
overlay
parse_row read both the vendored table and the user overlay, and its
`filter(|&m| m != 0)` -- correct for the vendored table, whose unfilled
rows carry `0` and which has no separate empty marker -- also discarded a
mapper-0 override the user saved from the ROM Database panel. The
override applied in-session through upsert_user_entry, serialize_row
wrote it as `0`, and the next start read it back as "no override".
The overlay has an empty marker: serialize_row leaves the Mapper column
blank for None. So an overlay `0` is a deliberate NROM correction. The
overlay is now read by parse_overlay_row, which keeps it; the vendored
table keeps its rule through parse_row. Both share parse_row_from.
Red first: an_overlay_mapper_zero_override_survives_reload failed with
`mapper: None` where `Some(0)` was saved. It also pins that the vendored
reader still drops the `0` and that an empty overlay column stays None.
Bot finding RustyNES#373/A/5303905922/3.
Verified: cargo fmt --all --check; cargo test -p rustynes-gamedb (all
pass); cargo clippy -p rustynes-gamedb --all-targets -D warnings;
RUSTDOCFLAGS=-D warnings cargo doc -p rustynes-gamedb; RUSTUP_TOOLCHAIN=
1.96.0 cargo check --release -p rustynes-libretro.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
crates/rustynes-gamedb/src/lib.rs | 67 ++++++++++++++++++++++++++++++-
1 file changed, 65 insertions(+), 2 deletions(-)
diff --git a/crates/rustynes-gamedb/src/lib.rs b/crates/rustynes-gamedb/src/lib.rs
index c88ac5ef..5aa73e16 100644
--- a/crates/rustynes-gamedb/src/lib.rs
+++ b/crates/rustynes-gamedb/src/lib.rs
@@ -148,7 +148,29 @@ fn parse_mirroring(token: &str) -> Option {
/// PrgRamBanks, Battery, Mirroring, Title` row into a [`GameDbEntry`]. Returns
/// `None` for comment / blank / malformed lines. The title is the final field
/// and may contain commas (it is split off with `splitn`).
+///
+/// This is the **vendored-table** reader: a Mapper column of `0` is read as
+/// "unspecified" (see the comment in [`parse_row_from`]). The user overlay is
+/// read by [`parse_overlay_row`], which keeps a `0`.
fn parse_row(line: &str) -> Option {
+ parse_row_from(line, true)
+}
+
+/// Parse one row of the **user overlay** (`game_db_user.txt`).
+///
+/// Same format as the vendored table, with one difference: a Mapper column of
+/// `0` is a real override. The overlay writer ([`serialize_row`]) leaves the
+/// column EMPTY for "no override", so a `0` there can only be a deliberate NROM
+/// correction saved from the ROM Database panel. Until v3.0.1 the overlay was
+/// read through the vendored reader, which dropped that `0`: the override
+/// applied in-session and vanished on the next start.
+fn parse_overlay_row(line: &str) -> Option {
+ parse_row_from(line, false)
+}
+
+/// The shared row parser. `zero_mapper_is_unset` is `true` for the vendored
+/// table only.
+fn parse_row_from(line: &str, zero_mapper_is_unset: bool) -> Option {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
return None;
@@ -180,7 +202,14 @@ fn parse_row(line: &str) -> Option {
// should not have; the first was the mirroring column freezing Wizards &
// Warriors (ADR 0031), fixed the same way -- by refusing to apply an override
// that cannot be distinguished from "no data".
- let mapper = fields[2].parse::().ok().filter(|&m| m != 0);
+ //
+ // The rule is the VENDORED table's only. The user overlay has an empty
+ // marker (its writer leaves the column blank), so a `0` there is a
+ // deliberate correction and is kept -- see `parse_overlay_row`.
+ let mapper = fields[2]
+ .parse::()
+ .ok()
+ .filter(|&m| !(zero_mapper_is_unset && m == 0));
let submapper = fields[3].parse::().ok();
let mirroring = parse_mirroring(fields[8]);
let title = fields
@@ -349,7 +378,7 @@ fn load_overlay() -> Vec {
let Ok(text) = std::fs::read_to_string(&path) else {
return Vec::new();
};
- let mut rows: Vec = text.lines().filter_map(parse_row).collect();
+ let mut rows: Vec = text.lines().filter_map(parse_overlay_row).collect();
rows.sort_unstable_by_key(|e| e.crc);
rows.dedup_by_key(|e| e.crc);
rows
@@ -1387,6 +1416,40 @@ mod tests {
assert_eq!(parse_row(&serialize_row(&sparse)), Some(sparse));
}
+ /// A mapper-0 override saved to the USER OVERLAY must survive a restart.
+ ///
+ /// The `0`-means-unspecified rule belongs to the vendored table, whose
+ /// unfilled rows carry `0`. The overlay writer leaves the column empty for
+ /// "no override", so an overlay `0` is a deliberate NROM correction. Until
+ /// v3.0.1 the overlay reader shared the vendored filter, and the override
+ /// applied in-session (`upsert_user_entry`) then vanished on reload.
+ #[test]
+ fn an_overlay_mapper_zero_override_survives_reload() {
+ let entry = GameDbEntry {
+ crc: 0x1234_5678,
+ region: None,
+ mapper: Some(0),
+ submapper: None,
+ mirroring: None,
+ title: "Homebrew NROM".into(),
+ };
+ let row = serialize_row(&entry);
+ assert_eq!(parse_overlay_row(&row), Some(entry));
+ // The vendored reader keeps its rule: the same text from the vendored
+ // table is "unspecified".
+ assert_eq!(parse_row(&row).expect("row parses").mapper, None);
+ // An empty overlay column is still "no override".
+ let sparse = GameDbEntry {
+ crc: 0x1234_5678,
+ region: None,
+ mapper: None,
+ submapper: None,
+ mirroring: None,
+ title: "Homebrew NROM".into(),
+ };
+ assert_eq!(parse_overlay_row(&serialize_row(&sparse)), Some(sparse));
+ }
+
#[test]
fn header_overrides_noop_for_non_ines() {
let mut not_a_rom = b"not a rom".to_vec();
From 8ab0d4b39a4195891008a48a194e68aa0bc8b3d9 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:29:37 -0400
Subject: [PATCH 24/44] test(ppu): round-trip the v6 snapshot tail at
non-default values
No unit test set the v6-tail fields (spr_halted[0..8],
prev_rendering_enabled, rendering_enabled_delayed, oam_corruption_pending,
oam_corruption_index, oam_corruption_disabled,
oam_corruption_disabled_instant) away from their power-on defaults before
a snapshot/restore. snapshot_round_trip does not touch them, and
snapshot_schema_audit proves only that the writer mentions each field, so
a reader that disagreed with the writer about order would still have
passed: every field reads back the value it already had.
The new test flips one field at a time and compares the whole tail after
restore, so a swapped pair of adjacent bools reads back as two wrong
fields. This is a coverage gap, not a behaviour defect, so the test is
green on the current code; it was checked by mutation instead: swapping
the reader lines for oam_corruption_disabled and
oam_corruption_disabled_instant failed this test and no other snapshot
test (26 passed, 1 failed). The mutation was reverted.
Bot finding RustyNES#250/T/PRRT_kwDOQrRows6PurIn.
Verified: cargo fmt --all --check; cargo clippy -p rustynes-ppu
--all-targets -D warnings; cargo test -p rustynes-ppu; RUSTUP_TOOLCHAIN=
1.96.0 cargo check --release -p rustynes-libretro.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
crates/rustynes-ppu/src/snapshot.rs | 69 +++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/crates/rustynes-ppu/src/snapshot.rs b/crates/rustynes-ppu/src/snapshot.rs
index d6656f72..3a592309 100644
--- a/crates/rustynes-ppu/src/snapshot.rs
+++ b/crates/rustynes-ppu/src/snapshot.rs
@@ -1385,6 +1385,75 @@ mod tests {
assert!(t.oam2_fetch_frozen, "freeze flag is NOT the overflow flag");
}
+ #[test]
+ fn snapshot_round_trips_the_v6_tail_at_non_default_values() {
+ // The v6 tail -- per-sprite `spr_halted` plus the six rendering /
+ // OAM-corruption fields after it -- was round-tripped only at its
+ // power-on defaults, so a reader that disagreed with the writer about
+ // ORDER still passed: every field read back the value it already had.
+ // `snapshot_schema_audit` proves only that each field is written.
+ //
+ // So flip ONE field at a time away from its default and compare the
+ // whole tail after restore. A swapped pair of adjacent bools then
+ // reads back as two wrong fields instead of two right ones.
+ type Tail = ([bool; 8], bool, bool, bool, u8, bool, bool);
+ fn tail(p: &Ppu) -> Tail {
+ (
+ p.spr_halted,
+ p.prev_rendering_enabled,
+ p.rendering_enabled_delayed,
+ p.oam_corruption_pending,
+ p.oam_corruption_index,
+ p.oam_corruption_disabled,
+ p.oam_corruption_disabled_instant,
+ )
+ }
+ type Setter = fn(&mut Ppu);
+ let cases: [(&str, Setter); 14] = [
+ ("spr_halted[0]", |p| p.spr_halted[0] = false),
+ ("spr_halted[1]", |p| p.spr_halted[1] = false),
+ ("spr_halted[2]", |p| p.spr_halted[2] = false),
+ ("spr_halted[3]", |p| p.spr_halted[3] = false),
+ ("spr_halted[4]", |p| p.spr_halted[4] = false),
+ ("spr_halted[5]", |p| p.spr_halted[5] = false),
+ ("spr_halted[6]", |p| p.spr_halted[6] = false),
+ ("spr_halted[7]", |p| p.spr_halted[7] = false),
+ ("prev_rendering_enabled", |p| {
+ p.prev_rendering_enabled = true;
+ }),
+ ("rendering_enabled_delayed", |p| {
+ p.rendering_enabled_delayed = true;
+ }),
+ ("oam_corruption_pending", |p| {
+ p.oam_corruption_pending = true;
+ }),
+ ("oam_corruption_index", |p| p.oam_corruption_index = 0x1F),
+ ("oam_corruption_disabled", |p| {
+ p.oam_corruption_disabled = true;
+ }),
+ ("oam_corruption_disabled_instant", |p| {
+ p.oam_corruption_disabled_instant = true;
+ }),
+ ];
+ let default_tail = tail(&Ppu::new(PpuRegion::Ntsc));
+ for (name, set) in cases {
+ let mut p = Ppu::new(PpuRegion::Ntsc);
+ set(&mut p);
+ let expected = tail(&p);
+ assert_ne!(
+ expected, default_tail,
+ "{name}: the case must change something"
+ );
+ let mut q = Ppu::new(PpuRegion::Ntsc);
+ q.restore(&p.snapshot()).unwrap();
+ assert_eq!(
+ tail(&q),
+ expected,
+ "{name} did not survive snapshot/restore"
+ );
+ }
+ }
+
#[test]
fn snapshot_round_trips_sprite_evaluation_state() {
// v8: a snapshot taken with a sprite-evaluation pass in flight (dots
From 4301fb9e4b3021c408506b52b0120b130fc082b4 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:53:01 -0400
Subject: [PATCH 25/44] fix(frontend): blank the Bisqwit index frame with no
ROM; review leftovers
Four bot findings against rustynes-frontend, all still valid on v3.0.0.
Bisqwit index staging (medium). The two no-ROM present branches refilled
only present_staging (RGBA); present_index_staging kept the last game's
index frame, and close_rom cleared it to empty. Gfx skips the index
upload on a length mismatch and keeps the texture's previous contents,
so with the composite-rt (Bisqwit) filter on, closing the ROM left the
last game frame on screen instead of black. A new associated fn,
App::blank_present_staging, fills the RGBA half with zeros and, when an
index consumer is active, a full NES_W*NES_H index frame of colour $0F;
both no-ROM branches and close_rom now call it. Red first:
a_blank_present_stages_a_black_index_frame failed against a helper that
did only what the no-ROM branches did. (Bot findings RustyNES#33/B/
4492472013 at app.rs:4547:154 and app.rs:4620:155 -- one defect.)
Startup-path test (RustyNES#373/A/5303905922/2).
the_startup_path_applies_the_same_header_overrides_as_the_menu_path built
bytes that never matched a DB row, so it always took its early return
and never ran the comparison. The image now forges its last four hashed
bytes so the header-excluded CRC32 is Seicross's (0x0F05FF0A, mapper 185
submapper 4), the early return is gone, and it asserts the helper
changes the header and matches apply_header_overrides. Mutation check:
making apply_load_time_header_overrides a no-op fails it ("the startup
helper must rewrite the header"); reverted.
Run-ahead throttle (RustyNES#371/A/5290707031/1). The per-frame cost
model was written out separately in the engage cascade and the release
arm; both now call one private per_frame_cost, pinned by a unit test.
tick_iv (RustyNES#366/A/5287325218/1). tick_iv_ns is differenced on the
receiver, so a tick dropped on the depth-1 channel is never seen and the
next interval spans two sends, while its docs said "between successive
SENDS". The field docs now say "between delivered ticks" and that it
equals the send cadence only while tick_dropped is 0; docs/performance.md
gets a dated correction under the F15 table (drops were measured at 0 and
0-1 per capture, so the F15 conclusion stands).
Verified: cargo fmt --all --check; cargo clippy -p rustynes-frontend
--all-targets -D warnings with default, scripting, scripting,hd-pack,
retroachievements and full; cargo test -p rustynes-frontend --lib for the
three tests; pre-commit run markdownlint --files docs/performance.md.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
crates/rustynes-frontend/src/app.rs | 159 +++++++++++++++++----
crates/rustynes-frontend/src/emu.rs | 25 +++-
crates/rustynes-frontend/src/emu_thread.rs | 15 +-
docs/performance.md | 7 +
4 files changed, 170 insertions(+), 36 deletions(-)
diff --git a/crates/rustynes-frontend/src/app.rs b/crates/rustynes-frontend/src/app.rs
index 1af432a3..4ca001d1 100644
--- a/crates/rustynes-frontend/src/app.rs
+++ b/crates/rustynes-frontend/src/app.rs
@@ -1817,11 +1817,21 @@ impl App {
// `NES_W*NES_H*4` frame so the next upload uploads black, not nothing.
#[cfg(all(not(target_arch = "wasm32"), feature = "emu-thread"))]
self.present_buffer.reset();
- self.present_staging.clear();
- self.present_staging.resize((NES_W * NES_H * 4) as usize, 0);
+ // v3.0.1 — the palette-index half is blanked the same way when an index
+ // consumer is active, not merely cleared: an empty slice makes `Gfx`
+ // skip the upload and keep the last game frame in the index texture.
+ let want_index = self
+ .gfx
+ .as_ref()
+ .is_some_and(|g| g.ntsc_bisqwit_active() || g.shader_stack_needs_index());
+ self.present_index_staging.clear();
+ Self::blank_present_staging(
+ &mut self.present_staging,
+ &mut self.present_index_staging,
+ want_index,
+ );
// Drop the sibling presentation buffers too, so a stale frame can't be
// re-presented after the ROM is closed.
- self.present_index_staging.clear();
#[cfg(feature = "hd-pack")]
{
self.present_hd_tiles.clear();
@@ -8857,6 +8867,26 @@ impl App {
}
}
+ /// Fill the present staging with a BLACK NES frame: a zeroed
+ /// `NES_W*NES_H*4` RGBA image and, when `want_index` (the true composite
+ /// `NES_NTSC` filter, or a shader pass that samples the index texture), a
+ /// full `NES_W*NES_H` palette-index frame of colour `$0F` (black, no
+ /// emphasis).
+ ///
+ /// The index half matters because `Gfx` skips the index upload on a length
+ /// mismatch and keeps the texture's previous contents. Until v3.0.1 the
+ /// no-ROM paths refilled only the RGBA half and `close_rom` cleared the
+ /// index half to empty, so with the Bisqwit filter on, the last game frame
+ /// stayed on screen after the ROM was closed instead of black.
+ fn blank_present_staging(rgba: &mut Vec, index: &mut Vec, want_index: bool) {
+ rgba.clear();
+ rgba.resize((NES_W * NES_H * 4) as usize, 0);
+ if want_index {
+ index.clear();
+ index.resize((NES_W * NES_H) as usize, 0x0F);
+ }
+ }
+
/// v1.1.0 beta.1 (T-110-A1) — select the gfx NTSC post-pass to match the
/// `[graphics] ntsc_filter` mode, keeping the two NTSC filters mutually
/// exclusive: `"composite-rt"` = the true composite Bisqwit filter,
@@ -10926,8 +10956,11 @@ impl ApplicationHandler for App {
}
}
} else {
- self.present_staging.clear();
- self.present_staging.resize((NES_W * NES_H * 4) as usize, 0);
+ Self::blank_present_staging(
+ &mut self.present_staging,
+ &mut self.present_index_staging,
+ want_index,
+ );
}
// `guard` drops here, releasing the emu lock BEFORE the
// CPU-heavy composite below.
@@ -11328,9 +11361,13 @@ impl ApplicationHandler for App {
}
} else {
// No ROM: present a black NES image (the shell still
- // draws on top).
- self.present_staging.clear();
- self.present_staging.resize((NES_W * NES_H * 4) as usize, 0);
+ // draws on top) -- the index half too, when the
+ // Bisqwit filter or an index-sampling pass is on.
+ Self::blank_present_staging(
+ &mut self.present_staging,
+ &mut self.present_index_staging,
+ want_index,
+ );
}
}
// v1.2.0 C3 — lock dropped: now run the CPU-heavy HD composite
@@ -12086,6 +12123,37 @@ mod tests {
use super::{apply_load_time_header_overrides, apply_per_game_overlay};
use rustynes_core::Nes;
+ /// The no-ROM present must stage a BLACK index frame for the Bisqwit
+ /// filter, not leave the last game's index frame (or an empty slice, which
+ /// `Gfx` skips, keeping the old texture) in place.
+ #[test]
+ fn a_blank_present_stages_a_black_index_frame() {
+ use crate::gfx::{NES_H, NES_W};
+ let n = (NES_W * NES_H) as usize;
+ // Stale game frame in both halves, as after a ROM has run.
+ let mut rgba = vec![0xAB; n * 4];
+ let mut index = vec![0x21_u16; n];
+ super::App::blank_present_staging(&mut rgba, &mut index, true);
+ assert_eq!(rgba.len(), n * 4);
+ assert!(rgba.iter().all(|&b| b == 0), "RGBA half is black");
+ assert_eq!(index.len(), n, "a full frame, or Gfx skips the upload");
+ assert!(
+ index.iter().all(|&i| i == 0x0F),
+ "index half is black ($0F)"
+ );
+
+ // `close_rom` cleared the index half to EMPTY; that must not survive
+ // a blank present either.
+ let mut empty: Vec = Vec::new();
+ super::App::blank_present_staging(&mut rgba, &mut empty, true);
+ assert_eq!(empty.len(), n);
+
+ // Without an index consumer the index half is left alone (zero cost).
+ let mut untouched: Vec = Vec::new();
+ super::App::blank_present_staging(&mut rgba, &mut untouched, false);
+ assert_eq!(untouched, [] as [u16; 0]);
+ }
+
/// The CLI / initial-ROM path must apply the same load-time header
/// corrections as the File-menu path.
///
@@ -12094,8 +12162,48 @@ mod tests {
/// image, so `rustynes ` skipped every mapper / submapper / region fix
/// that opening the same ROM from the menu applied. Seicross is the case
/// that matters -- it needs submapper 4 to clear its protection loop.
+ ///
+ /// Until v3.0.1 the synthetic image never matched a DB row, so the test
+ /// always took an early return that checked only "the helper is a no-op on
+ /// an unknown CRC" and never reached the comparison below. The image now
+ /// carries the real Seicross key: its last four hashed bytes are forged so
+ /// the header-excluded CRC32 equals the vendored row's.
#[test]
fn the_startup_path_applies_the_same_header_overrides_as_the_menu_path() {
+ /// Seicross (Japan) in the vendored table: mapper 185, submapper 4.
+ const SEICROSS_CRC: u32 = 0x0F05_FF0A;
+ const POLY: u32 = 0xEDB8_8320;
+ /// The reflected CRC-32 register after `data`, without the final XOR.
+ fn crc_reg(mut reg: u32, data: &[u8]) -> u32 {
+ for &b in data {
+ reg ^= u32::from(b);
+ for _ in 0..8 {
+ reg = if reg & 1 != 0 {
+ (reg >> 1) ^ POLY
+ } else {
+ reg >> 1
+ };
+ }
+ }
+ reg
+ }
+ /// Four bytes that, appended to `prefix`, give CRC-32 `target`. CRC is
+ /// linear, so 4 bytes `w` take register `s` to `step32(s ^ w)`;
+ /// running the 32 bit-steps backwards from the wanted register gives
+ /// `s ^ w`. A reverse step is unambiguous because `POLY` has bit 31
+ /// set: a set top bit means the low bit shifted out was 1.
+ fn forge_tail(prefix: &[u8], target: u32) -> [u8; 4] {
+ let mut reg = !target;
+ for _ in 0..32 {
+ reg = if reg & 0x8000_0000 != 0 {
+ ((reg ^ POLY) << 1) | 1
+ } else {
+ reg << 1
+ };
+ }
+ (reg ^ crc_reg(0xFFFF_FFFF, prefix)).to_le_bytes()
+ }
+
// Seicross: iNES 1.0, mapper 185, no submapper field of its own.
// 32 KiB PRG + 8 KiB CHR so the header-excluded CRC is well defined.
let mut rom = vec![0u8; 16 + 0x8000 + 0x2000];
@@ -12104,34 +12212,25 @@ mod tests {
rom[5] = 1; // 8 KiB CHR
rom[6] = 0x90; // mapper low nibble 9
rom[7] = 0xB0; // mapper high nibble B -> 185
+ let end = rom.len();
+ let tail = forge_tail(&rom[16..end - 4], SEICROSS_CRC);
+ rom[end - 4..].copy_from_slice(&tail);
let crc = crate::game_db::rom_crc32(&rom).expect("iNES header parses");
- let Some(entry) = crate::game_db::load_time_entry(crc, &rom) else {
- // Synthetic bytes will not match a real DB row; the point of the
- // test is the CALL, so drive the helper with a known entry instead.
- let mut a = rom.clone();
- let mut b = rom.clone();
- let e = crate::game_db::GameDbEntry {
- crc,
- region: None,
- mapper: Some(4),
- submapper: Some(4),
- mirroring: None,
- title: String::new(),
- };
- crate::game_db::apply_header_overrides(&mut a, &e);
- apply_load_time_header_overrides(&mut b, None);
- assert_ne!(a, rom, "premise: the override does change the header");
- assert_eq!(
- b, rom,
- "no DB row for these synthetic bytes, so the helper is a no-op"
- );
- return;
- };
+ assert_eq!(
+ crc, SEICROSS_CRC,
+ "premise: the forged image carries the key"
+ );
+ let entry = crate::game_db::load_time_entry(crc, &rom).expect("Seicross row is listed");
+
let mut via_helper = rom.clone();
apply_load_time_header_overrides(&mut via_helper, None);
let mut via_direct = rom.clone();
crate::game_db::apply_header_overrides(&mut via_direct, &entry);
+ assert_ne!(
+ via_helper, rom,
+ "the startup helper must rewrite the header (submapper 4)"
+ );
assert_eq!(
via_helper, via_direct,
"the startup helper must produce the same header as the DB rewrite"
diff --git a/crates/rustynes-frontend/src/emu.rs b/crates/rustynes-frontend/src/emu.rs
index 6975c43b..c647993c 100644
--- a/crates/rustynes-frontend/src/emu.rs
+++ b/crates/rustynes-frontend/src/emu.rs
@@ -190,6 +190,18 @@ const THROTTLE_MIN_SAMPLES: usize = 120;
#[cfg(not(target_arch = "wasm32"))]
const RUNAHEAD_THROTTLE_RELEASE: f32 = 0.70;
+/// The throttle's per-frame-linear cost model: the median produce cost at
+/// `running` run-ahead frames, divided over the `running + 1` frames it pays
+/// for (F18: +4.49 and +4.21 ms per depth, equal within 6%).
+///
+/// One function because the engage cascade and the release arm both need it,
+/// and until v3.0.1 each wrote the formula out separately, free to drift.
+/// `f64` so both conversions are total and lossless (see the engage arm).
+#[cfg(not(target_arch = "wasm32"))]
+fn per_frame_cost(produce_p50_ms: f32, running: u32) -> f64 {
+ f64::from(produce_p50_ms) / (f64::from(running) + 1.0)
+}
+
/// The two must not be swapped back. A minimum-to-report at or above the ring's
/// capacity would mean the throttle either never reports or reports only on a
/// full ring, and the F27 defect was precisely a confusion between these two
@@ -1414,7 +1426,7 @@ impl EmuCore {
// configuration that is over budget" is worse than no fallback.
// Raised by both reviewers on PR #371.
let band = f64::from(engage_band);
- let per_frame = f64::from(produce_p50_ms) / (f64::from(running) + 1.0);
+ let per_frame = per_frame_cost(produce_p50_ms, running);
while self.runahead_throttle_steps < bounded {
let from = bounded.saturating_sub(self.runahead_throttle_steps);
let predicted = per_frame * (f64::from(from) + 1.0);
@@ -1474,7 +1486,7 @@ impl EmuCore {
// would predict the cost of a SINGLE frame and release into a
// configuration that cannot afford it. Fixed in both places at once
// rather than only in the code review happened to be looking at.
- let per_frame = f64::from(produce_p50_ms) / (f64::from(running) + 1.0);
+ let per_frame = per_frame_cost(produce_p50_ms, running);
let predicted_one_more = per_frame * (f64::from(running) + 2.0);
let release_band = f64::from(target) * f64::from(RUNAHEAD_THROTTLE_RELEASE);
let release = predicted_one_more < release_band;
@@ -2410,6 +2422,15 @@ pub(crate) fn drive_ra(
mod tests {
use super::*;
+ /// The shared cost model divides the measured cost over `running + 1`
+ /// frames: depth 0 is one frame, depth 2 is three.
+ #[test]
+ fn per_frame_cost_divides_over_running_plus_one() {
+ assert!((per_frame_cost(9.0, 0) - 9.0).abs() < 1e-12);
+ assert!((per_frame_cost(9.0, 2) - 3.0).abs() < 1e-12);
+ assert!((per_frame_cost(12.0, 3) - 3.0).abs() < 1e-12);
+ }
+
// ---- v2.3.3 F21: the run-ahead budget throttle state machine ----------
//
// It had no tests at all, through an all-or-nothing implementation and a
diff --git a/crates/rustynes-frontend/src/emu_thread.rs b/crates/rustynes-frontend/src/emu_thread.rs
index 62f75301..d69d1837 100644
--- a/crates/rustynes-frontend/src/emu_thread.rs
+++ b/crates/rustynes-frontend/src/emu_thread.rs
@@ -316,16 +316,23 @@ pub struct EmuControl {
///
/// * `tick_lat_ns` — send to receipt, i.e. the winit->emu hop: scheduler
/// wake-up latency, the last completely unmeasured step in the chain.
- /// * `tick_iv_ns` — the interval between successive SENDS, i.e. how
- /// regularly the winit thread asked for a frame, independent of how long
- /// the ask took to arrive.
+ /// * `tick_iv_ns` — the interval between the send stamps of successive
+ /// DELIVERED ticks, i.e. how regularly the winit thread asked for a frame,
+ /// independent of how long the ask took to arrive. It is differenced on
+ /// the receiver, so a tick dropped on the full depth-1 channel
+ /// ([`Self::tick_dropped`]) is never seen and the next interval spans two
+ /// sends. It equals the send cadence only while `tick_dropped` is 0;
+ /// drops were measured at 0 (`run_ahead` 2) and 0-1 (`run_ahead` 0) per
+ /// 45 s capture (`docs/performance.md`, "Suspect A"). Until v3.0.1 this
+ /// said "between successive SENDS", which holds only while nothing drops.
///
/// Aimed by measurement, not by guess: produce-interval standard deviation
/// tracks missed presents at r = 0.937 across eighteen captures, and these
/// two plus the existing `produce_cost` are the three terms that make up
/// that interval.
tick_lat_ns: AtomicU64,
- /// Interval between successive tick sends. See [`Self::tick_lat_ns`].
+ /// Interval between the send stamps of successive delivered ticks. See
+ /// [`Self::tick_lat_ns`].
tick_iv_ns: AtomicU64,
/// Previous send stamp, for differencing into [`Self::tick_iv_ns`]. Zero
/// means "no previous tick", which suppresses the first interval rather
diff --git a/docs/performance.md b/docs/performance.md
index 1008e335..95913f23 100644
--- a/docs/performance.md
+++ b/docs/performance.md
@@ -3440,6 +3440,13 @@ screen and confirmed valid by F16's gate, 18 post-warmup rows):
| `tick_iv` — between tick *sends* | 16.289 ms | **24.578 ms** | 28.637 ms |
| `produced` — resulting interval | 16.269 ms | **24.635 ms** | — |
+> Correction (v3.0.1): `tick_iv` is differenced on the receiver, between the
+> send stamps of successive *delivered* ticks. A tick dropped on the full depth-1
+> channel is never seen, so the next interval spans two sends. It equals the
+> interval between sends only while `tick_dropped` is 0. Drops were measured at
+> 0 (`run_ahead` 2) and 0-1 (`run_ahead` 0) per 45 s capture under "Suspect A",
+> so the conclusion below is not affected in practice; the row label is.
+
#### The result
**The cross-thread hop is 33-50 microseconds.** It is not a contributor, and the
From ad0dec72df630765463e6a6f9bec075f4362f995 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:54:07 -0400
Subject: [PATCH 26/44] test(harness): close four gaps in the release and
feature-flag audits
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Five bot findings against three audit tests in rustynes-test-harness,
each still valid on v3.0.0. Each audit could pass over prose or rows it
exists to check.
release_state_prose_audit: versions_near collected digits and dots
greedily, so a version ending a sentence ("v2.4.2.") read as "2.4.2.",
which triple() rejects, and the version was silently skipped -- the same
scanner feeds tag_claims. It now trims trailing dots before triple().
Red first: a_version_ending_a_sentence_is_still_found returned [] for
"Next up — v2.4.2.". The audit still passes on the current tree with the
fix, so no prose was hiding behind it. (RustyNES#438/A/5381656496/1)
release_anchor_audit: the theme check refused only `**`, `__` and a
backtick, so `~~strike~~` or a `[link](url)` would reach the plain-text
GitHub release title unrendered. The list moves into
title_markdown_marker() and gains "~~" and "]("; single `*`/`_` stay
allowed. Red first: title_markers_cover_strikethrough_and_links got None
for "~~". (RustyNES#459/A/5397645197/1)
feature_flag_audit, three findings:
- the table ended at the next blank line, so prose directly under it was
read as table text (to EOF with no blank line). It now ends at the
first line that does not start with `|`. (RustyNES#507/A/5643265643/2)
- a table line that did not parse as "| `flag` |" was skipped with
`continue`, so a malformed row vanished while the row-count floor still
passed. It now panics naming the line. (RustyNES#507/T/PRRT_kwDOQrRows6hs6Uy)
- both cross-checks pass over rows marked *(removed)*, so a manifest
re-declaring cpu-implied-dummy-reads passed every test.
no_removed_flag_is_still_declared now asserts that no removed row names
a declared flag. (RustyNES#507/T/PRRT_kwDOQrRows6hs6U2)
The parsing moved into parse_feature_table(text) so it can be tested on
synthetic tables. Red first: the v3.0.0 parse loop, compiled standalone
with the two new tests, read a stray "| `gamma`" line after the table as
a row and did not panic on "| delta |". removed_but_declared is pinned
by a synthetic test; the real-tree check passes.
Verified: cargo fmt --all --check; cargo clippy -p rustynes-test-harness
--all-targets -D warnings; cargo test -p rustynes-test-harness for the
three test files (9, 16 and 7 passed).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.../tests/feature_flag_audit.rs | 119 ++++++++++++++++--
.../tests/release_anchor_audit.rs | 26 +++-
.../tests/release_state_prose_audit.rs | 22 +++-
3 files changed, 149 insertions(+), 18 deletions(-)
diff --git a/crates/rustynes-test-harness/tests/feature_flag_audit.rs b/crates/rustynes-test-harness/tests/feature_flag_audit.rs
index 8ac78940..d500a67f 100644
--- a/crates/rustynes-test-harness/tests/feature_flag_audit.rs
+++ b/crates/rustynes-test-harness/tests/feature_flag_audit.rs
@@ -149,36 +149,129 @@ fn declared_features(root: &Path) -> BTreeMap> {
/// table. Fails closed if the table cannot be located: a heading rename must
/// break this test rather than silently make it check nothing.
fn tabled_flags(root: &Path) -> Vec<(String, bool)> {
- const HEADER: &str = "| Flag | Crate(s) | Default | Purpose |";
let path = root.join("docs/STATUS.md");
let text =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
+ let rows = parse_feature_table(&text);
+ assert!(
+ rows.len() > 5,
+ "parsed only {} rows from the feature table -- the parser is broken",
+ rows.len()
+ );
+ rows
+}
+
+/// The feature table's rows in `text`, as `(flag, row_is_marked_removed)`.
+///
+/// The table ends at the first line that does not start with `|`. Until v3.0.1
+/// it ended at the next BLANK line, so prose placed directly under the table
+/// was read as table text (and with no blank line, everything to EOF was).
+///
+/// Every `|` line after the header and separator must parse as a flag row,
+/// or this panics naming it. Until v3.0.1 a malformed row was skipped with
+/// `continue`, so it disappeared while the row-count floor still passed.
+fn parse_feature_table(text: &str) -> Vec<(String, bool)> {
+ const HEADER: &str = "| Flag | Crate(s) | Default | Purpose |";
let start = text.find(HEADER).unwrap_or_else(|| {
panic!(
"{HEADER:?} not found in docs/STATUS.md -- if the feature table \
was renamed or moved, update this audit in the same change"
)
});
- let table = &text[start..];
- let end = table.find("\n\n").unwrap_or(table.len());
let mut rows = Vec::new();
- for line in table[..end].lines().skip(2) {
- let Some(rest) = line.strip_prefix("| `") else {
- continue;
- };
- let Some((flag, tail)) = rest.split_once('`') else {
- continue;
+ for line in text[start..]
+ .lines()
+ .skip(2)
+ .take_while(|l| l.starts_with('|'))
+ {
+ let parsed = line
+ .strip_prefix("| `")
+ .and_then(|rest| rest.split_once('`'));
+ let Some((flag, tail)) = parsed else {
+ panic!(
+ "docs/STATUS.md's feature table has a row that does not parse as \
+ \"| `flag` | ...\": {line:?}. A malformed row would otherwise \
+ vanish from every check below."
+ );
};
// A retired flag is kept as a row for the historical record and marked.
let removed = tail.contains("*(removed)*");
rows.push((flag.to_owned(), removed));
}
+ rows
+}
+
+const SAMPLE_TABLE: &str = "\
+| Flag | Crate(s) | Default | Purpose |
+|------|----------|---------|---------|
+| `alpha` | `a` | off | first |
+| `beta` *(removed)* | — | removed | second |
+";
+
+#[test]
+fn the_table_ends_at_the_first_non_table_line() {
+ // Prose directly under the table, with no blank line, followed by a line
+ // that merely LOOKS like a row: it is not part of the table.
+ let text = format!("{SAMPLE_TABLE}Note under the table.\n| `gamma` | x | y | z |\n");
+ assert_eq!(
+ parse_feature_table(&text),
+ vec![("alpha".to_owned(), false), ("beta".to_owned(), true)]
+ );
+}
+
+#[test]
+#[should_panic(expected = "does not parse")]
+fn a_malformed_row_is_refused_not_skipped() {
+ let text = format!("{SAMPLE_TABLE}| delta | x | y | z |\n");
+ let _ = parse_feature_table(&text);
+}
+
+/// Flags whose row is marked removed but which a manifest still declares.
+fn removed_but_declared(
+ rows: &[(String, bool)],
+ declared: &BTreeMap>,
+) -> Vec {
+ rows.iter()
+ .filter(|(flag, removed)| *removed && declared.contains_key(flag))
+ .map(|(flag, _)| flag.clone())
+ .collect()
+}
+
+#[test]
+fn removed_but_declared_finds_a_re_declared_flag() {
+ let rows = parse_feature_table(SAMPLE_TABLE);
+ let mut declared: BTreeMap> = BTreeMap::new();
+ declared
+ .entry("alpha".into())
+ .or_default()
+ .insert("a".into());
+ assert_eq!(removed_but_declared(&rows, &declared), Vec::::new());
+ declared
+ .entry("beta".into())
+ .or_default()
+ .insert("b".into());
+ assert_eq!(
+ removed_but_declared(&rows, &declared),
+ vec!["beta".to_owned()]
+ );
+}
+
+/// A row marked removed must name a flag no manifest declares.
+///
+/// The other two directions both pass over removed rows: the first skips them,
+/// the second counts them as documented. So a manifest that re-declared
+/// `cpu-implied-dummy-reads` passed every test while this table called it
+/// removed. Added in v3.0.1.
+#[test]
+fn no_removed_flag_is_still_declared() {
+ let root = repo_root();
+ let stale = removed_but_declared(&tabled_flags(&root), &declared_features(&root));
assert!(
- rows.len() > 5,
- "parsed only {} rows from the feature table -- the parser is broken",
- rows.len()
+ stale.is_empty(),
+ "docs/STATUS.md marks these flags *(removed)* but a crates/*/Cargo.toml \
+ still declares them: {stale:?}. Either the flag is live again -- drop the \
+ marker and document it -- or the declaration should go."
);
- rows
}
/// A row may only claim a live flag if a manifest declares it.
diff --git a/crates/rustynes-test-harness/tests/release_anchor_audit.rs b/crates/rustynes-test-harness/tests/release_anchor_audit.rs
index 5a51af2e..e15b7f1e 100644
--- a/crates/rustynes-test-harness/tests/release_anchor_audit.rs
+++ b/crates/rustynes-test-harness/tests/release_anchor_audit.rs
@@ -630,9 +630,8 @@ fn the_changelog_has_a_section_for_the_workspace_version() {
// correct in the eight anchor documents that DO render markdown. Only this
// one string crosses into a plain-text surface, which is exactly why
// nothing caught it: every other consumer of the same words was fine.
- for marker in ["**", "__", "`"] {
- assert!(
- !theme.contains(marker),
+ if let Some(marker) = title_markdown_marker(theme) {
+ panic!(
"CHANGELOG header for {version} carries the markdown marker {marker:?} \
in its theme. That theme is parsed by release-auto.yml into the \
GitHub release title, which is NOT markdown-rendered, so the marker \
@@ -641,6 +640,27 @@ fn the_changelog_has_a_section_for_the_workspace_version() {
}
}
+/// The first markdown marker in a release-title theme that would appear
+/// literally in the plain-text GitHub release title, or `None`.
+///
+/// Single `*` and `_` stay allowed: they occur in ordinary prose. Strikethrough
+/// (`~~`) and a link (`](`) were added in v3.0.1; until then only `**`, `__`
+/// and a backtick were refused.
+fn title_markdown_marker(theme: &str) -> Option<&'static str> {
+ ["**", "__", "`", "~~", "]("]
+ .into_iter()
+ .find(|marker| theme.contains(marker))
+}
+
+#[test]
+fn title_markers_cover_strikethrough_and_links() {
+ assert_eq!(title_markdown_marker("(a **bold** claim)"), Some("**"));
+ assert_eq!(title_markdown_marker("(a ~~struck~~ claim)"), Some("~~"));
+ assert_eq!(title_markdown_marker("(see [the notes](url))"), Some("]("));
+ // Ordinary prose stays allowed.
+ assert_eq!(title_markdown_marker("(the API major_version, 3*4)"), None);
+}
+
/// Anchors that quote a codename must quote the CHANGELOG's codename.
///
/// A correct version beside the previous release's codename is still a wrong
diff --git a/crates/rustynes-test-harness/tests/release_state_prose_audit.rs b/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
index 3636a977..7b88be87 100644
--- a/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
+++ b/crates/rustynes-test-harness/tests/release_state_prose_audit.rs
@@ -136,8 +136,12 @@ fn versions_near(line: &str, at: usize, window: usize) -> Vec {
num.push(bytes[j]);
j += 1;
}
- if triple(&num).is_some() {
- out.push(num);
+ // A version ending a sentence (`v2.4.2.`) carries the full stop;
+ // without the trim `triple` rejects it and the version is
+ // silently skipped (fixed in v3.0.1).
+ let num = num.trim_end_matches('.');
+ if triple(num).is_some() {
+ out.push(num.to_owned());
}
i = j;
} else {
@@ -561,3 +565,17 @@ fn a_multibyte_line_does_not_panic_the_scanner() {
let line = "Next up — v9.9.9 → the next thing — really";
assert_eq!(versions_near(line, 0, 60), vec!["9.9.9".to_string()]);
}
+
+#[test]
+fn a_version_ending_a_sentence_is_still_found() {
+ // The scanner collects digits and dots greedily, so `v2.4.2.` read as
+ // "2.4.2.", which `triple` rejects -- and the version was silently
+ // skipped, letting non-compliant prose pass the audit.
+ let line = "Next up — v2.4.2.";
+ assert_eq!(versions_near(line, 0, 60), vec!["2.4.2".to_string()]);
+ let line = "Shipped in v2.4.2. Then v2.4.3...";
+ assert_eq!(
+ versions_near(line, 0, 60),
+ vec!["2.4.2".to_string(), "2.4.3".to_string()]
+ );
+}
From 93cc31bbba4951b22b89ad52136716da251de3c3 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:56:11 -0400
Subject: [PATCH 27/44] fix(cosim): a public doc linked a private constant; CI
now builds its rustdoc
`MAX_APU_TRACE_CAPACITY` (pub) linked `[`APU_REC_LEN`]`, a private const,
which `RUSTDOCFLAGS="-D warnings" cargo doc` rejects. It went unseen because
rustynes-cosim is excluded from the workspace and CI built its clippy and
tests but never its rustdoc; the v3.0.1 sweep's code agent found it by running
it by hand. The link is now a plain code span (the project rule for items a
public doc cannot link), and the lint job gains a cosim rustdoc step beside
the existing cosim clippy and test steps.
Verified: cosim `cargo doc --no-deps` with -D warnings passes; actionlint
passes on ci.yml.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.github/workflows/ci.yml | 7 +++++++
crates/rustynes-cosim/src/lib.rs | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6e4c9f22..e5fc901d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -361,6 +361,13 @@ jobs:
--all-targets -- -D warnings
- name: "test (excluded crate: rustynes-cosim)"
run: cargo test --manifest-path crates/rustynes-cosim/Cargo.toml
+ # v3.0.1: the workspace rustdoc step above does not reach the excluded
+ # crate either, and a public doc linking a private constant sat there
+ # unseen until a review fix ran it by hand.
+ - name: "rustdoc (excluded crate: rustynes-cosim)"
+ env:
+ RUSTDOCFLAGS: "-D warnings"
+ run: cargo doc --no-deps --manifest-path crates/rustynes-cosim/Cargo.toml
# ... and again WITH `ppu-state-trace`, because that crate's
# `state_trace_records_carry_their_cpu_cycle` test is itself gated on the
# feature and so ran NOWHERE in CI. It exists to catch a field that is
diff --git a/crates/rustynes-cosim/src/lib.rs b/crates/rustynes-cosim/src/lib.rs
index 7df9cf84..f48ddbdb 100644
--- a/crates/rustynes-cosim/src/lib.rs
+++ b/crates/rustynes-cosim/src/lib.rs
@@ -76,7 +76,7 @@ const APU_REC_LEN: usize = 16;
/// Upper bound on `--apu-trace`'s capacity, in RECORDS.
///
/// One record is one CPU cycle, so this is 8,388,608 cycles -- roughly 281 NTSC
-/// frames, and 134 MB once multiplied by [`APU_REC_LEN`]. Comfortably above any
+/// frames, and 134 MB once multiplied by `APU_REC_LEN`. Comfortably above any
/// legitimate rung-4 stimulus (a 24-frame run wants ~715,000) and bounded.
///
/// The bound exists because the capacity is external input that reaches
From 1bf6f14ff36072a86659d665df99d450f9028ec4 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 20:57:49 -0400
Subject: [PATCH 28/44] docs(changelog): the MiSTer dot-0 A12 fix and the
review sweep in [Unreleased]
Two v3.0.1 results with no entry yet: the MiSTer core's dot-0 A12 rule
now gated on cycle-0 rendering (found by the new mapper4mmc3oddskip080
stimulus, which also gates the odd-frame exception through the new /IRQ-rise
comparison), and the back-to-#1 bot-review sweep (290 unanswered items, 473
verdicts, 80 still-valid findings fixed), listed by user-visible effect.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
CHANGELOG.md | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77290e1b..60978e4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,31 @@ cycle-accurate core later replaced.
header and in `docs/originality-and-provenance.md`, but the site said
otherwise; it says "derived from" again (maintainer, 2026-10-06). The ten
other site comments that release reworded had already been restored.
+- **MiSTer core: the dot-0 A12 rule now asks whether cycle 0 was rendering.**
+ T-MMC3-BG-A12's rule (a visible line's cycle 0 drives the background CHR
+ address) was gated on the live rendering state at the two dots where this
+ core shows it, so a `$2001` write taking effect one dot late still applied
+ it, and the MMC3 IRQ came one CPU cycle early. A new generated test ROM
+ (`mapper4mmc3oddskip080`, written to reach the rule's odd-frame exception,
+ which nothing could) found it; the core and the emulator now agree on all
+ 2,978,055 cycles of it. The exception itself is now gated: a new comparison
+ of the cycle each /IRQ rises on catches its mutant, which the bus gate alone
+ could not. The bitstreams are rebuilt for v3.0.1 because this is RTL.
+- **Every unanswered bot review, back to PR #1, answered.** 290 unanswered
+ review threads, review-body findings and Antigravity reviews across both
+ repositories became 473 verdicts; the 80 still valid were fixed, among them:
+ the Bisqwit NTSC filter kept showing the last game frame after a ROM was
+ closed; a mapper-0 override saved from the ROM Database panel vanished on
+ restart; `bump_release.py` could exit 0 with a stale co-simulation lockfile,
+ garble a non-ASCII anchor marker, and append a stray stop after a quote;
+ three release audits could pass on prose they should fail; a review-thread
+ lister reported "0 unresolved" for a malformed response; a perf-log check
+ called a capture with no presentation clock VALID; and Dependabot's titles
+ read `chore(deps)(deps)`, with stale egui holds that blocked every future
+ egui and wgpu update (now a group that moves the five together). The
+ co-simulation crate's checkpoint parser rejects a corrupt stream instead of
+ underflowing, and CI now builds its rustdoc. Every reply is posted and every
+ open thread resolved.
### Changed
From 9502c208a65ae8e26181f02c48596b61f32f8027 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 23:02:02 -0400
Subject: [PATCH 29/44] docs(libretro): the buildbot builds every pushed
branch; v3.0.1 tested on it
Three comments (.gitlab-ci.yml, docs/agents/libretro.md, ci.yml) said the
libretro buildbot cannot run a change before `main` moves, which made the
v3.0.1 toolchain split look untestable until after release. It is false.
libretro's mirror runs a pipeline for every pushed branch: the v3.0.0 cycle
has pipelines for release/v3.0.0 and both review slices, and the pushed
release/v3.0.1 got pipeline 119606 about 20 minutes later. All 15 jobs
passed on the RUSTUP_TOOLCHAIN=1.96.0 pin, including osx-x64, osx-arm64,
ios-arm64 and tvos-arm64, the four that `-C ar` threatens.
Recorded with two facts that make it usable: the job list is public through
the GitLab API while job logs need a login (401), and a review-slice branch
fails every job by construction because it holds only part of a release.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.github/workflows/ci.yml | 6 ++++--
.gitlab-ci.yml | 6 ++++--
docs/agents/libretro.md | 2 +-
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e5fc901d..caa29f9d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -947,8 +947,10 @@ jobs:
# differently-configured lookalike.
- run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --target ${{ matrix.target }}
# Until this job existed there was ZERO libretro coverage in GitHub
- # Actions, so breakage only surfaced on libretro's own GitLab buildbot —
- # where we can't push, can't re-run, and turnaround is days. Pipeline
+ # Actions, so breakage only surfaced on libretro's own GitLab buildbot,
+ # whose job logs need a login and whose turnaround was days (it does build
+ # every pushed branch, so a release branch is tested there too; see
+ # docs/agents/libretro.md). Pipeline
# #91899 failed 9 of 10 jobs on problems this job reproduces in seconds:
# a missing cross target (see `rust-toolchain.toml`) and an upstream
# `rust-libretro` MinGW-ABI bug (see `.cargo/config.toml`).
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 30be17ec..46f627eb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -81,8 +81,10 @@
# `rustynes-libretro` on 1.96.0 (`libretro-cross`), so the floor this pin
# relies on is checked on every PR rather than discovered on the buildbot.
# The `-C ar` fix that would let this pin go is recorded in
- # `rust-toolchain.toml`; it is not taken blind because no pipeline here can
- # be run before `main` moves.
+ # `rust-toolchain.toml`. It CAN be tried before merge: libretro's mirror
+ # builds every pushed branch, not only `main` (the v3.0.1 branch's own
+ # pipeline, 119606, ran all 15 jobs green on this pin, Apple included),
+ # so a release branch carrying the fix is its own test.
RUSTUP_TOOLCHAIN: "1.96.0"
# Shared `before_script` for the jobs whose upstream template defines none
diff --git a/docs/agents/libretro.md b/docs/agents/libretro.md
index 50c6d8d3..a8c35a3f 100644
--- a/docs/agents/libretro.md
+++ b/docs/agents/libretro.md
@@ -20,7 +20,7 @@
- **Libretro buildbot CI (issue #311) shipped in PR #312 (2026-07-19/20, `b49dd1e0`) — and the upstream half is now DONE; the "stays OPEN by design" instruction this bullet used to lead with is RETIRED, see below.** `.gitlab-ci.yml` + a `[lib] name = "rustynes"` naming-collision fix + `[workspace] default-members` + RA memory-maps + an FDS load-path fix/disk-control + native Game Genie cheats all shipped. TAS and Netplay needed no new libretro-side wiring — RetroArch's own rollback/movie machinery already rides the existing `on_serialize`/`on_unserialize` hooks. **DONE as of 2026-07-21, re-measured 2026-09-20** — this bullet described the upstream half as pending for two months after it landed. Companion PRs `libretro/docs#1164` (merged 2026-07-21) and `libretro/libretro-super#2021` (merged 2026-07-24) are both in, the mirror + buildbot step the libretro team owned is complete, and `rustynes_libretro.so.zip` is on the nightly buildbot for **linux/x86_64, windows/x86_64 and apple/osx/arm64** (checked by fetching the three `latest/` listings, not by asking anyone). `dist/info/rustynes_libretro.info` upstream reads `license = "GPLv3+"`, `display_version = "v2.3.9"`. **Issue #311 is CLOSED and that is now CORRECT** — but read its timeline before citing it, because it closed twice for opposite reasons: `2026-07-19T22:28` by doublegate via a commit-body keyword (premature, reopened 27 minutes later at `22:55`), then `2026-07-21T13:07` **by `hizzlekizzle`, an upstream maintainer, by hand and with no commit id** — which is exactly the condition this bullet demanded. **The keyword rule still stands and the timeline is its evidence:** never put a closing keyword for an unfinished issue in a commit or PR body, because the first close proves GitHub acts on it instantly and the tracker then reports finished work that was not done. What retires an issue is a verified outcome — here, three buildbot listings and a maintainer's own click.
- **The libretro buildbot is a THIRD CI system with its own rules — and the pinned toolchain fights it.** The first real run (pipeline #91899, 2026-07-20) passed 1 of 10 jobs. `rust-toolchain.toml`'s `channel = "1.96.0"` makes rustup install a *fresh* toolchain inside libretro's build image, bypassing the image's pre-provisioned cross targets, so 8 jobs died on `E0463: can't find crate for core`; each job in `.gitlab-ci.yml` now runs `rustup target add ${RUST_TARGET}` (NOT added to `rust-toolchain.toml`'s `targets` — that would cost every contributor and GH Actions job ~8 extra `rust-std` downloads). The Apple jobs must use `!reference` rather than `extends` for that, because GitLab's `extends` REPLACES array keys and would silently drop the templates' `SDKROOT`/`STRIP`/`CC`/`CXX` exports. **tvOS: the upstream template's `cargo +nightly build -Zbuild-std` override is OBSOLETE — don't reinstate it.** It dates from when `aarch64-apple-tvos` was tier 3 with no distributed `rust-std`; the target has since been promoted and rustup ships a complete prebuilt std **including `panic_abort`** (verified on the pinned 1.96.0: `rustup target add aarch64-apple-tvos` gives 26 rlibs and the crate `cargo check`s clean, bindgen included). Our job overrides `script` back to `!reference [.libretro-rust-apple-base, script]`, putting tvOS on the same pinned stable as every other job. That one change dissolved THREE stacked workarounds the `+nightly` path had forced: a nightly-channel reinstall (`+nightly` outranks both `rust-toolchain.toml` and `RUSTUP_TOOLCHAIN`, so the job rode the image's stale 1.94.0-nightly, below our MSRV); `CARGO_PROFILE_RELEASE_PANIC=unwind` (bare `-Zbuild-std` omits `panic_abort`, and `CARGO_UNSTABLE_BUILD_STD` does NOT override the hardcoded crate list — the CLI `-Z` flag wins); and clearing the image's `-C ar` (see the next bullet). (Since v2.8.0 that same variable is set to `unwind` again for EVERY buildbot job, tvOS included, by `.core-defs` — deliberately, for panic containment (L-1.1), not as the old tvOS workaround; `panic_abort` being available does not make the abort profile effective there.) Worth reporting upstream: every Rust core's tvOS job could drop `+nightly` the same way. **A green GitHub Actions run does not imply a green buildbot** — the `libretro-cross` CI job (the buildbot triples a Linux runner can model — at #554, 2026-09-24: 64- and 32-bit MinGW-Windows, Linux aarch64 / i686, armhf, the webOS `armv7-unknown-linux-gnueabi`, and Android/NDK; this parenthesis first said "MinGW-Windows and Android/NDK", which had been stale since the aarch64 and armhf legs landed; the Apple families are deliberately excluded, as bindgen needs a real per-target sysroot and there is no Apple SDK on a Linux runner) is the early-warning gate; before touching anything libretro-related, cross-check `cargo check --release -p rustynes-libretro --target ` locally.
- **The libretro build image injects `-C ar` into EVERY Apple job, and it is a hard error from Rust 1.97 — a bomb armed against the next MSRV bump.** The image (not the `rust-apple.yml` template, which sets no `RUSTFLAGS` at all, and not our `.cargo/config.toml`) adds `-Car=,Clink-arg=-undefined,Clink-arg=dynamic_lookup,-rpath=` to osx-x64 / osx-arm64 / ios-arm64 / tvos-arm64. `-C ar` was a deprecated no-op for years and became a **hard error in 1.97** (bisected locally: 1.93.0-nightly / 1.96.0 / 1.96.1 warn; 1.97.1 and 1.99.0-nightly error). No job trips it today — all four Apple jobs are on the pinned 1.96.0 and merely log the warning. **The day `rust-toolchain.toml` moves to 1.97+, all four fail together** — the warning lives in that file, at the line someone would edit. Discarding the flags is behaviour-preserving, not a gamble: rustc splits `-C` at the FIRST `=`, so the whole comma-joined string is swallowed as the `ar` value and those link args have never reached the linker for *any* core (cargo prints it as one argv token), and two upstream Rust cores have green tvOS jobs on the same image with the same dead token. The override works without knowing where the image sets it because cargo takes rustflags from exactly one source, first match wins: `CARGO_ENCODED_RUSTFLAGS` → `RUSTFLAGS` → `target..rustflags` → `build.rustflags` (verified locally against a global `~/.cargo/config.toml` `build.rustflags`: `RUSTFLAGS=""` removes every injected `-C`, and empty means zero flags, not one empty argument).
-- **v3.0.1 SPLIT THE TOOLCHAIN instead of disarming the `-C ar` bomb.** `rust-toolchain.toml` moved to 1.99.0; `.gitlab-ci.yml` `.core-defs` sets `RUSTUP_TOOLCHAIN: "1.96.0"` (it outranks `rust-toolchain.toml`), and every job's `rustup target add` became `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}`. The libretro templates set no `RUSTUP_TOOLCHAIN` or rustflags (checked in `rust-apple.yml`, `rust-linux-x64.yml`, `rust-windows-x64.yml`, `rust-android-jni.yml`, `rust-webos.yml`, 2026-10-06). The seven crates the core compiles declare `rust-version = "1.96"`, and GitHub CI's `libretro-cross` reads the version out of `.gitlab-ci.yml` (no literal in `.github/`) and builds on it. **That check has already paid for itself:** clippy 1.99 rewrote `for b in self.ram.iter_mut()` (a `Box<[u8; 2048]>`) to `for b in &mut self.ram`, which 1.96 rejects (`&mut Box<[T; N]>` is not `IntoIterator` there); clippy honoured nothing about the crate's `rust-version`, and only the 1.96 build caught it (`bus.rs`, fixed as `&mut *self.ram`). The documented two-line `RUSTFLAGS` fix in `rust-toolchain.toml` would let the buildbot follow, but it was not taken blind: no buildbot pipeline can run before `main` moves.
+- **v3.0.1 SPLIT THE TOOLCHAIN instead of disarming the `-C ar` bomb.** `rust-toolchain.toml` moved to 1.99.0; `.gitlab-ci.yml` `.core-defs` sets `RUSTUP_TOOLCHAIN: "1.96.0"` (it outranks `rust-toolchain.toml`), and every job's `rustup target add` became `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile minimal --target ${RUST_TARGET}`. The libretro templates set no `RUSTUP_TOOLCHAIN` or rustflags (checked in `rust-apple.yml`, `rust-linux-x64.yml`, `rust-windows-x64.yml`, `rust-android-jni.yml`, `rust-webos.yml`, 2026-10-06). The seven crates the core compiles declare `rust-version = "1.96"`, and GitHub CI's `libretro-cross` reads the version out of `.gitlab-ci.yml` (no literal in `.github/`) and builds on it. **That check has already paid for itself:** clippy 1.99 rewrote `for b in self.ram.iter_mut()` (a `Box<[u8; 2048]>`) to `for b in &mut self.ram`, which 1.96 rejects (`&mut Box<[T; N]>` is not `IntoIterator` there); clippy honoured nothing about the crate's `rust-version`, and only the 1.96 build caught it (`bus.rs`, fixed as `&mut *self.ram`). The documented two-line `RUSTFLAGS` fix in `rust-toolchain.toml` would let the buildbot follow; it was not taken in v3.0.1. **CORRECTED the same day: the buildbot CAN be tested before merge.** libretro's mirror of the GitHub repo runs a pipeline for every pushed BRANCH, not only `main` (`git.libretro.com/api/v4/projects/libretro%2FRustyNES/pipelines?ref=`; the v3.0.0 cycle shows pipelines for `release/v3.0.0` and the review slices). The pushed `release/v3.0.1` got pipeline 119606 within about 20 minutes, and all 15 jobs (Apple included) passed on the 1.96 pin. The job LIST is public; job LOGS need a login (401). A review-slice branch fails every job by construction (each holds only part of a release), so expect red there and do not read it as a defect.
- **`rust-libretro 0.3.2` is unmaintained (no commit since 2023-02) and has a MinGW bug we work around.** It casts a keycode with `cfg(target_family = "windows")`, but C enum signedness follows the *ABI*: only **MSVC** gives plain enums `int` — under **MinGW** (`x86_64-pc-windows-gnu`, what the buildbot builds) bindgen emits `c_uint` and the crate fails `E0308`. `.cargo/config.toml`'s `[env] BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu = "--target=x86_64-pc-windows-msvc"` fixes it; the generated-bindings diff is 28 lines, all enum signedness. Don't "clean up" that env var without rebuilding for `x86_64-pc-windows-gnu`.
- **The libretro `.info` RetroArch reads is a DIFFERENT FILE from this repo's, and it went stale for eleven days.** RetroArch downloads `dist/info/rustynes_libretro.info` from `libretro/libretro-super`; `crates/rustynes-libretro/rustynes_libretro.info` is an unrelated copy that nothing syncs and nothing compared. So the v2.2.9 GPL relicense reached `Cargo.toml`, `NOTICE`, `deny.toml`, the SPDX headers and the local `.info` — and **not** the file users actually see, which went on advertising "MIT OR Apache-2.0" at `display_version = v2.2.1`. Both upstream PRs had merged *exactly two weeks before* the relicense, so no sync could have carried it. **Upstream PRs are opened only on MINOR or MAJOR releases** — a `vX.Y.0` where `X` or `Y` changed. Patch releases do NOT trigger an upstream sync; the `.info` `display_version` is allowed to lag through a `v2.4.1`..`v2.4.9` run and is brought current at the next `vX.Y.0`. **Next scheduled upstream sync: v3.0.0, prepared at v2.9.9 and submitted with v3.0.0** (ADR 0043, 2026-09-29, which replaced the 2026-08-23 rule below that it would wait for the MiSTer core to be complete). History, superseded by ADR 0043: the 2026-08-23 decision deferred it past v2.5.0, superseding a 2026-08-20 decision that named v2.5.0; it held that the sync happens at whatever `vX.Y.0` the MiSTer core is fully implemented at, not at the next `vX.Y.0` on the calendar, so v2.5.0 opened no upstream PR and no release before the core was done would. Its reason, which ADR 0043 answered by labelling the v3.0.0 bitstreams not hardware-verified, was that the Fabric line's user-visible claims are about a core that does not exist yet, and an `.info` advertising a version whose headline feature is unfinished is the same class of stale declaration this bullet exists to prevent — just in the other direction. The one override is a **licence change, which syncs immediately** regardless of version — that is what this bullet's incident was about, and it stays on the same footing as a release. `crates/rustynes-test-harness/tests/libretro_info_audit.rs` pins the local file against the workspace manifest so the sync is a *copy*, never a re-derivation; it cannot see upstream, so the sync itself stays a human step. libretro `.info` uses short license tokens, not SPDX, and marks "or later" with a trailing `+` (tallied across all 316 upstream cores: `GPLv2` x100, `GPLv3` x64, `GPLv2+` x19, `GPLv3+` x5) — RustyNES is **`GPLv3+`**; a bare `GPLv3` understates it as GPL-3.0-only. Full detail + the surface table: `docs/libretro/UPSTREAM_SYNC.md`.
- **iOS/iPadOS/tvOS availability is a THIRD repo and a HARDCODED list — being on the buildbot buys nothing there.** **RESOLVED 2026-08-16** by `libretro/RetroArch#19416` (merged `76f60626984a`; verified against `master`, not the PR state — `rustynes` sits at line 268 between `reminiscence` and `sameboy`). The mechanism below is retained because it recurs for any other core and for the sibling forges, and because "in the build list" is not "installable": it ships with the next App Store RetroArch build. iOS cannot download cores (Apple bans fetching executable code), so the App Store build bundles a fixed set chosen by `pkg/apple/update-cores.sh` in `libretro/RetroArch`. That script has two lists: `allcores`, fetched *dynamically* from the buildbot directory (RustyNES is in it automatically), and `appstore_cores`, a hardcoded array (RustyNES is **absent**). The iOS/tvOS build phases run `rm -f ${SRCROOT}//modules/*.dylib` then `./update-cores.sh appstore`, so only the hardcoded list survives. One entry covers iOS + tvOS + macOS App Store. **Alphabetical order is mandatory** — `rustynes` sorts between `reminiscence` and `sameboy`; re-check the neighbours at submission time rather than trusting a line number.
From dece4a24f1f8f9ca24fcb2e2f47f796f84373407 Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 23:34:47 -0400
Subject: [PATCH 30/44] fix(provenance): record the shared Bisqwit NTSC pass as
derived
`rustynes-gfx-shaders` documented `BISQWIT_WGSL` as "an independent
implementation of the NES composite signal model documented at the NESdev
wiki; no third-party emulator code is incorporated". That is a
self-certification, which docs/ai-emulator-provenance-guardrails.md forbids
as a finished claim. It was also false. The constant is
`include_str!("bisqwit.wgsl")`, and that file is generated verbatim from
`rustynes-frontend`'s `ntsc_bisqwit::shader_src()` (a drift test keeps the two
identical). `ntsc_bisqwit.rs` has long carried a `// Provenance:` header and a
section 1 row saying its coefficient tables were ported verbatim from
Bisqwit's C via Mesen2's `BisqwitNtscFilter` (GPL-3.0-or-later). So the
shared crate held a copy of recorded-derived code under a sentence denying
it. The maintainer's decision (2026-10-07) was to treat it as derived; the
existing record made that the only consistent answer.
Changes:
- lib.rs gains an SPDX line and a `// Provenance:` header naming
`BISQWIT_WGSL` / `bisqwit.wgsl`. The doc comment now states the derivation
and says it read "independent" until v3.0.1.
- Section 1 of docs/originality-and-provenance.md: the CRT row now names only
crt_stack.rs (lib.rs merely re-exports those shaders), and lib.rs gets its
own Bisqwit; Mesen2 row.
- NOTICE's video-filter section names the generated WGSL file.
- provenance_record_audit.rs: `ROW_WITHOUT_HEADER` is now empty. Its one
entry was lib.rs, excused as "only re-exports the CRT shaders", and that
excuse is exactly what hid the Bisqwit copy from the audit.
- to-dos/ROADMAP.md: T-NTSC-PROVENANCE closed, with the original record
kept below the resolution.
Deliberately not done: no header in bisqwit.wgsl itself. The file is
generated byte for byte from shader_src(), and a header there would fail the
drift test. The lib.rs header covers the constant that includes it.
Verified: provenance_record_audit 2/2 pass. With the new lib.rs header
mutated away it fails every_section_1_row_names_a_file_that_carries_a_header
(caught), and passes again once restored. clippy -D warnings on
rustynes-test-harness and rustynes-gfx-shaders (all targets) is clean, fmt is
clean, and markdownlint passes.
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
NOTICE | 3 ++-
crates/rustynes-gfx-shaders/src/lib.rs | 12 ++++++++++--
.../tests/provenance_record_audit.rs | 11 +++++------
docs/originality-and-provenance.md | 3 ++-
to-dos/ROADMAP.md | 12 +++++++++++-
5 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/NOTICE b/NOTICE
index 95a6f796..25ab1491 100644
--- a/NOTICE
+++ b/NOTICE
@@ -222,7 +222,8 @@ Video shaders and NTSC filters
--------------------------------------------------------------------------------
RustyNES's optional CRT shader stack (crates/rustynes-gfx-shaders/) and NTSC
-filters (crates/rustynes-frontend/src/ntsc_bisqwit.rs, ntsc_lmp88959.rs):
+filters (crates/rustynes-frontend/src/ntsc_bisqwit.rs, ntsc_lmp88959.rs, and the
+generated copy of the Bisqwit pass in crates/rustynes-gfx-shaders/src/bisqwit.wgsl):
* The Bisqwit NTSC filter's numeric coefficient tables were ported verbatim (via
Mesen2's implementation) and are therefore GPL-derived (listed above and in
diff --git a/crates/rustynes-gfx-shaders/src/lib.rs b/crates/rustynes-gfx-shaders/src/lib.rs
index b3d40777..0f75b697 100644
--- a/crates/rustynes-gfx-shaders/src/lib.rs
+++ b/crates/rustynes-gfx-shaders/src/lib.rs
@@ -1,3 +1,7 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// Provenance: `BISQWIT_WGSL` (`bisqwit.wgsl`) is a generated verbatim copy of the desktop's Bisqwit NES composite-NTSC pass, whose numeric coefficient tables were ported verbatim from Bisqwit's C via Mesen2's `BisqwitNtscFilter` (Mesen2: GPL-3.0-or-later). See docs/originality-and-provenance.md (Section 1)
+// and NOTICE for the complete, audited derivation record.
//! Shared WGSL presentation-shader sources for the RustyNES wgpu render path.
//!
//! These `pub const` strings are the single source of truth for the presentation
@@ -307,8 +311,12 @@ fn fs_main(in: VsOut) -> @location(0) vec4 {
/// The Bisqwit-style composite NES NTSC post-pass.
///
-/// An independent implementation of the NES composite signal model documented at
-/// the NESdev wiki ("NTSC video"); no third-party emulator code is incorporated.
+/// Derived: the numeric coefficient tables were ported verbatim from Bisqwit's C
+/// via Mesen2's `BisqwitNtscFilter` (GPL-3.0-or-later), the same derivation
+/// `rustynes-frontend`'s `ntsc_bisqwit.rs` records; the two-level composite signal
+/// shape is documented at the NESdev wiki ("NTSC video"). This sentence said
+/// "independent" until v3.0.1 (T-NTSC-PROVENANCE): it was a self-certification on a
+/// generated copy of code already recorded as derived.
///
/// Unlike CRT/LMP it samples the **palette-index** framebuffer as an `R16Uint`
/// texture (`@group(0) @binding(0) idx_tex`), not the RGBA, plus the per-frame NTSC
diff --git a/crates/rustynes-test-harness/tests/provenance_record_audit.rs b/crates/rustynes-test-harness/tests/provenance_record_audit.rs
index 58602db8..c21414a7 100644
--- a/crates/rustynes-test-harness/tests/provenance_record_audit.rs
+++ b/crates/rustynes-test-harness/tests/provenance_record_audit.rs
@@ -37,12 +37,11 @@ fn workspace_root() -> PathBuf {
/// §1 rows that name a file with no header of its own, each with the reason.
/// The list is where a reviewer sees the exception argued.
-const ROW_WITHOUT_HEADER: &[(&str, &str)] = &[(
- "crates/rustynes-gfx-shaders/src/lib.rs",
- "named in the crt_stack.rs row because it re-exports CRT_ROYALE_WGSL / CRT_GUEST_WGSL / \
- MEGATRON_WGSL; the reimplemented shaders themselves live in crt_stack.rs, which carries \
- the header",
-)];
+const ROW_WITHOUT_HEADER: &[(&str, &str)] = &[];
+// `crates/rustynes-gfx-shaders/src/lib.rs` was the one entry until v3.0.1: it was
+// named in the crt_stack.rs row only because it re-exports the CRT shaders. It also
+// carries `BISQWIT_WGSL`, a generated copy of derived code, so it now has a header and
+// a row of its own (T-NTSC-PROVENANCE).
fn rs_files(dir: &Path, out: &mut Vec) {
// An unreadable directory fails the test rather than being skipped: a
diff --git a/docs/originality-and-provenance.md b/docs/originality-and-provenance.md
index 5a30c41a..a7797359 100644
--- a/docs/originality-and-provenance.md
+++ b/docs/originality-and-provenance.md
@@ -70,7 +70,8 @@ LGPL-2.1-or-later ones may be incorporated into a GPL-3.0-or-later work.
| `crates/rustynes-apu/src/blip.rs` | blip_buf (Blargg) | band-limited synthesis (`blip_buf`) | LGPL-2.1-or-later |
| `crates/rustynes-apu/src/opll.rs` | emu2413 (upstream MIT; Mesen2 vendors it) | `emu2413.{h,cpp}` | MIT |
| `crates/rustynes-frontend/src/ntsc_bisqwit.rs` | Bisqwit; Mesen2 | Bisqwit `nes_ntsc`-style composite model as implemented by Mesen2's `BisqwitNtscFilter`; **numeric tables ported verbatim** | GPL-3.0-or-later (Mesen2) |
-| `crates/rustynes-gfx-shaders/src/crt_stack.rs`, `src/lib.rs` | CRT-Royale, crt-guest-advanced, Sony Megatron | single-pass WGSL reimplementations of those shaders (see §6) | GPL-2.0-or-later / permissive |
+| `crates/rustynes-gfx-shaders/src/crt_stack.rs` | CRT-Royale, crt-guest-advanced, Sony Megatron | single-pass WGSL reimplementations of those shaders (see §6); `src/lib.rs` re-exports them | GPL-2.0-or-later / permissive |
+| `crates/rustynes-gfx-shaders/src/lib.rs` | Bisqwit; Mesen2 | `BISQWIT_WGSL` (`src/bisqwit.wgsl`), a generated verbatim copy of `ntsc_bisqwit.rs`'s pass, tables ported from Bisqwit's C via Mesen2's `BisqwitNtscFilter` (recorded v3.0.1, T-NTSC-PROVENANCE: its doc comment had called it independent) | GPL-3.0-or-later (Mesen2) |
| `crates/rustynes-core/src/vs_dualsystem.rs` | Mesen2 | `NesConsole::RunFrame` / `RunVsSubConsole`, `VsControlManager` (reset seed, coin routing), `UpdateMainSubBit` (recorded v2.9.9, NC-17) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m019_namco163.rs` | Mesen2 | `NesSoundMixer::GetOutputVolume`: the N163 `* 20` output weight (recorded v2.9.9, NC-17) | GPL-3.0-or-later |
| `crates/rustynes-mappers/src/m016_bandai_fcg.rs` | Mesen2 | `Eeprom24C01` / `Eeprom24C02`, `Core/NES/Mappers/Bandai/` | GPL-3.0-or-later |
diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md
index ae5f683e..52af5ab7 100644
--- a/to-dos/ROADMAP.md
+++ b/to-dos/ROADMAP.md
@@ -1233,7 +1233,17 @@ A10-A12, bypassing the MMC3's CHR banks. The mapper 45 page does not mention
CHR-RAM at all. Open: fix only from a document or a hardware measurement, as
for T-GA23C-POWERON. (The document turned out to be the mapper 372 page.)
-## T-NTSC-PROVENANCE — is the Bisqwit-style NTSC pass derived from Bisqwit's published code? (raised v3.0.1, OPEN for the maintainer)
+## T-NTSC-PROVENANCE — is the Bisqwit-style NTSC pass derived from Bisqwit's published code? (raised v3.0.1, CLOSED v3.0.1: recorded as derived)
+
+**Closed in v3.0.1 (maintainer, 2026-10-07: treat it as derived).** The answer
+was already in the tree: `BISQWIT_WGSL` is generated verbatim from
+`rustynes-frontend`'s `ntsc_bisqwit.rs`, whose header and section 1 row have
+long recorded its tables as ported from Bisqwit's C via Mesen2's
+`BisqwitNtscFilter`. The "independent" sentence sat on a copy of that same
+code. `lib.rs` now carries a `// Provenance:` header and its own section 1
+row, `NOTICE` names the generated file, and `provenance_record_audit.rs` lost
+its one exception (`ROW_WITHOUT_HEADER` is empty; deleting the new header
+fails the audit). The record below is kept as it was raised.
`crates/rustynes-gfx-shaders/src/lib.rs` (around line 311) documents the
Bisqwit-style composite NTSC post-pass as "an independent implementation of
From 416fe7d78cc8e981df767bc9a08371babb88220c Mon Sep 17 00:00:00 2001
From: DoubleGate
Date: Tue, 6 Oct 2026 23:37:17 -0400
Subject: [PATCH 31/44] chore(provenance): move the TriCNES source out of the
repository
The MIT TriCNES source, the AccuracyCoin author's own emulator, had been
vendored under crates/rustynes-test-harness/golden/tricnes/ since v2.0.x:
tricnes-full-src/ (upstream, byte-identical) and tricnes-harness-src/ (that
source plus 88 instrumentation lines). The vendoring was a documented,
attributed exception to the reference firewall. But it put reference-emulator
source inside every repository search, and in the v3.0.1 backlog survey a TODO
grep over crates/ matched lines inside it. The firewall is meant to hold by
the source being out of reach, not by searchers remembering an exclusion.
Maintainer decision (2026-10-07): move it out after verifying the licence,
and keep it available, attributed, as reference when troubleshooting
AccuracyCoin tests its author has already passed.
Licence: confirmed MIT twice. The vendored LICENSE reads (c) 2025 Chris
Siebert, and the GitHub API reports `100thCoin/TriCNES` as MIT; AccuracyCoin
is also MIT. Upstream HEAD is still 94f1b117 (2026-09-16), which is what was
vendored.
New location, outside ~/Code:
- ~/reference-oracles/TriCNES: a fresh clone checked out at 94f1b117.
`diff -rq` against the vendored copy found no differing file. Upstream
only adds files that were never vendored (.sln, icon.ico, SDL2.dll,
.gitattributes, .gitignore, Settings.settings).
- ~/reference-oracles/TriCNES-rustynes-harness: the instrumented harness,
copied as-is, plus LICENSE and a README.
The rule, now explicit in two places:
- AGENTS.md has a new firewall bullet naming TriCNES as the ONE exception.
- docs/ai-emulator-provenance-guardrails.md section 3a gives the terms:
- AccuracyCoin work only, after rungs 1-3 of the escalation ladder;
- read from outside the repository only;
- anything written after reading it is a port, attributed in the same
change (header, section 1 row, NOTICE);
- not a precedent for GPL references or third-party HDL, and the
sibling's rung-4 declaration still applies.
Before this change, AGENTS.md listed TriCNES among the black boxes whose
source "must not" be read, while oracle-tooling-setup.md called its vendored
source "the deliberate exception". Both now say the same thing.
Repointed:
- the in-tree README (now explains the move and lists the remaining evidence
files);
- the path comments at bus.rs (DMA dispatch table) and cpu.rs (BPL poll);
- NOTICE, which also corrects the vendored commit: it said f388af0b, one
re-sync stale;
- .gitignore (the bin/obj negations went with the harness);
- docs/tooling/oracle-tooling-setup.md sections 2 and 2a;
- docs/agents/accuracy-oracle.md.
The cross-diff outputs (implicit_*_xdiff_*.txt, implicit_abort_region.txt)
stay, because they are evidence and not source.
Not done: the lint exclusions for golden/tricnes/ in .markdownlintignore,
.pre-commit-config.yaml and ruff.toml are kept. The directory still holds the
committed xdiff text, which was never written to lint rules.
Verified:
- provenance_record_audit 2/2 and release_state_prose_audit 9/9 pass;
- rustynes-core and rustynes-cpu build;
- markdownlint passes on every touched document;
- no test reads the removed paths (git grep across crates/*/tests and src).
Co-Authored-By: Claude Opus 5.5 (1M context)
Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj
---
.gitignore | 14 +-
AGENTS.md | 1 +
NOTICE | 11 +-
crates/rustynes-core/src/bus.rs | 4 +-
crates/rustynes-cpu/src/cpu.rs | 2 +-
.../golden/tricnes/README.md | 44 +-
.../tricnes-full-src/6502Documentation.cs | 692 -
.../tricnes/tricnes-full-src/App.config | 6 -
.../tricnes/tricnes-full-src/Emulator.cs | 11660 ----------------
.../Forms/TriC72PinConnector.Designer.cs | 46 -
.../Forms/TriC72PinConnector.cs | 132 -
.../Forms/TriC72PinConnector.resx | 120 -
.../golden/tricnes/tricnes-full-src/LICENSE | 21 -
.../tricnes/tricnes-full-src/Program.cs | 22 -
.../Properties/AssemblyInfo.cs | 36 -
.../Properties/Resources.Designer.cs | 71 -
.../Properties/Resources.resx | 117 -
.../Properties/Settings.Designer.cs | 30 -
.../golden/tricnes/tricnes-full-src/README.md | 70 -
.../tricnes/tricnes-full-src/TriCNES.csproj | 182 -
.../forms/TASProperties.Designer.cs | 263 -
.../tricnes-full-src/forms/TASProperties.cs | 130 -
.../tricnes-full-src/forms/TASProperties.resx | 200 -
.../forms/TASProperties3ct.Designer.cs | 235 -
.../forms/TASProperties3ct.cs | 161 -
.../forms/TASProperties3ct.resx | 197 -
.../forms/TriCHexEditor.Designer.cs | 194 -
.../tricnes-full-src/forms/TriCHexEditor.cs | 295 -
.../tricnes-full-src/forms/TriCHexEditor.resx | 200 -
.../forms/TriCNESGUI.Designer.cs | 487 -
.../tricnes-full-src/forms/TriCNESGUI.cs | 1977 ---
.../tricnes-full-src/forms/TriCNESGUI.resx | 209 -
.../forms/TriCNTViewer.Designer.cs | 143 -
.../tricnes-full-src/forms/TriCNTViewer.cs | 61 -
.../tricnes-full-src/forms/TriCNTViewer.resx | 200 -
.../forms/TriCTASTimeline.Designer.cs | 426 -
.../tricnes-full-src/forms/TriCTASTimeline.cs | 1474 --
.../forms/TriCTASTimeline.resx | 222 -
.../forms/TriCTraceLogger.Designer.cs | 170 -
.../tricnes-full-src/forms/TriCTraceLogger.cs | 92 -
.../forms/TriCTraceLogger.resx | 197 -
.../tricnes-full-src/mappers/Mapper_AOROM.cs | 91 -
.../tricnes-full-src/mappers/Mapper_CNROM.cs | 44 -
.../tricnes-full-src/mappers/Mapper_FDS.cs | 308 -
.../tricnes-full-src/mappers/Mapper_FME7.cs | 321 -
.../tricnes-full-src/mappers/Mapper_GxROM.cs | 69 -
.../tricnes-full-src/mappers/Mapper_MMC1.cs | 296 -
.../tricnes-full-src/mappers/Mapper_MMC2.cs | 188 -
.../tricnes-full-src/mappers/Mapper_MMC3.cs | 487 -
.../tricnes-full-src/mappers/Mapper_NROM.cs | 7 -
.../tricnes-full-src/mappers/Mapper_NULL.cs | 55 -
.../tricnes-full-src/mappers/Mapper_UxROM.cs | 72 -
.../tricnes/tricnes-full-src/packages.config | 4 -
.../tricnes-harness-src/6502Documentation.cs | 692 -
.../tricnes/tricnes-harness-src/Emulator.cs | 11660 ----------------
.../tricnes/tricnes-harness-src/Program.cs | 47 -
.../mappers/Mapper_AOROM.cs | 91 -
.../mappers/Mapper_CNROM.cs | 44 -
.../tricnes-harness-src/mappers/Mapper_FDS.cs | 308 -
.../mappers/Mapper_FME7.cs | 321 -
.../mappers/Mapper_GxROM.cs | 69 -
.../mappers/Mapper_MMC1.cs | 296 -
.../mappers/Mapper_MMC2.cs | 188 -
.../mappers/Mapper_MMC3.cs | 487 -
.../mappers/Mapper_NROM.cs | 7 -
.../mappers/Mapper_NULL.cs | 55 -
.../mappers/Mapper_UxROM.cs | 72 -
.../tricnes-harness.csproj | 14 -
docs/agents/accuracy-oracle.md | 2 +-
docs/ai-emulator-provenance-guardrails.md | 20 +
docs/tooling/oracle-tooling-setup.md | 42 +-
71 files changed, 81 insertions(+), 37090 deletions(-)
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/6502Documentation.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/App.config
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Emulator.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/LICENSE
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Program.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/AssemblyInfo.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Settings.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/README.md
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/TriCNES.csproj
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.Designer.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.resx
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_AOROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_CNROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FDS.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FME7.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_GxROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC1.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC2.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC3.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NULL.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_UxROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/packages.config
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/6502Documentation.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Emulator.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Program.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_AOROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_CNROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FDS.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FME7.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_GxROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC1.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC2.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC3.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NULL.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_UxROM.cs
delete mode 100644 crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/tricnes-harness.csproj
diff --git a/.gitignore b/.gitignore
index d1d887e9..3385cc02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,13 +73,9 @@ Cargo.lock
!/golden/**
!/crates/rustynes-test-harness/golden/**
-# ...but the vendored TriCNES harness is BUILT in place (`dotnet build -c
-# Release`, per its README), and the re-inclusion above would otherwise sweep
-# its output into the repo -- ~1.6 MB of bin/ + obj/ beside a tree whose own
-# README promises "no build artifacts". The negation is what makes this
-# necessary: without it the generic rules below would have covered these.
-/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/bin/
-/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/obj/
+# (The vendored TriCNES harness used to be BUILT in place under golden/, and two
+# negations here kept its bin/ + obj/ out. It left the repository in v3.0.1, so
+# they went with it.)
# --- Native build & FFI artifacts ---
*.so
@@ -265,8 +261,8 @@ flamegraph.svg
# directory has been REMOVED from disk and stays ignored here as a firewall guard
# so the copyleft source can never re-enter the working tree. Do NOT re-clone
# reference-emulator source into the repo; implement hardware behavior from
-# docs/test ROMs. (The MIT TriCNES source is instead deliberately vendored, with
-# attribution, under crates/rustynes-test-harness/golden/tricnes/.) See
+# docs/test ROMs. (The MIT TriCNES source was vendored until v3.0.1 and now lives
+# outside the repository at ~/reference-oracles/; guardrails section 3a.) See
# docs/ai-emulator-provenance-guardrails.md and the "MOST IMPORTANT RULE" of AGENTS.md.
/ref-proj/
diff --git a/AGENTS.md b/AGENTS.md
index 6b1a642e..6ab815dc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -28,6 +28,7 @@
**This rule outranks everything else in this file.** RustyNES exists because of a real, corrected provenance failure (GPL emulator code was reproduced despite a black-box instruction, then the honest "ported from" comments were scrubbed; the project was relicensed to **GPL-3.0-or-later** and every derived site re-attributed). The full account is `docs/provenance-failure-postmortem.md`; the preventive ruleset is **`docs/ai-emulator-provenance-guardrails.md`** (PDFs of both in `ref-docs/`). **Read the guardrails doc and treat it as binding.** The non-negotiable core:
- **REFERENCE FIREWALL.** Reference emulators (Mesen2, puNES, FCEUX, Nestopia, higan, ares, GeraNES, TriCNES, tetanes, …) are **black-box oracles**. You may run them and read their *output* (framebuffers, traces, audio, logs). You **must not** open, read, quote, or reproduce their **source** (`.c`/`.cpp`/`.h`/`.cs`/`.rs`), constants, tables, variable names, code ordering, or comments — not "for reference," not once. **The local `ref-proj/` reference-emulator clone has been removed from disk and stays gitignored (`/ref-proj/`), so the source is out of reach by design. Do not re-clone it into the working tree.** If you find such source in reach, report that it should be removed; do not read it.
+- **TriCNES IS THE ONE NAMED EXCEPTION (maintainer, 2026-10-07).** It is MIT and written by the AccuracyCoin author, and several of its models are already ported and attributed. Its source may be consulted to troubleshoot an AccuracyCoin test, after rungs 1-3 of the escalation ladder below, and only from OUTSIDE the repository: `~/reference-oracles/TriCNES` (upstream clone at `94f1b117`) and `~/reference-oracles/TriCNES-rustynes-harness` (the instrumented harness), moved out of `crates/rustynes-test-harness/golden/tricnes/` in v3.0.1 so no repository search reaches it. Anything written after reading it is a port: a `// Provenance:` header, a section 1 row and `NOTICE`, in the same change. It covers TriCNES only, and it does not lift the HDL rule below. Terms: `docs/ai-emulator-provenance-guardrails.md` section 3a.
- **THE FIREWALL COVERS HDL TOO (ADR 0037, 2026-08-20).** The v2.4.1 → v2.5.0 "Fabric" line writes a **new** NES core in SystemVerilog. `NES_MiSTer`, `fpganes`, and any other NES `rtl/` are **strict black boxes** on exactly the same terms as emulator source: never opened, read, quoted or transcribed — not the RTL, not its constants, not its module or signal names. **Permitted:** instantiating a third-party core as an opaque testbench module and comparing its *outputs*. **Not permitted:** reading it. Keep those repositories physically outside the workspace, exactly as was done for `ref-proj/`. Anything genuinely unimplementable from documentation escalates to a **new ADR before any source is opened** — this is the rule most at risk of quiet erosion, because the pull toward reading a working core is strongest exactly when the DUT and RustyNES disagree at dot 260 of scanline 241 and nesdev is ambiguous.
- **IMPLEMENT FROM DOCS.** Write hardware behavior from public documentation (`nesdev_wiki/`, `ref-docs/`, datasheets, die studies) and pin it to public test ROMs / golden vectors. Hardware behavior is a fact; the specific *code expression* is copyrighted.
- **IF YOU DERIVE, SAY SO — AND STOP.** If you do port/adapt/closely-model an external source, (1) it is a derivative work under that source's license; (2) attribute it at the site + in `docs/originality-and-provenance.md` §1 + in `NOTICE` + via an SPDX header; (3) the project license must stay compatible (GPL-3.0-or-later) — flag it to the maintainer before proceeding.
diff --git a/NOTICE b/NOTICE
index 25ab1491..a76f6891 100644
--- a/NOTICE
+++ b/NOTICE
@@ -120,11 +120,14 @@ Incorporated third-party components (permissively licensed, GPL-compatible)
test-driven accuracy"); its
PPU address/data-multiplex (ALE / octal-latch), OAM-corruption, per-cycle
DMA-dispatch and DMC-DMA state models are ported into RustyNES
- (crates/rustynes-ppu, rustynes-cpu, rustynes-core, rustynes-apu), and its full source is vendored as a golden
- oracle at crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/.
+ (crates/rustynes-ppu, rustynes-cpu, rustynes-core, rustynes-apu). Its full
+ source was vendored as a golden oracle under
+ crates/rustynes-test-harness/golden/tricnes/ until v3.0.1, when it moved
+ outside the repository (the committed cross-diff outputs remain).
https://github.com/100thCoin/TriCNES
- Ported models were taken from commit 9199870; the VENDORED oracle is commit
- f388af0b (re-synced 2026-09-11). The two are stated separately on purpose --
+ Ported models were taken from commit 9199870; the last VENDORED oracle was
+ commit 94f1b117 (re-synced 2026-09-19; this line said f388af0b until v3.0.1,
+ one re-sync stale). The two are stated separately on purpose --
one records what was derived from, the other what is in the tree, and this
entry previously gave one id for both after the vendored copy moved on.
Copyright (c) 2025 Chris Siebert -- MIT
diff --git a/crates/rustynes-core/src/bus.rs b/crates/rustynes-core/src/bus.rs
index 5a68b765..759c858b 100644
--- a/crates/rustynes-core/src/bus.rs
+++ b/crates/rustynes-core/src/bus.rs
@@ -3345,8 +3345,8 @@ impl SystemBus {
/// W3-Stage-1 (`mc-r1-dma-unified`): ONE cycle of the unified DMC/OAM DMA
/// engine — a direct port of the `TriCNES` `_6502` per-cycle DMA dispatch
- /// table (`crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/
- /// Emulator.cs` ~4233-4357), the SINGLE driver that standalone DMC,
+ /// table (the instrumented harness's `Emulator.cs` ~4233-4357; out of the
+ /// repository since v3.0.1, at `~/reference-oracles/TriCNES-rustynes-harness`), the SINGLE driver that standalone DMC,
/// standalone OAM, and the DMC-during-OAM overlap all ride — AT FLOOR
/// PARITY for this stage (the structural-equivalence proof; Stage 2 flips
/// the one engine to the breakthrough parity).
diff --git a/crates/rustynes-cpu/src/cpu.rs b/crates/rustynes-cpu/src/cpu.rs
index ba443ce6..c361ef56 100644
--- a/crates/rustynes-cpu/src/cpu.rs
+++ b/crates/rustynes-cpu/src/cpu.rs
@@ -2860,7 +2860,7 @@ impl Cpu {
// W1 (`mc-r1-branch-poll-points`): a page-cross taken branch
// polls a SECOND time at C4-start — TriCNES's
// `PollInterrupts_CantDisableIRQ` in the BPL microcode
- // (`golden/tricnes/tricnes-full-src/Emulator.cs`): if the C2-start
+ // (`100thCoin/TriCNES` `Emulator.cs` at `94f1b117`): if the C2-start
// poll already saw the IRQ this one cannot un-see it (can-SET-
// not-clear). `mc_run_irq` is frozen across the branch's
// remaining cycles by the `handle_interrupts` early-return, so
diff --git a/crates/rustynes-test-harness/golden/tricnes/README.md b/crates/rustynes-test-harness/golden/tricnes/README.md
index 63074df3..9e8fca78 100644
--- a/crates/rustynes-test-harness/golden/tricnes/README.md
+++ b/crates/rustynes-test-harness/golden/tricnes/README.md
@@ -1,24 +1,26 @@
-# TriCNES — vendored reference oracle (MIT)
+# TriCNES cross-diff evidence (the source now lives outside the repository)
-TriCNES is the AccuracyCoin author's own emulator (Chris "100th_Coin" Siebert), which passes the full
-144-test battery — the gold oracle for these tests. Re-synced 2026-09-19 to upstream `94f1b117`
-(previously `f388af0`, 2026-09-11, and `f54d8be`, 2026-05-05); that window carries the OAM2-address
-and OAM-evaluation fixes matching AccuracyCoin's new `Advanced Sprite Evaluation` page, the 6502
-internal-data-bus fix, Mapper 66 (GxROM), and — in `94f1b117` — a one-line RESET fix
-(`CPU_SYNC = true;` in `Reset()`). `tricnes-full-src/` is byte-identical to upstream `94f1b117`;
-the same one line was applied by hand to the instrumented `tricnes-harness-src/Emulator.cs`, whose
-delta against the full source stays at its established 88 instrumentation lines. Vendored here under its **MIT License** (see
-`tricnes-full-src/LICENSE`) as the per-cycle cross-diff oracle for the DMA-tail / Program-M work,
-salvaged from `/tmp` so it survives reboot.
+TriCNES is the AccuracyCoin author's own emulator (Chris "100th_Coin" Siebert, **MIT**), which
+passes the full AccuracyCoin battery and served as the per-cycle cross-diff oracle for the
+DMA-tail / Program-M work. Several of its models are ported into RustyNES and attributed in
+`NOTICE` and `docs/originality-and-provenance.md` section 1.
-- **`tricnes-harness-src/`** — the trimmed, **instrumented** harness actually used for the cross-diff:
- `Emulator.cs` (with the per-cycle window logger), `Program.cs`, `6502Documentation.cs`, `mappers/`
- (all 11 `Mapper_*.cs`, required to build — the re-sync added `Mapper_GxROM.cs`), `tricnes-harness.csproj`. Build: `dotnet build -c Release`
- (.NET 10 SDK). The MIT license in `../tricnes-full-src/LICENSE` covers this trimmed copy too.
-- **`tricnes-full-src/`** — the complete upstream TriCNES source (`.cs`/`.csproj`/`.resx` + `LICENSE`,
- no build artifacts), for reference / re-trimming the harness.
-- **`implicit_abort_*_xdiff_*.txt` / `implicit_abort_region.txt`** — committed cross-diff outputs.
+**Moved out in v3.0.1 (maintainer decision, 2026-10-07).** The vendored source trees
+(`tricnes-full-src/`, byte-identical to upstream `94f1b117`, and `tricnes-harness-src/`, that
+source plus 88 instrumentation lines) were removed from the repository so no search over it
+reaches reference-emulator source. They now live at:
-Upstream: `github.com/100thCoin/TriCNES`. Reverse-engineered model:
-`docs/audit/v2.0-f2-tricnes-reference-model-2026-06-02.md`. Setup/regeneration:
-`docs/tooling/oracle-tooling-setup.md` §2 / §2a.
+- `~/reference-oracles/TriCNES`: a clone of `github.com/100thCoin/TriCNES` at `94f1b117`
+ (checked file-for-file against the vendored copy before removal; upstream additionally carries
+ its `.sln`, `icon.ico`, `SDL2.dll` and settings file, which were never vendored);
+- `~/reference-oracles/TriCNES-rustynes-harness`: the instrumented harness, with `LICENSE`.
+ Build: `dotnet build -c Release` (.NET 10 SDK).
+
+On another machine, re-create them from upstream; the harness is in this repository's history
+before the v3.0.1 removal commit. When and how the source may be consulted (AccuracyCoin work,
+after rungs 1-3, always attributed) is `docs/ai-emulator-provenance-guardrails.md` section 3a.
+
+What stays here is evidence, not source: the committed cross-diff outputs
+(`implicit_abort_*_xdiff_*.txt`, `implicit_abort_region.txt`, `implicit_540_grid_xdiff_*.txt`).
+Reverse-engineered model: `docs/audit/v2.0-f2-tricnes-reference-model-2026-06-02.md`. Tooling:
+`docs/tooling/oracle-tooling-setup.md` sections 2 and 2a.
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/6502Documentation.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/6502Documentation.cs
deleted file mode 100644
index 9fd7cb4b..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/6502Documentation.cs
+++ /dev/null
@@ -1,692 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace TriCNES
-{
- public class Op
- {
- public byte code;
- public string mnemonic;
- public string mode;
- public int length;
- public int affectedFlags;
- public string CycleByCycle;
- public string InstructionDocumentation;
-
- public Op(byte c, string m, string mo, int l, int a, string d, string i)
- {
- code = c;
- mnemonic = m;
- mode = mo;
- length = l;
- affectedFlags = a;
- CycleByCycle = d;
- InstructionDocumentation = i;
- }
-
-
- }
-
-
-
- public static class Documentation
- {
-
- // This class is exlusively referenced for debugging and trace logging information.
- // It's also possible I mistyped some numbers here and there, and probably shouldn't be 100% trusted.
-
-
-
- //cycle information from https://www.atarihq.com/danb/files/64doc.txt
-
- static int cFlag = 1;
- static int zFlag = 2;
- static int iFlag = 4;
- static int dFlag = 8;
-
-
- static int vFlag = 64;
- static int nFlag = 128;
- static int aChanges = 256;
- static int xChanges = 512;
- static int yChanges = 1024;
- static int stackPChanges = 2048;
- static int pcChanges = 4096;
- static int memChanges = 8192;
-
-
- static string[] CycleDocs =
- {
- //0 BRK
- " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away), increment PC\r\n 3 $0100,S W push PCH on stack, decrement S\r\n 4 $0100,S W push PCL on stack, decrement S\r\n 5 $0100,S W push P on stack (with B flag set), decrement S\r\n 6 $FFFE R fetch PCL\r\n 7 $FFFF R fetch PCH"
- ,
- //1 RTI
- " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull P from stack, increment S\r\n 5 $0100,S R pull PCL from stack, increment S\r\n 6 $0100,S R pull PCH from stack"
- ,
- //2 RTS
- " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull PCL from stack, increment S\r\n 5 $0100,S R pull PCH from stack\r\n 6 PC R increment PC"
- ,
- //3 PHA, PHP
- " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S W push register on stack, decrement S"
- ,
- //4 PLA, PLP
- " # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)\r\n 3 $0100,S R increment S\r\n 4 $0100,S R pull register from stack"
- ,
- //5 JSR
- " # address R/W description\r\n --- ------- --- -------------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low address byte, increment PC\r\n 3 $0100,S R internal operation (predecrement S?)\r\n 4 $0100,S W push PCH on stack, decrement S\r\n 5 $0100,S W push PCL on stack, decrement S\r\n 6 PC R copy low address byte to PCL, fetch high address byte to PCH"
- ,
- //6 Accumulator or implied addressing
- " Accumulator or implied addressing\r\n\r\n # address R/W description\r\n --- ------- --- -----------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R read next instruction byte (and throw it away)"
- ,
- //7 Immediate addressing
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch value, increment PC"
- ,
- // --Absolute Instructions--
- //8 JMP
- " # address R/W description\r\n --- ------- --- -------------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low address byte, increment PC\r\n 3 PC R copy low address byte to PCL, fetch high address byte to PCH"
- ,
- //9 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address R read from effective address"
- ,
- //10 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address R read from effective address\r\n 5 address W write the value back to effective address, and do the operation on it\r\n 6 address W write the new value to effective address"
- ,
- //11 Write instructions (STA, STX, STY, SAX)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, increment PC\r\n 4 address W write register to effective address"
- ,
- // --Zero page addressing--
- //12 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from effective address"
- ,
- //13 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from effective address\r\n 4 address W write the value back to effective address, and do the operation on it\r\n 5 address W write the new value to effective address"
- ,
- //14 Write instructions (STA, STX, STY, SAX)
- " # address R/W description\r\n --- ------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address W write register to effective address"
- ,
- // --Zero page indexed addressing--
- //15 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP)
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register to it\r\n 4 address+I* R read from effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled."
- ,
- //16 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- --------- --- ---------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register X to it\r\n 4 address+X* R read from effective address\r\n 5 address+X* W write the value back to effective address, and do the operation on it\r\n 6 address+X* W write the new value to effective address\r\n\r\n Note: * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled."
- ,
- //17 Write instructions (STA, STX, STY, SAX)
- " # address R/W description\r\n --- --------- --- -------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch address, increment PC\r\n 3 address R read from address, add index register to it\r\n 4 address+I* W write to effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address is always zero,\r\n i.e. page boundary crossings are not handled."
- ,
- // --Absolute indexed addressing--
- //18 Read instructions (LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, LAE, SHS, NOP)
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register to low address byte, increment PC\r\n 4 address+I* R read from effective address, fix the high byte of effective address\r\n 5+ address+I R re-read from effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100.\r\n\r\n + This cycle will be executed only if the effective address\r\n was invalid during cycle #4, i.e. page boundary was crossed."
- ,
- //19 Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register X to low address byte, increment PC\r\n 4 address+X* R read from effective address, fix the high byte of effective address\r\n 5 address+X R re-read from effective address\r\n 6 address+X W write the value back to effective address, and do the operation on it\r\n 7 address+X W write the new value to effective address\r\n\r\n Notes: * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100."
- ,
- //20 Write instructions (STA, STX, STY, SHA, SHX, SHY)
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch low byte of address, increment PC\r\n 3 PC R fetch high byte of address, add index register to low address byte, increment PC\r\n 4 address+I* R read from effective address, fix the high byte of effective address\r\n 5 address+I W write to effective address\r\n\r\n Notes: I denotes either index register (X or Y).\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100. Because\r\n the processor cannot undo a write to an invalid\r\n address, it always reads from the address first."
- ,
- //21 Relative addressing (BCC, BCS, BNE, BEQ, BPL, BMI, BVC, BVS)
- " # address R/W description\r\n --- --------- --- ---------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch operand, increment PC. If branch is not taken, the instruction has ended.\r\n 3+ PC R If branch is taken, add operand to PCL.\r\n 4! PC* R Fix PCH.\r\n Notes: * The high byte of Program Counter (PCH) may be invalid\r\n at this time, i.e. it may be smaller or bigger by $100.\r\n\r\n + If branch is taken, this cycle will be executed.\r\n\r\n ! If branch occurs to different page, this cycle will be\r\n executed."
- ,
- // --Indexed indirect addressing--
- //22 Read instructions (LDA, ORA, EOR, AND, ADC, CMP, SBC, LAX)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address R read from effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled."
- ,
- //23 Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address R read from effective address\r\n 7 address W write the value back to effective address, and do the operation on it\r\n 8 address W write the new value to effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled."
- ,
- //24 Write instructions (STA, SAX)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R read from the address, add X to it\r\n 4 pointer+X R fetch effective address low\r\n 5 pointer+X+1 R fetch effective address high\r\n 6 address W write to effective address\r\n\r\n Note: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled."
- ,
- // --Indirect indexed addressing--
- //25 Read instructions (LDA, EOR, AND, ORA, ADC, SBC, CMP)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6+ address+Y R read from effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100.\r\n\r\n + This cycle will be executed only if the effective address\r\n was invalid during cycle #5, i.e. page boundary was crossed."
- ,
- //26 Read-Modify-Write instructions (SLO, SRE, RLA, RRA, ISC, DCP)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6 address+Y R read from effective address\r\n 7 address+Y W write the value back to effective address, and do the operation on it\r\n 8 address+Y W write the new value to effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100."
- ,
- //27 Write instructions (STA, SHA)
- " # address R/W description\r\n --- ----------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address, increment PC\r\n 3 pointer R fetch effective address low\r\n 4 pointer+1 R fetch effective address high, add Y to low byte of effective address\r\n 5 address+Y* R read from effective address, fix high byte of effective address\r\n 6 address+Y W write to effective address\r\n\r\n Notes: The effective address is always fetched from zero page,\r\n i.e. the zero page boundary crossing is not handled.\r\n\r\n * The high byte of the effective address may be invalid\r\n at this time, i.e. it may be smaller by $100."
- ,
- // --Absolute indirect addressing--
- //28 JMP
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, increment PC\r\n 2 PC R fetch pointer address low, increment PC\r\n 3 PC R fetch pointer address high, increment PC\r\n 4 pointer R fetch low address to latch\r\n 5 pointer+1* R fetch PCH, copy latch to PCL\r\n\r\n Note: * The PCH will always be fetched from the same page\r\n than PCL, i.e. page boundary crossing is not handled.\r\n\r\n How Real Programmers Acknowledge Interrupts"
- ,
- //29
- //HLT
- " # address R/W description\r\n --- --------- --- ------------------------------------------\r\n 1 PC R fetch opcode, does not increment PC\r\n 2 PC R fetch opcode, does not increment PC\r\n 3 PC R fetch opcode, does not increment PC\r\n 4 PC R fetch opcode, does not increment PC\r\n 5 PC R fetch opcode, does not increment PC\r\n 6 PC R fetch opcode, does not increment PC\r\n 7 PC R fetch opcode, does not increment PC\r\n ... PC R fetch opcode, does not increment PC\r\n\r\n Notes: This process goes on forever."
- };
-
- // this explains what each isntruction does by their pnuemonic
-
- static string[] InstructionDocs =
- {
- //0 BRK
- "Break\n\nPushes PC to the stack.\n\nPushes processor status to the stack.\nPC' = ($FFFE)\nSP' = SP-3"
- ,
- //1 ORA
- "Bitwise OR with Accumulator\n\nA' = A|M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //2 HLT
- "Halt\n\nHalts the processor."
- ,
- //3 NOP
- "No operation."
- ,
- //4 ASL
- "Arithmetic Shift Left\n\nM' = M<<1\n\nCflag' = (M>=0x80)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)"
- ,
- //5 SLO
- "Arithemtic Shift Left then Bitwise OR with Accumulator\n\nM' = M<<1\nA' = A|M'\n\nCflag' = (M>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //6 PHP
- "Push Processor\n\nPushes processor status to the stack.\n\nSP' = SP-1"
- ,
- //7 ANC
- "Bitwise AND with Accumulator then Set Carry if Negative\n\nA' = A & M\n\nCflag' = (A'>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //8 BPL
- "Branch on Plus\n\nIf the negative flag is not set, branch.\n\nPC' = PC + (!NFlag ? SignedOperand : 0)"
- ,
- //9 CLC
- "Clear Carry Flag\n\nCFlag' = false"
- ,
- //10 JSR
- "Jump to Subroutine\n\nPushes PC to the stack\nPC' = Operand\nSP' = SP-2"
- ,
- //11 AND
- "Bitwise AND with Accumulator\n\nA' = A&M\n\nZFlag = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //12 RLA
- "Rotate Left then Bitwise AND with Accumulator\n\nM' = M<<1 + CFlag\nA' = A&M'\n\nCflag' = (M>=0x80)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //13 BIT
- "Bit Test\n\nZFlag = (A=M)\nNFlag = ((M>>7)&1==1)\nVFlag = ((M>>6)&1==1)"
- ,
- //14 ROL
- "Rotate Left\n\nM' = M<<1 + CFlag\n\nCflag' = (M>=0x80)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)"
- ,
- //15 PLP
- "Pull Processor\n\nPulls processor status from the stack.\n\nSP' = SP+1"
- ,
- //16 BMI
- "Branch on Minus\n\nIf the negative flag is set, branch.\n\nPC' = PC + (NFlag ? SignedOperand : 0)"
- ,
- //17 SEC
- "Set Carry Flag\n\nCFlag' = true"
- ,
- //18 RTI
- "Return from Interrupt\n\nPulls the processor from the stack.\nPulls PC from the stack.\n\nSP' = SP+3"
- ,
- //19 EOR
- "Bitwise Exclusive OR with Accumulator\n\nA' = A^M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //20 SRE
- "Logical Shift Right then Bitwise Exclusive OR with Accumulator\n\nM' = M>>2\nA' = A^M'\n\nCFlag' = (M&1==1)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //21 LSR
- "Logical Shift Right\n\nM' = M>>2\n\nCFlag' = (M&1==1)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)"
- ,
- //22 PHA
- "Push A\n\nPushes A to the stack.\n\nSP' = SP-1"
- ,
- //23 ASR
- "Bitwise AND with Accumulator then Logical Shift Right Accumulator\n\nA' = ((A&M)>>1)\nCFlag' = ((A&M)&1==1)\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //24 JMP
- "Jump\n\nPC' = M"
- ,
- //25 BVC
- "Branch on Overflow Clear\n\nIf the overflow flag is not set, branch.\n\nPC' = PC + (!VFlag ? SignedOperand : 0)"
- ,
- //26 CLI
- "Clear Interrupt Disable Flag\n\nIFlag' = false"
- ,
- //27 RTS
- "Return from Subroutine\n\nPulls the PC from the stack.\\SP' = SP + 2"
- ,
- //28 ADC
- "Add with Carry\n\nA' = A + M + CFlag\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //29 RRA
- "Rotate Right then Add With Carry\n\nM' = (M>>1)+Cflag*0x80\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //30 ROR
- "Rotate Right\n\nM' = M>>1 + CFlag*0x80\n\nCflag' = (M&1)\nZflag' = (M'==0)\nNflag' = (M'>=0x80)"
- ,
- //31 PLA
- "Pull A\n\nPull A from the stack\n\nSP' = SP+1"
- ,
- //32 ARR
- "Bitwise AND with A then Rotate A and check bits\n\nA' = ((A&M)>>1)+Carry*0x80\n\nCFlag = ((A'>>6)&1==1)\nVFlag = ((A'>>5)&1==1)\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //33 BVS
- "Branch on Overflow Set\n\nIf the overflow flag is set, branch.\n\nPC' = PC + (VFlag ? SignedOperand : 0)"
- ,
- //34 SEI
- "Set Interrupt Disable Flag\n\nIFlag' = true"
- ,
- //35 STA
- "Store A\n\nM' = A"
- ,
- //36 SAX
- "Store A and X\n\nM' = A&X"
- ,
- //37 STY
- "Store Y\n\nM' = Y"
- ,
- //38 STX
- "Store X\n\nM' = X"
- ,
- //39 DEY
- "Decrement Y\n\nY' = Y-1\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)"
- ,
- //40 TXA
- "Transfer X to A\n\nA' = X\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //41 ANE
- "Bitwise OR A with Magic then Bitwise AND with X AND with Memory\n\nA' = (A | magic) & X & M\n\n *Note: 'Magic' depends on the chip manufacturer\n 'Magic' is usually 00, EE, EF, FE, or FF"
- ,
- //42 BCC
- "Branch on Carry Clear\n\nIf the carry flag is not set, branch.\n\nPC' = PC + (!CFlag ? SignedOperand : 0)"
- ,
- //43 TXS
- "Transfer X to Stack Pointer\n\nSP' = X"
- ,
- //44 SHA
- "Store Bitwise AND X with A AND the High Byte of the Operand Plus 1\n\nM' = A & X & (HIGH(Arg)+1)"
- ,
- //45 TYA
- "Transfer Y to A\n\nA' = Y\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //46 SHY
- "Store Bitwise AND Y with The High Byte of the Operand Plus 1\n\nM' = Y & (HIGH(Arg)+1)"
- ,
- //47 SHS
- "Transfer Bitwise AND A with X to Stack Pointer then Store Bitwise And Stack Pointer with the High Byte of the Operand Plus 1\n\nSP' = A&X\nM' = SP'&(HIGH(Arg)+1)"
- ,
- //48 SHX
- "Store Bitwise AND X with The High Byte of the Operand Plus 1\n\nM' = X & (HIGH(Arg)+1)"
- ,
- //49 LDY
- "Load Y\n\nY' = M\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)"
- ,
- //50 LDA
- "Load A\n\nA' = M\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //51 LDX
- "Load X\n\nX' = M\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //52 LAX
- "Load A X\n\nA' = M\nX' = M\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //53 TAY
- "Transfer A to Y\n\nY' = A\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)"
- ,
- //54 TAX
- "Transfer A to X\n\nX' = A\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //55 LXA
- "Bitwise AND with A then Transfer A to X\n\nA' = A&M\nX'=A'\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //56 BCS
- "Branch on Carry Set\n\nIf the carry flag is set, branch.\n\nPC' = PC + (CFlag ? SignedOperand : 0)"
- ,
- //57 CLV
- "Clear Overflow\n\nVFlag = false"
- ,
- //58 TSX
- "Transfer Stack Pointer to X\n\nX' = SP"
- ,
- //59 LAS
- "Transfer Bitwise AND with Stack Pointer to A, X, and Stack Pointer\n\nA' = M&SP\nX' = M&SP\nSP' = M&SP"
- ,
- //60 CPY
- "Compare Y\n\nZFlag' = (Y==M)\nCFlag' = (Y>=M)\nNFlag' = (Y-M)>0x80"
- ,
- //61 CMP
- "Compare A\n\nZFlag' = (A==M)\nCFlag' = (A>=M)\nNFlag' = (A-M)>0x80"
- ,
- //62 DCP
- "Decrement then Compare A\n\nM' = M-1\nZFlag' = (A==M')\nCFlag' = (A>=M')\nNFlag' = (A-M')>0x80"
- ,
- //63 DEC
- "Decrement\n\nM' = M-1\n\nZFlag' = (M'==0)\nNFlag' = (M'>=0x80)"
- ,
- //64 INY
- "Increment Y\n\nY' = Y+1\n\nZFlag' = (Y'==0)\nNFlag' = (Y'>=0x80)"
- ,
- //65 DEX
- "Decrement X\n\nX' = X-1\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //66 AXS
- "Load X with Subtraction with Bitwise AND X with A\n\nX' = (A&X)-M\n\nZFlag' = (X==M)\nCFlag' = (X>=M)\nNFlag' = (X-M)>0x80"
- ,
- //67 BNE
- "Branch on Not Equal\n\nIf the zero flag is not set, branch.\n\nPC' = PC + (!ZFlag ? SignedOperand : 0)"
- ,
- //68 CLD
- "Clear Decimal Flag\n\nDFlag = false"
- ,
- //69 CPX
- "Compare X\n\nZFlag' = (X==M)\nCFlag' = (X>=M)\nNFlag' = (X-M)>0x80"
- ,
- //70 SBC
- "Subtract with Carry\n\nA' = A+(0xFF-M)+CFlag\n\nVFlag' = ((A ^ (M + A + Carry)) & ((M + A + Carry) & M) & 0x80) == 0x80\nCFlag' = A + M > 0xFF\nZflag' = (A'==0)\nNflag' = (A'>=0x80)"
- ,
- //71 ISC
- "Increment then subtract from accumulator\n\nM' = M+1\nA' = A+(0xFF-M')+CFlag\n\nVFlag' = ((A ^ (M' + A + Carry)) & ((M' + A + Carry) & M') & 0x80) == 0x80\nCFlag' = A + M' > 0xFF\nZflag' = (M'==0)\nNflag' = (M'>=0x80)"
- ,
- //72 INC
- "Increment\n\nM' = M+1\n\nZFlag' = (A'==0)\nNFlag' = (A'>=0x80)"
- ,
- //73 INX
- "Increment\n\nX' = X+1\n\nZFlag' = (X'==0)\nNFlag' = (X'>=0x80)"
- ,
- //74 BEQ
- "Branch on Equal\n\nIf the zero flag is set, branch.\n\nPC' = PC + (ZFlag ? SignedOperand : 0)"
- ,
- //75 SED
- "Set Decimal\n\nDFlag = true"
-
-
- };
-
- // this table is referenced in the debugging stuff.
- // basically, for each index into this array, you can fetch an opcode's name, addressing mode, what flags/registers it can modify, documentation, and the number of cycles before a read/write.
-
- public static Op[] OpDocs = {
- new Op(0x00,"BRK","i" ,2,stackPChanges | pcChanges ,CycleDocs[0] ,InstructionDocs[0]),
- new Op(0x01,"ORA","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[1]),
- new Op(0x02,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x03,"SLO","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[5]),
- new Op(0x04,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]),
- new Op(0x05,"ORA","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[1]),
- new Op(0x06,"ASL","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[4]),
- new Op(0x07,"SLO","d" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[13],InstructionDocs[5]),
- new Op(0x08,"PHP","i" ,1,stackPChanges ,CycleDocs[3] ,InstructionDocs[6]),
- new Op(0x09,"ORA","#v" ,2,nFlag | zFlag | aChanges ,CycleDocs[7] ,InstructionDocs[1]),
- new Op(0x0A,"ASL","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[4]),
- new Op(0x0B,"ANC","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[7]),
- new Op(0x0C,"NOP","a" ,3,0 ,CycleDocs[9] ,InstructionDocs[3]),
- new Op(0x0D,"ORA","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[1]),
- new Op(0x0E,"ASL","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[4]),
- new Op(0x0F,"SLO","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[5]),
- new Op(0x10,"BPL","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[8]),
- new Op(0x11,"ORA","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[1]),
- new Op(0x12,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x13,"SLO","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[5]),
- new Op(0x14,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0x15,"ORA","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[1]),
- new Op(0x16,"ASL","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[4]),
- new Op(0x17,"SLO","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[5]),
- new Op(0x18,"CLC","i" ,1,cFlag ,CycleDocs[6] ,InstructionDocs[9]),
- new Op(0x19,"ORA","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[1]),
- new Op(0x1A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0x1B,"SLO","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[5]),
- new Op(0x1C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0x1D,"ORA","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[1]),
- new Op(0x1E,"ASL","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[4]),
- new Op(0x1F,"SLO","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[5]),
-
- new Op(0x20,"JSR","a" ,3,stackPChanges | pcChanges ,CycleDocs[5] ,InstructionDocs[10]),
- new Op(0x21,"AND","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[11]),
- new Op(0x22,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x23,"RLA","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[12]),
- new Op(0x24,"BIT","d" ,2,nFlag | zFlag | cFlag | vFlag ,CycleDocs[12],InstructionDocs[13]),
- new Op(0x25,"AND","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[11]),
- new Op(0x26,"ROL","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[14]),
- new Op(0x27,"RLA","d" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[13],InstructionDocs[12]),
- new Op(0x28,"PLP","i" ,1,cFlag|zFlag|iFlag|dFlag|vFlag|nFlag,CycleDocs[4],InstructionDocs[15]),
- new Op(0x29,"AND","#v" ,2,nFlag | zFlag | aChanges ,CycleDocs[7] ,InstructionDocs[11]),
- new Op(0x2A,"ROL","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[14]),
- new Op(0x2B,"ANC","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[7]),
- new Op(0x2C,"BIT","a" ,3,nFlag | zFlag | cFlag | vFlag ,CycleDocs[9] ,InstructionDocs[13]),
- new Op(0x2D,"AND","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[11]),
- new Op(0x2E,"ROL","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[14]),
- new Op(0x2F,"RLA","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[12]),
- new Op(0x30,"BMI","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[16]),
- new Op(0x31,"AND","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[11]),
- new Op(0x32,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x33,"RLA","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[12]),
- new Op(0x34,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0x35,"AND","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[11]),
- new Op(0x36,"ROL","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[14]),
- new Op(0x37,"RLA","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[12]),
- new Op(0x38,"SEC","i" ,1,cFlag ,CycleDocs[6] ,InstructionDocs[17]),
- new Op(0x39,"AND","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[11]),
- new Op(0x3A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0x3B,"RLA","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[12]),
- new Op(0x3C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0x3D,"AND","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[11]),
- new Op(0x3E,"ROL","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[14]),
- new Op(0x3F,"RLA","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[12]),
-
- new Op(0x40,"RTI","i" ,1,0xFF | stackPChanges | pcChanges ,CycleDocs[1] ,InstructionDocs[18]),
- new Op(0x41,"EOR","(d,x)" ,2,nFlag | zFlag | aChanges ,CycleDocs[22],InstructionDocs[19]),
- new Op(0x42,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x43,"SRE","(d,x)" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[20]),
- new Op(0x44,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]),
- new Op(0x45,"EOR","d" ,2,nFlag | zFlag | aChanges ,CycleDocs[12],InstructionDocs[19]),
- new Op(0x46,"LSR","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[21]),
- new Op(0x47,"SRE","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[13],InstructionDocs[20]),
- new Op(0x48,"PHA","i" ,1,stackPChanges ,CycleDocs[3] ,InstructionDocs[22]),
- new Op(0x49,"EOR","#v" ,2,nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[19]),
- new Op(0x4A,"LSR","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[21]),
- new Op(0x4B,"ASR","#v" ,2,nFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[23]),
- new Op(0x4C,"JMP","a" ,3,0 ,CycleDocs[8] ,InstructionDocs[24]),
- new Op(0x4D,"EOR","a" ,3,nFlag | zFlag | aChanges ,CycleDocs[9] ,InstructionDocs[19]),
- new Op(0x4E,"LSR","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[21]),
- new Op(0x4F,"SRE","a" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[20]),
- new Op(0x50,"BVC","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[25]),
- new Op(0x51,"EOR","(d),y" ,2,nFlag | zFlag | aChanges ,CycleDocs[25],InstructionDocs[19]),
- new Op(0x52,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x53,"SRE","(d),y" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[20]),
- new Op(0x54,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0x55,"EOR","d,x" ,2,nFlag | zFlag | aChanges ,CycleDocs[15],InstructionDocs[19]),
- new Op(0x56,"LSR","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[21]),
- new Op(0x57,"SRE","d,x" ,2,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[20]),
- new Op(0x58,"CLI","i" ,1,iFlag ,CycleDocs[6] ,InstructionDocs[26]),
- new Op(0x59,"EOR","a,y" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[19]),
- new Op(0x5A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0x5B,"SRE","a,y" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[20]),
- new Op(0x5C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0x5D,"EOR","a,x" ,3,nFlag | zFlag | aChanges ,CycleDocs[18],InstructionDocs[19]),
- new Op(0x5E,"LSR","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[21]),
- new Op(0x5F,"SRE","a,x" ,3,nFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[20]),
-
- new Op(0x60,"RTS","i" ,1,stackPChanges | pcChanges ,CycleDocs[2] ,InstructionDocs[27]),
- new Op(0x61,"ADC","(d,x)" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[22],InstructionDocs[28]),
- new Op(0x62,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x63,"RRA","(d,x)" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[23],InstructionDocs[29]),
- new Op(0x64,"NOP","d" ,2,0 ,CycleDocs[12],InstructionDocs[3]),
- new Op(0x65,"ADC","d" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[12],InstructionDocs[28]),
- new Op(0x66,"ROR","d" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[13],InstructionDocs[30]),
- new Op(0x67,"RRA","d" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[13],InstructionDocs[29]),
- new Op(0x68,"PLA","i" ,1,stackPChanges | aChanges ,CycleDocs[4] ,InstructionDocs[31]),
- new Op(0x69,"ADC","#v" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[7] ,InstructionDocs[28]),
- new Op(0x6A,"ROR","A" ,1,nFlag | zFlag | cFlag | aChanges ,CycleDocs[6] ,InstructionDocs[30]),
- new Op(0x6B,"ARR","#v" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[7] ,InstructionDocs[32]),
- new Op(0x6C,"JMP","(a)" ,3,0 ,CycleDocs[28],InstructionDocs[24]),
- new Op(0x6D,"ADC","a" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[9] ,InstructionDocs[28]),
- new Op(0x6E,"ROR","a" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[10],InstructionDocs[30]),
- new Op(0x6F,"RRA","a" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[10],InstructionDocs[29]),
- new Op(0x70,"BVS","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[33]),
- new Op(0x71,"ADC","(d),y" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[25],InstructionDocs[28]),
- new Op(0x72,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x73,"RRA","(d),y" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[26],InstructionDocs[29]),
- new Op(0x74,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0x75,"ADC","d,x" ,2,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[15],InstructionDocs[28]),
- new Op(0x76,"ROR","d,x" ,2,nFlag | zFlag | cFlag | memChanges ,CycleDocs[16],InstructionDocs[30]),
- new Op(0x77,"RRA","d,x" ,2,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[16],InstructionDocs[29]),
- new Op(0x78,"SEI","i" ,1,iFlag ,CycleDocs[6] ,InstructionDocs[34]),
- new Op(0x79,"ADC","a,y" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[18],InstructionDocs[28]),
- new Op(0x7A,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0x7B,"RRA","a,y" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[29]),
- new Op(0x7C,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0x7D,"ADC","a,x" ,3,nFlag | vFlag | zFlag | cFlag | aChanges ,CycleDocs[18],InstructionDocs[28]),
- new Op(0x7E,"ROR","a,x" ,3,nFlag | zFlag | cFlag | memChanges ,CycleDocs[19],InstructionDocs[30]),
- new Op(0x7F,"RRA","a,x" ,3,nFlag | vFlag | zFlag | cFlag | aChanges | memChanges,CycleDocs[19],InstructionDocs[29]),
-
- new Op(0x80,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]),
- new Op(0x81,"STA","(d,x)" ,2,memChanges ,CycleDocs[24],InstructionDocs[35]),
- new Op(0x82,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]),
- new Op(0x83,"SAX","(d,x)" ,2,memChanges ,CycleDocs[24],InstructionDocs[36]),
- new Op(0x84,"STY","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[37]),
- new Op(0x85,"STA","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[35]),
- new Op(0x86,"STX","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[38]),
- new Op(0x87,"SAX","d" ,2,memChanges ,CycleDocs[14],InstructionDocs[36]),
- new Op(0x88,"DEY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[39]),
- new Op(0x89,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]),
- new Op(0x8A,"TXA","i" ,1,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[40]),
- new Op(0x8B,"ANE","#v" ,2,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[41]),
- new Op(0x8C,"STY","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[37]),
- new Op(0x8D,"STA","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[35]),
- new Op(0x8E,"STX","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[38]),
- new Op(0x8F,"SAX","a" ,3,memChanges ,CycleDocs[11],InstructionDocs[36]),
- new Op(0x90,"BCC","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[42]),
- new Op(0x91,"STA","(d),y" ,2,memChanges ,CycleDocs[27],InstructionDocs[35]),
- new Op(0x92,"HLT","i" ,1,memChanges ,CycleDocs[29],InstructionDocs[2]),
- new Op(0x93,"SHA","(d),y" ,2,memChanges ,CycleDocs[27],InstructionDocs[44]),
- new Op(0x94,"STY","d,x" ,2,memChanges ,CycleDocs[17],InstructionDocs[37]),
- new Op(0x95,"STA","d,x" ,2,memChanges ,CycleDocs[17],InstructionDocs[35]),
- new Op(0x96,"STX","d,y" ,2,memChanges ,CycleDocs[17],InstructionDocs[38]),
- new Op(0x97,"SAX","d,y" ,2,memChanges ,CycleDocs[17],InstructionDocs[36]),
- new Op(0x98,"TYA","i" ,1,aChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[45]),
- new Op(0x99,"STA","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[35]),
- new Op(0x9A,"TXS","i" ,1,stackPChanges ,CycleDocs[6] ,InstructionDocs[43]),
- new Op(0x9B,"SHS","a,y" ,3,stackPChanges | memChanges,CycleDocs[20],InstructionDocs[47]),
- new Op(0x9C,"SHY","a,x" ,3,memChanges ,CycleDocs[20],InstructionDocs[46]),
- new Op(0x9D,"STA","a,x" ,3,memChanges ,CycleDocs[20],InstructionDocs[35]),
- new Op(0x9E,"SHX","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[48]),
- new Op(0x9F,"SHA","a,y" ,3,memChanges ,CycleDocs[20],InstructionDocs[44]),
-
- new Op(0xA0,"LDY","#v" ,2,yChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[49]),
- new Op(0xA1,"LDA","(d,x)" ,2,aChanges | nFlag | zFlag ,CycleDocs[22],InstructionDocs[50]),
- new Op(0xA2,"LDX","#v" ,2,xChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[51]),
- new Op(0xA3,"LAX","(d,x)" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[22],InstructionDocs[52]),
- new Op(0xA4,"LDY","d" ,2,yChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[49]),
- new Op(0xA5,"LDA","d" ,2,aChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[50]),
- new Op(0xA6,"LDX","d" ,2,xChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[51]),
- new Op(0xA7,"LAX","d" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[12],InstructionDocs[52]),
- new Op(0xA8,"TAY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[53]),
- new Op(0xA9,"LDA","#v" ,2,aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[50]),
- new Op(0xAA,"TAX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[54]),
- new Op(0xAB,"LXA","#v" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[7] ,InstructionDocs[55]),
- new Op(0xAC,"LDY","a" ,3,yChanges | nFlag | zFlag ,CycleDocs[9] ,InstructionDocs[49]),
- new Op(0xAD,"LDA","a" ,3,aChanges | nFlag | zFlag ,CycleDocs[9] ,InstructionDocs[50]),
- new Op(0xAE,"LDX","a" ,3,xChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[51]),
- new Op(0xAF,"LAX","a" ,3,xChanges | aChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[52]),
- new Op(0xB0,"BCS","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[56]),
- new Op(0xB1,"LDA","(d),y" ,2,aChanges | nFlag | zFlag ,CycleDocs[25],InstructionDocs[50]),
- new Op(0xB2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0xB3,"LAX","(d),y" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[25],InstructionDocs[52]),
- new Op(0xB4,"LDY","d,x" ,2,yChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[49]),
- new Op(0xB5,"LDA","d,x" ,2,aChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[50]),
- new Op(0xB6,"LDX","d,y" ,2,xChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[51]),
- new Op(0xB7,"LAX","d,y" ,2,xChanges | aChanges | nFlag | zFlag ,CycleDocs[15],InstructionDocs[52]),
- new Op(0xB8,"CLV","i" ,1,vFlag ,CycleDocs[6] ,InstructionDocs[57]),
- new Op(0xB9,"LDA","a,y" ,3,aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[50]),
- new Op(0xBA,"TSX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[58]),
- new Op(0xBB,"LAS","a,y" ,3,nFlag | zFlag | aChanges | xChanges | stackPChanges ,CycleDocs[18],InstructionDocs[59]),
- new Op(0xBC,"LDY","a,x" ,3,yChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[49]),
- new Op(0xBD,"LDA","a,x" ,3,aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[50]),
- new Op(0xBE,"LDX","a,y" ,3,xChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[51]),
- new Op(0xBF,"LAX","a,y" ,3,xChanges | aChanges | nFlag | zFlag ,CycleDocs[18],InstructionDocs[52]),
-
- new Op(0xC0,"CPY","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[60]),
- new Op(0xC1,"CMP","(d,x)" ,2,nFlag | zFlag | cFlag ,CycleDocs[22],InstructionDocs[61]),
- new Op(0xC2,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]),
- new Op(0xC3,"DCP","(d,x)" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[23],InstructionDocs[62]),
- new Op(0xC4,"CPY","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[60]),
- new Op(0xC5,"CMP","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[61]),
- new Op(0xC6,"DEC","d" ,2,memChanges | nFlag | zFlag ,CycleDocs[13],InstructionDocs[63]),
- new Op(0xC7,"DCP","d" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[13],InstructionDocs[62]),
- new Op(0xC8,"INY","i" ,1,yChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[64]),
- new Op(0xC9,"CMP","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[61]),
- new Op(0xCA,"DEX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[65]),
- new Op(0xCB,"AXS","#v" ,2,memChanges | nFlag | cFlag | zFlag ,CycleDocs[7] ,InstructionDocs[66]),
- new Op(0xCC,"CPY","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[60]),
- new Op(0xCD,"CMP","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[61]),
- new Op(0xCE,"DEC","a" ,3,memChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[63]),
- new Op(0xCF,"DCP","a" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[10],InstructionDocs[62]),
- new Op(0xD0,"BNE","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[67]),
- new Op(0xD1,"CMP","(d),y" ,2,nFlag | zFlag | cFlag ,CycleDocs[25],InstructionDocs[61]),
- new Op(0xD2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0xD3,"DCP","(d),y" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[26],InstructionDocs[62]),
- new Op(0xD4,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0xD5,"CMP","d,x" ,2,nFlag | zFlag | cFlag ,CycleDocs[15],InstructionDocs[61]),
- new Op(0xD6,"DEC","d,x" ,2,memChanges | nFlag | zFlag ,CycleDocs[16],InstructionDocs[63]),
- new Op(0xD7,"DCP","d,x" ,2,memChanges | nFlag | zFlag | cFlag ,CycleDocs[16],InstructionDocs[62]),
- new Op(0xD8,"CLD","i" ,1,dFlag ,CycleDocs[6] ,InstructionDocs[68]),
- new Op(0xD9,"CMP","a,y" ,3,nFlag | zFlag | cFlag ,CycleDocs[18],InstructionDocs[61]),
- new Op(0xDA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0xDB,"DCP","a,x" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[19],InstructionDocs[62]),
- new Op(0xDC,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0xDD,"CMP","a,x" ,3,nFlag | zFlag | cFlag ,CycleDocs[18],InstructionDocs[61]),
- new Op(0xDE,"DEC","a,x" ,3,memChanges | nFlag | zFlag ,CycleDocs[19],InstructionDocs[63]),
- new Op(0xDF,"DCP","a,x" ,3,memChanges | nFlag | zFlag | cFlag ,CycleDocs[19],InstructionDocs[62]),
-
- new Op(0xE0,"CPX","#v" ,2,nFlag | zFlag | cFlag ,CycleDocs[7] ,InstructionDocs[69]),
- new Op(0xE1,"SBC","(d,x)" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[22],InstructionDocs[70]),
- new Op(0xE2,"NOP","#v" ,2,0 ,CycleDocs[7] ,InstructionDocs[3]),
- new Op(0xE3,"ISC","(d,x)" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[23],InstructionDocs[71]),
- new Op(0xE4,"CPX","d" ,2,nFlag | zFlag | cFlag ,CycleDocs[12],InstructionDocs[69]),
- new Op(0xE5,"SBC","d" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[12],InstructionDocs[70]),
- new Op(0xE6,"INC","d" ,2,memChanges | nFlag | zFlag ,CycleDocs[13],InstructionDocs[72]),
- new Op(0xE7,"ISC","d" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[13],InstructionDocs[71]),
- new Op(0xE8,"INX","i" ,1,xChanges | nFlag | zFlag ,CycleDocs[6] ,InstructionDocs[73]),
- new Op(0xE9,"SBC","#v" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[7] ,InstructionDocs[70]),
- new Op(0xEA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0xEB,"SBC","#v" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[7] ,InstructionDocs[70]),
- new Op(0xEC,"CPX","a" ,3,nFlag | zFlag | cFlag ,CycleDocs[9] ,InstructionDocs[69]),
- new Op(0xED,"SBC","a" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[9] ,InstructionDocs[70]),
- new Op(0xEE,"INC","a" ,3,memChanges | nFlag | zFlag ,CycleDocs[10],InstructionDocs[72]),
- new Op(0xEF,"ISC","a" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[10],InstructionDocs[71]),
- new Op(0xF0,"BEQ","r" ,2,pcChanges ,CycleDocs[21],InstructionDocs[74]),
- new Op(0xF1,"SBC","(d),y" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[25],InstructionDocs[70]),
- new Op(0xF2,"HLT","i" ,1,0 ,CycleDocs[29],InstructionDocs[2]),
- new Op(0xF3,"ISC","(d),y" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[26],InstructionDocs[71]),
- new Op(0xF4,"NOP","d,x" ,2,0 ,CycleDocs[15],InstructionDocs[3]),
- new Op(0xF5,"SBC","d,x" ,2,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[15],InstructionDocs[70]),
- new Op(0xF6,"INC","d,x" ,2,memChanges | nFlag | zFlag ,CycleDocs[16],InstructionDocs[72]),
- new Op(0xF7,"ISC","d,x" ,2,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[16],InstructionDocs[71]),
- new Op(0xF8,"SED","i" ,1,dFlag ,CycleDocs[6] ,InstructionDocs[75]),
- new Op(0xF9,"SBC","a,y" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[18],InstructionDocs[70]),
- new Op(0xFA,"NOP","i" ,1,0 ,CycleDocs[6] ,InstructionDocs[3]),
- new Op(0xFB,"ISC","a,x" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[19],InstructionDocs[71]),
- new Op(0xFC,"NOP","a,x" ,3,0 ,CycleDocs[18],InstructionDocs[3]),
- new Op(0xFD,"SBC","a,x" ,3,aChanges | nFlag | zFlag | cFlag | vFlag ,CycleDocs[18],InstructionDocs[70]),
- new Op(0xFE,"INC","a,x" ,3,memChanges | nFlag | zFlag ,CycleDocs[19],InstructionDocs[72]),
- new Op(0xFF,"ISC","a,x" ,3,aChanges | memChanges | nFlag | zFlag | cFlag | vFlag,CycleDocs[19],InstructionDocs[71])
- };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/App.config b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/App.config
deleted file mode 100644
index 5ad74305..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Emulator.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Emulator.cs
deleted file mode 100644
index 86dfc3ff..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Emulator.cs
+++ /dev/null
@@ -1,11660 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-using TriCNES.mappers;
-
-namespace TriCNES
-{
- // Coin's Contrabulous Cartswapulator!
- public class Cartridge
- {
- // Since I made this emulator with mid-instruction cartridge swapping in mind, the cartridge class holds information about the cartridge that would persist when swapped in and out.
-
- public Emulator Emu; // Mostly for triggering / clearing the IRQ from mapper function.
-
- public string Name; // For debugging
- public byte[] ROM; // The entire .nes file
-
- public byte[] PRGROM; // The entire program rom portion of the .nes file
- public byte[] CHRROM; // The entire character rom portion of the .nes file
-
- public byte MemoryMapper; // Header info: what mapper chip is this cartridge using?
- public byte SubMapper; // Header Info: what variant of the mapper chip are we using?
- public byte PRG_Size; // Header info: how many kb of PRG data does this cartridge have?
- public byte CHR_Size; // Header info: how many kb of CHR data does this cartridge have?
- public byte PRG_SizeMinus1; // PRG_Size-1; This is frequently used when grabbing data from PRG banks
-
- public bool UsingCHRRAM; // Header info: CHR RAM doesn't exist on all cartridges.
-
- public byte[] PRGRAM; // PRG RAM / Battery backed save RAM.
- public bool AlternativeNametableArrangement; // Header info: Some mapper chips support "alternative nametable arrangements", which are mapper-specific.
- public byte[] PRGVRAM; // PRG VRAM, for the alternative nametable arrangements.
-
- public static Mapper SetMapper(int m)
- {
- switch (m)
- {
- default:
- case 0: return new Mapper_NROM();
- case 1: return new Mapper_MMC1();
- case 71:
- case 2: return new Mapper_UxROM();
- case 3: return new Mapper_CNROM();
- case 4: return new Mapper_MMC3();
- case 7: return new Mapper_AOROM();
- case 9: return new Mapper_MMC2();
- case 66: return new Mapper_GxROM();
- case 69: return new Mapper_FME7();
- }
- }
- public Cartridge(string filepath) // Constructor from file path
- {
- ROM = File.ReadAllBytes(filepath); // Reads the file from the provided file path, and stores every byte into an array.
-
- // The iNES header isn't actually part of the physical cartridge.
- // Rather, the values of the iNES header are manually added to provide extra information to emulators.
- // Info such as "what mapper chip", "how many CHR banks?" and even "how should we mirror the nametables?" are part of this header.
-
- MemoryMapper = (byte)(ROM[7] & 0xF0); // Parsing the iNES header to determine what mapper chip this cartridge uses.
- MemoryMapper |= (byte)(ROM[6] >> 4); // The upper nybble of byte 6, bitwise OR with the upper nybble of byte 7.
- SubMapper = (byte)((ROM[8] & 0xF0) >> 4);
-
- PRG_Size = ROM[4]; // Parsing the iNES header to determine how many kb of PRG data exists on this cartridge.
- CHR_Size = ROM[5]; // Parsing the iNES header to determine how many kb of CHR data exists on this cartridge.
-
- PRG_SizeMinus1 = (byte)(PRG_Size - 1); // This value is occasionally used whenever a mapper has a fixed bank from the end of the PRG data, like address $E000 in the MMC3 chip.
-
- UsingCHRRAM = CHR_Size == 0; // If CHR_Size == 0, this is using CHR RAM
-
-
- PRGROM = new byte[PRG_Size * 0x4000]; // 0x4000 bytes of PRG ROM, multiplied by byte 4 of the iNES header.
- if (!UsingCHRRAM)
- {
- CHRROM = new byte[CHR_Size * 0x2000]; // 0x2000 bytes of CHR ROM, multiplied by byte 5 of the iNES header.
- }
- else
- {
- if (ROM[11] == 0)
- {
- CHRROM = new byte[0x2000]; // Default to 0x2000 bytes of CHR RAM.
- }
- else
- {
- CHRROM = new byte[64 << ROM[11]]; // 0x2000 bytes of CHR ROM, multiplied by byte 5 of the iNES header.
- }
- }
-
- NametableHorizontalMirroring = ((ROM[6] & 1) == 0); // The style in which the nametable is mirrored is part of the iNES header.
- AlternativeNametableArrangement = ((ROM[6] & 8) != 0); // Some mappers support other arrangements.
- if (AlternativeNametableArrangement)
- {
- PRGVRAM = new byte[0x800];
- }
-
- Array.Copy(ROM, 0x10, PRGROM, 0, PRGROM.Length); // This sets up the PRG ROM array with the values from the .nes file
- if (!UsingCHRRAM)
- {
- Array.Copy(ROM, 0x10 + PRGROM.Length, CHRROM, 0, CHRROM.Length); // This sets up the CHR ROM array with the values from the .nes file
- }
- // at this point, the ROM byte array is no longer needed, so null it to free up its memory.
- ROM = null;
-
- PRGRAM = new byte[0x2000]; // PRG RAM probably has different lengths depending on the mapper, but this emulator doesn't yet support any mappers in which that length isn't 2 kibibytes.
-
- Name = filepath; // For debugging, it's nice to see the file name sometimes.
-
- MapperChip = Cartridge.SetMapper(MemoryMapper);
-
- MapperChip.Cart = this;
- }
- public DiskDrive FDS; // The famicom disk system disk drive.
- public Cartridge(string filepath, string FDSBIOS_filepath)
- {
- ROM = File.ReadAllBytes(FDSBIOS_filepath); // Reads the file from the provided file path, and stores every byte into an array.
- FDS = new DiskDrive();
- FDS.InsertDisk(filepath);
- PRGRAM = new byte[0x8000]; // The FDS has 32Kib of PRG RAM!
- CHRROM = new byte[0x2000]; // and 8 Kib of CHR RAM.
- UsingCHRRAM = true;
- Name = filepath; // For debugging, it's nice to see the file name sometimes.
-
- MapperChip = new Mapper_FDS(ROM);
- MapperChip.Cart = this;
- FDS.Cart = this;
- }
-
- public bool NametableHorizontalMirroring;
-
- public Mapper MapperChip;
- }
-
- public class Mapper
- {
- public Cartridge Cart;
-
- public bool TiltingCart; // If you are using the GUI to mess with pins on the 72-pin connector. When false, we can optimize things a little.
-
- public ushort CPU_AddressIn; // internal to the cartridge
- public byte CPU_DataIn; // internal to the cartridge
- public byte CPU_DataOut; // internal to the cartridge
- public byte CPU_NonTiltData; //If you are not using the GUI to mess with pins on the 72-pin connector, we can optimize a little bit.
-
- public ushort PPU_AddressIn; // internal to the cartridge
- public byte PPU_DataIn; // internal to the cartridge
- public byte PPU_DataOut; // internal to the cartridge
- public byte PPU_NonTiltData; //If you are not using the GUI to mess with pins on the 72-pin connector, we can optimize a little bit.
-
- // Default to NROM behavior.
- public virtual void FetchCPU()
- {
- if ((Cart.Emu.ConnectorPinFloating[0] && Cart.Emu.ConnectorPinFloating[71]) || Cart.Emu.ConnectorPinFloating[35]) { return; } // If the cartridge is disconnected from power or ground, it cannot do anything.
- Connector_ReadCPUAddressPins();
-
- if (!Cart.Emu.SeventyTwoPinConnector[49]) // CPU /A15 + /M2
- {
- CPU_DataOut = Cart.PRGROM[CPU_AddressIn & (Cart.PRGROM.Length - 1)]; // Get the address from the ROM file. If the ROM only has $4000 bytes, this will make addresses > $BFFF mirrors of $8000 through $BFFF.
- Connector_SetUpCPUDataPins(CPU_DataOut);
- }
- return;
- }
-
-
- public virtual byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky.
- {
- if (Address >= 0x8000)
- {
- return Cart.PRGROM[Address & (Cart.PRGROM.Length - 1)];
- }
- return Cart.Emu.dataBus;
- }
- public virtual byte SnoopPPU(ushort Address) // For debug purposes. It's a bit clunky having to set this up for every mapper with a non-NROM CIRAM setup.
- {
- if (Address < 0x2000)
- {
- int CHR_Address = Cart.MapperChip.FetchPatternAddress(Address);
- return Cart.CHRROM[CHR_Address];
- }
- else
- {
- ushort Addr = (ushort)(Address & 0x3FF);
- Addr |= (ushort)((Cart.NametableHorizontalMirroring ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0);
- return Cart.Emu.VRAM[Addr];
- }
- }
-
- public virtual void StoreCPU(ushort Address, byte Input)
- {
- }
- public virtual int FetchPatternAddress(ushort Address)
- {
- return Address & 0x1FFF;
- }
- public virtual void AccessPPU()
- {
- Connector_ReadPPUAddressPins();
- ushort Address = PPU_AddressIn;
- if (!Cart.Emu.SeventyTwoPinConnector[64]) // (If PPU A13 is set, we don't do anything on the cartridge)
- {
- int CHR_Address = Cart.MapperChip.FetchPatternAddress(Address);
- PPU_DataIn = Connector_ReadPPUDataPins(PPU_DataIn);
- if (!Cart.Emu.SeventyTwoPinConnector[20]) // Reads
- {
- PPU_DataOut = Cart.CHRROM[CHR_Address];
- Connector_SetUpPPUDataPins(PPU_DataOut);
- }
- if (!Cart.Emu.SeventyTwoPinConnector[55] && Cart.UsingCHRRAM) // Writes
- {
- Cart.CHRROM[CHR_Address] = PPU_DataIn;
- }
- }
- else if (Cart.AlternativeNametableArrangement && Cart.Emu.SeventyTwoPinConnector[61]) // Unless we have an extra Nametable
- {
- Address &= 0x7FF; // I don't believe there are any carts that have an alternate nametable arrangement that aren't set up this way.
- PPU_DataIn = Connector_ReadPPUDataPins(PPU_DataIn);
- if (!Cart.Emu.SeventyTwoPinConnector[20]) // Reads
- {
- PPU_DataOut = Cart.PRGVRAM[Address];
- Connector_SetUpPPUDataPins(PPU_DataOut);
- }
- if (!Cart.Emu.SeventyTwoPinConnector[55]) // Writes
- {
- Cart.PRGVRAM[Address] = PPU_DataIn;
- }
- }
- }
-
- public virtual List SaveMapperRegisters()
- {
- List State = new List();
- foreach (Byte b in Cart.PRGRAM) { State.Add(b); }
- if (Cart.UsingCHRRAM)
- {
- foreach (Byte b in Cart.CHRROM) { State.Add(b); }
- }
- return State;
- }
- public virtual void LoadMapperRegisters(List State, int startIndex, out int exitIndex)
- {
- int p = startIndex;
- for (int i = 0; i < Cart.PRGRAM.Length; i++) { Cart.PRGRAM[i] = State[p++]; }
- if (Cart.UsingCHRRAM)
- {
- for (int i = 0; i < Cart.CHRROM.Length; i++) { Cart.CHRROM[i] = State[p++]; }
- }
- exitIndex = p;
- }
- public virtual void PPUClock() // runs every PPU clock. (See MMC3)
- {
- }
- public virtual void CPUClock() // runs every CPU clock. (See Sunsoft FME-7)
- {
- }
- public virtual void CPUClockRise() // runs every time the CPU clock rises. (See MMC3)
- {
- }
-
- public virtual void FDS_ByteTransferFlag()
- {
- }
- public virtual byte FDS_Get4025()
- {
- return 0;
- }
-
- public virtual string AppendToDebugLog()
- {
- return "";
- }
-
- public virtual bool CheckCIC()
- {
- // I'm not actually emulating the CIC internals. (... yet.)
- // Instead, I'll simply return true so long as the cartridge has power.
- if (TiltingCart)
- {
- if ((Cart.Emu.ConnectorPinFloating[0] && Cart.Emu.ConnectorPinFloating[71]) || Cart.Emu.ConnectorPinFloating[35]) { return false; } // If the cartridge is disconnected from power or ground, it cannot do anything, so the CIC check fails.
- if (Cart.Emu.ConnectorPinFloating[33] || Cart.Emu.ConnectorPinFloating[34] || Cart.Emu.ConnectorPinFloating[70]) { return false; } // If these pins for the CIC chip get disconnected, the CIC chip cannot match the console's CIC.
- }
- return true;
- }
-
- public void Connector_SetUpPPUAddressPins()
- {
- if(TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[28]) { Cart.Emu.SeventyTwoPinConnector[28] = (Cart.Emu.PPU_OctalLatch & 0x01) != 0; } // PPU A0
- if (!Cart.Emu.ConnectorPinFloating[27]) { Cart.Emu.SeventyTwoPinConnector[27] = (Cart.Emu.PPU_OctalLatch & 0x02) != 0; } // PPU A1
- if (!Cart.Emu.ConnectorPinFloating[26]) { Cart.Emu.SeventyTwoPinConnector[26] = (Cart.Emu.PPU_OctalLatch & 0x04) != 0; } // PPU A2
- if (!Cart.Emu.ConnectorPinFloating[25]) { Cart.Emu.SeventyTwoPinConnector[25] = (Cart.Emu.PPU_OctalLatch & 0x08) != 0; } // PPU A3
- if (!Cart.Emu.ConnectorPinFloating[24]) { Cart.Emu.SeventyTwoPinConnector[24] = (Cart.Emu.PPU_OctalLatch & 0x10) != 0; } // PPU A4
- if (!Cart.Emu.ConnectorPinFloating[23]) { Cart.Emu.SeventyTwoPinConnector[23] = (Cart.Emu.PPU_OctalLatch & 0x20) != 0; } // PPU A5
- if (!Cart.Emu.ConnectorPinFloating[22]) { Cart.Emu.SeventyTwoPinConnector[22] = (Cart.Emu.PPU_OctalLatch & 0x40) != 0; } // PPU A6
- if (!Cart.Emu.ConnectorPinFloating[58]) { Cart.Emu.SeventyTwoPinConnector[58] = (Cart.Emu.PPU_OctalLatch & 0x80) != 0; } // PPU A7
- if (!Cart.Emu.ConnectorPinFloating[59]) { Cart.Emu.SeventyTwoPinConnector[59] = (Cart.Emu.PPU_AddressBus & 0x0100) != 0; } // PPU A8
- if (!Cart.Emu.ConnectorPinFloating[60]) { Cart.Emu.SeventyTwoPinConnector[60] = (Cart.Emu.PPU_AddressBus & 0x0200) != 0; } // PPU A9
- if (!Cart.Emu.ConnectorPinFloating[62]) { Cart.Emu.SeventyTwoPinConnector[62] = (Cart.Emu.PPU_AddressBus & 0x0400) != 0; } // PPU A10
- if (!Cart.Emu.ConnectorPinFloating[61]) { Cart.Emu.SeventyTwoPinConnector[61] = (Cart.Emu.PPU_AddressBus & 0x0800) != 0; } // PPU A11
- if (!Cart.Emu.ConnectorPinFloating[63]) { Cart.Emu.SeventyTwoPinConnector[63] = (Cart.Emu.PPU_AddressBus & 0x1000) != 0; } // PPU A12
- if (!Cart.Emu.ConnectorPinFloating[64]) { Cart.Emu.SeventyTwoPinConnector[64] = (Cart.Emu.PPU_AddressBus & 0x2000) != 0; } // PPU A13
- if (!Cart.Emu.ConnectorPinFloating[57]) { Cart.Emu.SeventyTwoPinConnector[57] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; } // PPU \A13
- }
- else
- {
- // Okay, real talk. If we're not messing with the 72 pin connector, we don't need to do any of that, and we can optimize that down to this...
- PPU_AddressIn = (ushort)((Cart.Emu.PPU_AddressBus & 0x3F00) | Cart.Emu.PPU_OctalLatch);
- Cart.Emu.SeventyTwoPinConnector[64] = (Cart.Emu.PPU_AddressBus & 0x2000) != 0; // And make sure this pin is set up. We still use this for other purposes.
- }
- }
- public void Connector_ReadPPUAddressPins()
- {
- // This can only be done by the cartridge.
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[64]) { PPU_AddressIn &= 0x1FFF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[64] ? 0x2000 : 0); } // PPU A13
- if (!Cart.Emu.ConnectorPinFloating[63]) { PPU_AddressIn &= 0x2FFF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[63] ? 0x1000 : 0); } // PPU A12
- if (!Cart.Emu.ConnectorPinFloating[61]) { PPU_AddressIn &= 0x37FF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[61] ? 0x0800 : 0); } // PPU A11
- if (!Cart.Emu.ConnectorPinFloating[62]) { PPU_AddressIn &= 0x3BFF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[62] ? 0x0400 : 0); } // PPU A10
- if (!Cart.Emu.ConnectorPinFloating[60]) { PPU_AddressIn &= 0x3DFF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[60] ? 0x0200 : 0); } // PPU A9
- if (!Cart.Emu.ConnectorPinFloating[59]) { PPU_AddressIn &= 0x3EFF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[59] ? 0x0100 : 0); } // PPU A8
- if (!Cart.Emu.ConnectorPinFloating[58]) { PPU_AddressIn &= 0x3F7F; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[58] ? 0x0080 : 0); } // PPU A7
- if (!Cart.Emu.ConnectorPinFloating[22]) { PPU_AddressIn &= 0x3FBF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[22] ? 0x0040 : 0); } // PPU A6
- if (!Cart.Emu.ConnectorPinFloating[23]) { PPU_AddressIn &= 0x3FDF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[23] ? 0x0020 : 0); } // PPU A5
- if (!Cart.Emu.ConnectorPinFloating[24]) { PPU_AddressIn &= 0x3FEF; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[24] ? 0x0010 : 0); } // PPU A4
- if (!Cart.Emu.ConnectorPinFloating[25]) { PPU_AddressIn &= 0x3FF7; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[25] ? 0x0008 : 0); } // PPU A3
- if (!Cart.Emu.ConnectorPinFloating[26]) { PPU_AddressIn &= 0x3FFB; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[26] ? 0x0004 : 0); } // PPU A2
- if (!Cart.Emu.ConnectorPinFloating[27]) { PPU_AddressIn &= 0x3FFD; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[27] ? 0x0002 : 0); } // PPU A1
- if (!Cart.Emu.ConnectorPinFloating[28]) { PPU_AddressIn &= 0x3FFE; PPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[28] ? 0x0001 : 0); } // PPU A0
- }
- }
- public void Connector_SetUpPPUDataPins(byte t)
- {
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[29]) { Cart.Emu.SeventyTwoPinConnector[29] = (t & 0x01) != 0; } // PPU D0
- if (!Cart.Emu.ConnectorPinFloating[30]) { Cart.Emu.SeventyTwoPinConnector[30] = (t & 0x02) != 0; } // PPU D1
- if (!Cart.Emu.ConnectorPinFloating[31]) { Cart.Emu.SeventyTwoPinConnector[31] = (t & 0x04) != 0; } // PPU D2
- if (!Cart.Emu.ConnectorPinFloating[32]) { Cart.Emu.SeventyTwoPinConnector[32] = (t & 0x08) != 0; } // PPU D3
- if (!Cart.Emu.ConnectorPinFloating[68]) { Cart.Emu.SeventyTwoPinConnector[68] = (t & 0x10) != 0; } // PPU D4
- if (!Cart.Emu.ConnectorPinFloating[67]) { Cart.Emu.SeventyTwoPinConnector[67] = (t & 0x20) != 0; } // PPU D5
- if (!Cart.Emu.ConnectorPinFloating[66]) { Cart.Emu.SeventyTwoPinConnector[66] = (t & 0x40) != 0; } // PPU D6
- if (!Cart.Emu.ConnectorPinFloating[65]) { Cart.Emu.SeventyTwoPinConnector[65] = (t & 0x80) != 0; } // PPU D7
- }
- else
- {
- PPU_NonTiltData = t;
- }
- }
- public byte Connector_ReadPPUDataPins(byte bus)
- {
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[29]) { bus &= 0xFE; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[29] ? 0x01 : 0); } // PPU D0
- if (!Cart.Emu.ConnectorPinFloating[30]) { bus &= 0xFD; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[30] ? 0x02 : 0); } // PPU D1
- if (!Cart.Emu.ConnectorPinFloating[31]) { bus &= 0xFB; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[31] ? 0x04 : 0); } // PPU D2
- if (!Cart.Emu.ConnectorPinFloating[32]) { bus &= 0xF7; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[32] ? 0x08 : 0); } // PPU D3
- if (!Cart.Emu.ConnectorPinFloating[68]) { bus &= 0xEF; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[68] ? 0x10 : 0); } // PPU D4
- if (!Cart.Emu.ConnectorPinFloating[67]) { bus &= 0xDF; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[67] ? 0x20 : 0); } // PPU D5
- if (!Cart.Emu.ConnectorPinFloating[66]) { bus &= 0xBF; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[66] ? 0x40 : 0); } // PPU D6
- if (!Cart.Emu.ConnectorPinFloating[65]) { bus &= 0x7F; bus |= (byte)(Cart.Emu.SeventyTwoPinConnector[65] ? 0x80 : 0); } // PPU D7
- return bus;
- }
- else
- {
- return PPU_NonTiltData;
- }
- }
-
- public void Connector_SetUpCPUAddressPins(ushort Address) // the 2A03 bus, not the 6502 bus.
- {
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[12]) { Cart.Emu.SeventyTwoPinConnector[12] = (Address & 0x01) != 0; } // CPU A0
- if (!Cart.Emu.ConnectorPinFloating[11]) { Cart.Emu.SeventyTwoPinConnector[11] = (Address & 0x02) != 0; } // CPU A1
- if (!Cart.Emu.ConnectorPinFloating[10]) { Cart.Emu.SeventyTwoPinConnector[10] = (Address & 0x04) != 0; } // CPU A2
- if (!Cart.Emu.ConnectorPinFloating[9]) { Cart.Emu.SeventyTwoPinConnector[9] = (Address & 0x08) != 0; } // CPU A3
- if (!Cart.Emu.ConnectorPinFloating[8]) { Cart.Emu.SeventyTwoPinConnector[8] = (Address & 0x10) != 0; } // CPU A4
- if (!Cart.Emu.ConnectorPinFloating[7]) { Cart.Emu.SeventyTwoPinConnector[7] = (Address & 0x20) != 0; } // CPU A5
- if (!Cart.Emu.ConnectorPinFloating[6]) { Cart.Emu.SeventyTwoPinConnector[6] = (Address & 0x40) != 0; } // CPU A6
- if (!Cart.Emu.ConnectorPinFloating[5]) { Cart.Emu.SeventyTwoPinConnector[5] = (Address & 0x80) != 0; } // CPU A7
- if (!Cart.Emu.ConnectorPinFloating[4]) { Cart.Emu.SeventyTwoPinConnector[4] = (Address & 0x0100) != 0; } // CPU A8
- if (!Cart.Emu.ConnectorPinFloating[3]) { Cart.Emu.SeventyTwoPinConnector[3] = (Address & 0x0200) != 0; } // CPU A9
- if (!Cart.Emu.ConnectorPinFloating[2]) { Cart.Emu.SeventyTwoPinConnector[2] = (Address & 0x0400) != 0; } // CPU A10
- if (!Cart.Emu.ConnectorPinFloating[1]) { Cart.Emu.SeventyTwoPinConnector[1] = (Address & 0x0800) != 0; } // CPU A11
- if (!Cart.Emu.ConnectorPinFloating[38]) { Cart.Emu.SeventyTwoPinConnector[38] = (Address & 0x1000) != 0; } // CPU A12
- if (!Cart.Emu.ConnectorPinFloating[39]) { Cart.Emu.SeventyTwoPinConnector[39] = (Address & 0x2000) != 0; } // CPU A13
- if (!Cart.Emu.ConnectorPinFloating[40]) { Cart.Emu.SeventyTwoPinConnector[40] = (Address & 0x4000) != 0; } // CPU A14
- if (!Cart.Emu.ConnectorPinFloating[49]) { Cart.Emu.SeventyTwoPinConnector[49] = (Address & 0x8000) == 0; } // CPU /A15
- }
- else
- {
- // Okay, real talk. If we're not messing with the 72 pin connector, we don't need to do any of that, and we can optimize that down to this...
- CPU_AddressIn = Address;
- Cart.Emu.SeventyTwoPinConnector[49] = (Address & 0x8000) == 0; // This one is important too.
- }
- }
- public void Connector_ReadCPUAddressPins()
- {
- // This can only be done by the cartridge.
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[49]) { CPU_AddressIn &= 0x7FFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[49] ? 0 : 0x8000); } // CPU /A15
- if (!Cart.Emu.ConnectorPinFloating[40]) { CPU_AddressIn &= 0xBFFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[40] ? 0x4000 : 0); } // CPU A14
- if (!Cart.Emu.ConnectorPinFloating[39]) { CPU_AddressIn &= 0xDFFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[39] ? 0x2000 : 0); } // CPU A13
- if (!Cart.Emu.ConnectorPinFloating[38]) { CPU_AddressIn &= 0xEFFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[38] ? 0x1000 : 0); } // CPU A12
- if (!Cart.Emu.ConnectorPinFloating[1]) { CPU_AddressIn &= 0xF7FF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[1] ? 0x0800 : 0); } // CPU A11
- if (!Cart.Emu.ConnectorPinFloating[2]) { CPU_AddressIn &= 0xFBFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[2] ? 0x0400 : 0); } // CPU A10
- if (!Cart.Emu.ConnectorPinFloating[3]) { CPU_AddressIn &= 0xFDFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[3] ? 0x0200 : 0); } // CPU A9
- if (!Cart.Emu.ConnectorPinFloating[4]) { CPU_AddressIn &= 0xFEFF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[4] ? 0x0100 : 0); } // CPU A8
- if (!Cart.Emu.ConnectorPinFloating[5]) { CPU_AddressIn &= 0xFF7F; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[5] ? 0x0080 : 0); } // CPU A7
- if (!Cart.Emu.ConnectorPinFloating[6]) { CPU_AddressIn &= 0xFFBF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[6] ? 0x0040 : 0); } // CPU A6
- if (!Cart.Emu.ConnectorPinFloating[7]) { CPU_AddressIn &= 0xFFDF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[7] ? 0x0020 : 0); } // CPU A5
- if (!Cart.Emu.ConnectorPinFloating[8]) { CPU_AddressIn &= 0xFFEF; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[8] ? 0x0010 : 0); } // CPU A4
- if (!Cart.Emu.ConnectorPinFloating[9]) { CPU_AddressIn &= 0xFFF7; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[9] ? 0x0008 : 0); } // CPU A3
- if (!Cart.Emu.ConnectorPinFloating[10]) { CPU_AddressIn &= 0xFFFB; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[10] ? 0x0004 : 0); } // CPU A2
- if (!Cart.Emu.ConnectorPinFloating[11]) { CPU_AddressIn &= 0xFFFD; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[11] ? 0x0002 : 0); } // CPU A1
- if (!Cart.Emu.ConnectorPinFloating[12]) { CPU_AddressIn &= 0xFFFE; CPU_AddressIn |= (ushort)(Cart.Emu.SeventyTwoPinConnector[12] ? 0x0001 : 0); } // CPU A0
- }
- }
- public void Connector_SetUpCPUDataPins(byte t)
- {
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[48]) { Cart.Emu.SeventyTwoPinConnector[48] = (t & 0x01) != 0; } // CPU D0
- if (!Cart.Emu.ConnectorPinFloating[47]) { Cart.Emu.SeventyTwoPinConnector[47] = (t & 0x02) != 0; } // CPU D1
- if (!Cart.Emu.ConnectorPinFloating[46]) { Cart.Emu.SeventyTwoPinConnector[46] = (t & 0x04) != 0; } // CPU D2
- if (!Cart.Emu.ConnectorPinFloating[45]) { Cart.Emu.SeventyTwoPinConnector[45] = (t & 0x08) != 0; } // CPU D3
- if (!Cart.Emu.ConnectorPinFloating[44]) { Cart.Emu.SeventyTwoPinConnector[44] = (t & 0x10) != 0; } // CPU D4
- if (!Cart.Emu.ConnectorPinFloating[43]) { Cart.Emu.SeventyTwoPinConnector[43] = (t & 0x20) != 0; } // CPU D5
- if (!Cart.Emu.ConnectorPinFloating[42]) { Cart.Emu.SeventyTwoPinConnector[42] = (t & 0x40) != 0; } // CPU D6
- if (!Cart.Emu.ConnectorPinFloating[41]) { Cart.Emu.SeventyTwoPinConnector[41] = (t & 0x80) != 0; } // CPU D7
- }
- else
- {
- CPU_NonTiltData = t;
- }
- }
- public byte Connector_ReadCPUDataPins()
- {
- if (TiltingCart)
- {
- byte t = 0;
- if (!Cart.Emu.ConnectorPinFloating[48]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[48] ? 0x01 : 0); } // CPU D0
- if (!Cart.Emu.ConnectorPinFloating[47]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[47] ? 0x02 : 0); } // CPU D1
- if (!Cart.Emu.ConnectorPinFloating[46]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[46] ? 0x04 : 0); } // CPU D2
- if (!Cart.Emu.ConnectorPinFloating[45]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[45] ? 0x08 : 0); } // CPU D3
- if (!Cart.Emu.ConnectorPinFloating[44]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[44] ? 0x10 : 0); } // CPU D4
- if (!Cart.Emu.ConnectorPinFloating[43]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[43] ? 0x20 : 0); } // CPU D5
- if (!Cart.Emu.ConnectorPinFloating[42]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[42] ? 0x40 : 0); } // CPU D6
- if (!Cart.Emu.ConnectorPinFloating[41]) { t |= (byte)(Cart.Emu.SeventyTwoPinConnector[41] ? 0x80 : 0); } // CPU D7
- return t;
- }
- else
- {
- return CPU_NonTiltData;
- }
- }
- public virtual void Connector_CheckCIRAM()
- {
- if (TiltingCart)
- {
- if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; }
- if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.NametableHorizontalMirroring ? Cart.Emu.SeventyTwoPinConnector[61] : Cart.Emu.SeventyTwoPinConnector[62]; }
- }
- else
- {
- Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0;
- Cart.Emu.SeventyTwoPinConnector[21] = Cart.NametableHorizontalMirroring ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0);
- }
- }
- public virtual void Connector_IRQPin(bool t)
- {
- if (!Cart.Emu.ConnectorPinFloating[14]) { Cart.Emu.SeventyTwoPinConnector[14] = t; Cart.Emu.IRQ_LevelDetector = t; }
- }
- public virtual void Connector_PPU_RW(bool RD, bool WR)
- {
- if (!Cart.Emu.ConnectorPinFloating[20]) { Cart.Emu.SeventyTwoPinConnector[20] = !RD; }
- if (!Cart.Emu.ConnectorPinFloating[55]) { Cart.Emu.SeventyTwoPinConnector[55] = !WR; }
- }
- }
-
- public class DiskDrive
- {
- public Cartridge Cart;
- public byte[] Disk;
- public byte ShiftRegister;
- public byte ShiftRegisterLatch;
- public int clock;
-
- public int DiskAddress;
- public byte DiskAddressFine;
-
- public bool Status_ByteTransferFlag;
-
- public enum RamAdapterState
- {
- RUNNING,
- INSERTING,
- SPINUP,
- RESET,
- IDLE
- }
-
- public RamAdapterState CurrentState;
- public bool lookingForEndOfGap;
-
- public void Clock()
- {
- clock++;
-
- switch(CurrentState)
- {
- case RamAdapterState.RUNNING:
- if (clock == 244)
- {
- clock = 0;
- if((Cart.MapperChip.FDS_Get4025() & 0x2) == 0x2)
- {
- DiskAddress += 625; // Just doing what Neshawk does here... Basically fast forwarding until DiskAddress reaches the end?
- }
- else if ((Cart.MapperChip.FDS_Get4025() & 0x4) == 0x4)
- {
- // reading
- byte ShiftBit = (byte)((Disk[DiskAddress] >> (DiskAddressFine)) & 1);
-
- if (lookingForEndOfGap && (Cart.MapperChip.FDS_Get4025() & 0x10) == 0)
- {
- if (ShiftBit == 1)
- {
- // we found the end of the gap! :tada:
- lookingForEndOfGap = false;
- DiskAddressFine = 0;
- DiskAddress++;
- }
- else
- {
- DiskAddressFine++;
- if (DiskAddressFine == 8)
- {
- DiskAddressFine = 0;
- DiskAddress++;
- }
- }
- }
- else
- {
- ShiftRegister >>= 1;
- ShiftRegister |= (byte)(ShiftBit*0x80);
- DiskAddressFine++;
- if (DiskAddressFine == 8)
- {
- DiskAddressFine = 0;
- DiskAddress++;
-
- ShiftRegisterLatch = ShiftRegister;
- // disk drive is ready.
- // raise the byte transfer flag!
- Status_ByteTransferFlag = true;
- Cart.MapperChip.FDS_ByteTransferFlag(); // Trigger an IRQ if $4025.7 is set.
- if (Cart.Emu.IRQ_LevelDetector)
- {
- // debugging, put breakpoint here:
- }
- }
- }
- }
- else
- {
- DiskAddressFine = 0;
- }
- if (DiskAddress >= Disk.Length)
- {
- CurrentState = RamAdapterState.RESET;
- }
- }
- break;
- case RamAdapterState.RESET:
- case RamAdapterState.INSERTING:
- if (clock == 2140000)
- {
- clock = 0;
- DiskAddress = 0;
- CurrentState = RamAdapterState.IDLE;
- }
- break;
- case RamAdapterState.SPINUP:
- if (clock == 4280000)
- {
- clock = 0;
- CurrentState = RamAdapterState.RUNNING;
- }
- break;
- case RamAdapterState.IDLE:
- clock = 0;
- break;
- }
-
-
- }
-
-
- public void InsertDisk(string filepath)
- {
- Disk = File.ReadAllBytes(filepath); // Reads the file from the provided file path, and stores every byte into an array.
- Disk = FixFDSDiskSide(Disk);
- CurrentState = RamAdapterState.INSERTING;
- }
-
- private byte[] FixFDSDiskSide(byte[] disk)
- {
- // I haven't found any documentation on this stuff, so I'm just copying what Neshawk does.
- // TODO: Learn about this?
-
- MemoryStream ms = new MemoryStream(disk, false);
- BinaryReader br = new BinaryReader(ms);
- MemoryStream ret = new MemoryStream();
-
- byte[] header = br.ReadBytes(56);
- byte[] compare = { 0x01, 0x2a, 0x4e, 0x49, 0x4e, 0x54, 0x45, 0x4e, 0x44, 0x4f, 0x2d, 0x48, 0x56, 0x43, 0x2a };
- for (int i = 0; i < compare.Length; i++)
- {
- if (compare[i] != header[i])
- throw new Exception("Corrupt FDS block 1");
- }
- WriteBlock(ret, header, 3537);
-
- byte[] numfileblock = br.ReadBytes(2);
- if (numfileblock[0] != 0x02)
- {
- throw new Exception("Corrupt FDS block 2");
- }
- int numfiles = numfileblock[1];
- WriteBlock(ret, numfileblock, 122);
-
- for (int i = 0; i < numfiles; i++)
- {
- byte[] fileheader = br.ReadBytes(16);
- if (fileheader[0] != 0x03)
- {
- // Instead of exceptions, display strong warnings
- Console.WriteLine("WARNING: INVALID FILE, BLOCK 3 ERROR");
- //throw new Exception("Corrupt FDS block 3");
- }
- int filesize = fileheader[13] + fileheader[14] * 256;
-
- byte[] file = br.ReadBytes(filesize + 1);
- if (file[0] != 0x04)
- {
- Console.WriteLine("WARNING: INVALID FILE, BLOCK 4 ERROR");
- //throw new Exception("Corrupt FDS block 4");
- }
-
- WriteBlock(ret, fileheader, 122);
- WriteBlock(ret, file, 122);
- }
-
- ret.Close();
- byte[] tmp = ret.GetBuffer(); // don't care too much about actual "length" since extra is all 0
- Array.Resize(ref tmp, 65500); // might truncate
- return tmp;
- }
-
- private static void WriteBlock(Stream dest, byte[] data, int pregap)
- {
- for (int i = 0; i < pregap - 1; i++)
- dest.WriteByte(0);
- ushort crc = 0;
- dest.WriteByte(0x80); // end of gap marker
- crc = CCITT_8(crc, 0x80);
- for (int i = 0; i < data.Length; i++)
- {
- dest.WriteByte(data[i]);
- crc = CCITT_8(crc, data[i]);
- }
- dest.WriteByte((byte)(crc & 0xff));
- dest.WriteByte((byte)(crc >> 8));
- }
- private static ushort CCITT_8(ushort crc, byte b)
- {
- for (int i = 0; i < 8; i++)
- {
- int bit = (b >> i) & 1;
- crc = CCITT(crc, bit);
- }
- return crc;
- }
- private static ushort CCITT(ushort crc, int bit)
- {
- int bitc = crc & 1;
- crc >>= 1;
- if ((bitc ^ bit) != 0)
- crc ^= 0x8408;
- return crc;
- }
- }
-
-
- public class Emulator
- {
-
- public Cartridge Cart; // The idea behind this emulator is that this value could be changed at any time if you so desire.
- public byte PPUClock; // Counts down from 4. When it's 0, a PPU cycle occurs.
- public byte CPUClock; // Counts down from 12. When it's 0, a CPU cycle occurs.
- public byte MasterClock; // Counts up every master clock cycle. Resets at 24.
-
- public byte CICClock; // Seperate from the Master clock, we have the clock of the CIC chip.
- public bool ResetMode; // If the CIC chip was tripped, the console will reset approximately every second.
- public int ResetModeCounter; // Counter to reset teh console after the correct amount of time has passed.
-
- public bool[] SeventyTwoPinConnector; // The 72 pin connector.
- public bool[] ConnectorPinFloating; // Toggle which pins are disconnected.
- /* The actual pinout is 1-indexed, but I don't care, I'm zero-indexing this lad.
- * Key:
- * -- Power and Ground
- * -> From console to cartridge
- * <- From cartridge to console
- * <> I/O between both cartridge and console.
- *
- * 00 -- GND
- * 01 -> CPU A11
- * 02 -> CPU A10
- * 03 -> CPU A9
- * 04 -> CPU A8
- * 05 -> CPU A7
- * 06 -> CPU A6
- * 07 -> CPU A5
- * 08 -> CPU A4
- * 09 -> CPU A3
- * 10 -> CPU A2
- * 11 -> CPU A1
- * 12 -> CPU A0
- * 13 -> CPU R/W
- * 14 <- /IRQ
- * 15 EXP 0 (unused)
- * 16 EXP 1 (unused)
- * 17 EXP 2 (unused)
- * 18 EXP 3 (unused)
- * 19 EXP 4 (unused)
- * 20 -> PPU /RD
- * 21 <- CIRAM A10
- * 22 -> PPU A6
- * 23 -> PPU A5
- * 24 -> PPU A4
- * 25 -> PPU A3
- * 26 -> PPU A2
- * 27 -> PPU A1
- * 28 -> PPU A0
- * 29 <> PPU D0
- * 30 <> PPU D1
- * 31 <> PPU D2
- * 32 <> PPU D3
- * 33 -> CIC toPak
- * 34 <- CIC toMB
- * 35 -- +5V
- * 36 -> System CLK
- * 37 -> M2
- * 38 -> CPU A12
- * 39 -> CPU A13
- * 40 -> CPU A14
- * 41 <> CPU D7
- * 42 <> CPU D6
- * 43 <> CPU D5
- * 44 <> CPU D4
- * 45 <> CPU D3
- * 46 <> CPU D2
- * 47 <> CPU D1
- * 48 <> CPU D0
- * 49 -> /ROMSEL
- * 50 EXP 9 (unused)
- * 51 EXP 8 (unused)
- * 52 EXP 7 (unused)
- * 53 EXP 6 (unused)
- * 54 EXP 5 (unused)
- * 55 -> PPU /WR
- * 56 <- CIRAM /CE
- * 57 -> PPU /A13
- * 58 -> PPU A7
- * 59 -> PPU A8
- * 60 -> PPU A9
- * 61 -> PPU A11 (Not a typo, A11 and A10 are counterintuitively arranged)
- * 62 -> PPU A10 (Not a typo, A11 and A10 are counterintuitively arranged)
- * 63 -> PPU A12
- * 64 -> PPU A13
- * 65 <> PPU D7
- * 66 <> PPU D6
- * 67 <> PPU D5
- * 68 <> PPU D4
- * 69 -> CIC +RST
- * 70 -> CIC CLK
- * 71 -- GND
- *
- */
-
- public byte APUAlignment; // at power on or reset, is this a get/put, and how long until the DMC DMA?
-
- public bool APU_PutCycle = false; // The APU needs to know if this is a "get" or "put" cycle.
-
- public byte[] OAM = new byte[0x100]; // Object Attribute Memory is 256 bytes.
- public byte[] OAM2 = new byte[32]; // Secondary OAM is specifically the 8 objects being rendered on the current scanline.
- public byte SecondaryOAMSize = 0; // This is a count of how many objects are currently in secondary OAM.
- public byte OAM2Address = 0; // During sprite evaluation, the current SecondaryOAM Address is used to track what byte is set of a given dot.
- public byte SpriteEvaluationTick = 0; // During sprite evaluation, there's a switch statement that determines what to do on a given dot. This determines which action to take.
- public bool OAMAddressOverflowedDuringSpriteEvaluation = false; // If the OAM address overflows during sprite evaluation, there's a few bugs that can occur.
-
- public byte[] RAM = new byte[0x800]; // There are 0x800 bytes of RAM
- public byte[] VRAM = new byte[0x800]; // There are 0x800 bytes of VRAM
- public byte[] PaletteRAM = new byte[0x20]; // there are 0x20 bytes of palette RAM
-
- public ushort programCounter = 0; // The PC. What address is currently being executed?
- public byte opCode = 0; // The first CPU cycle of an instruction will read the opcode. This determines how the rest of the cycles will behave.
-
- public int totalCycles; // For debugging. This is just a count of how many CPU cycles have occurred since the console booted up.
-
- public byte stackPointer = 0x00; // The Stack pointer is used during pushing/popping values with the stack. This determines which address will be read or written to.
-
- public bool flag_Carry; // The Carry flag is used in BCC and BCS instructions, and is set when the result of an operation over/underflows.
- public bool flag_Zero; // The Zero flag is used in BNE and BEQ instructions, and is set when the result of an operation is zero.
- public bool flag_Interrupt; // The Interrupt suppression flag will suppress IRQ's.
- public bool flag_Decimal; // The NES doesn't use this flag.
- public bool flag_Overflow; // The Carry flag is used in BVC and BVS instructions, and is set when the result of an operation over/underflows and the sign of the result is the same as the value before the operation.
- public bool flag_Negative; // The Zero flag is used in BPL and BMI instructions, and is set when the result of an operation is negative. (bit 7 is set)
- byte status = 0; // This is a byte representation of all the flags.
- public byte A = 0; // The Accumulator, or "A Register"
- public byte X = 0; // The X Register
- public byte Y = 0; // The Y Register
- public byte H = 0; // The High byte of the target address. A couple unofficial instructions use this value.
- public bool IgnoreH; // However, with a well-timed DMA, the H register isn't actually part of the equation on some of those.
- public byte dataBus = 0; // The Data Bus.
- public byte internalBus = 0; // The Data Bus (internal to address $4015)
-
- public ushort addressBus = 0;// The Address Bus. "Where are we reading/writing"
- public byte specialBus = 0; // The Special Bus is used in certain instructions. (The special bus is mostly used in half-CPU-cycles connecting the various registers to the alu)
- public byte dl = 0; // Data Latch. This holds values between CPU cycles that are used in later cycles within an instruction.
-
-
- public byte operationCycle = 0; // This tracks what cycle of a given instruction is being emulated. Cycle 0 fetches the opcode, and all cycles after that have specific logic depending on which cycle needs emulated next.
- public bool CPU_SYNC = false;
- public ushort temporaryAddress; // I use this to temporarily modify the value of the address bus for some if statements. This is mostly for checking if the low byte under/over flows.
-
-
- public static uint[] NesPalInts = {
- // each uint represents the ARGB components of a color.
- // there's 64 colors, but this is also how I implement specific values for the PPU's emphasis bits.
- // default palette:
- 0xFF656565, 0xFF002A84, 0xFF1513A2, 0xFF3A019E, 0xFF59007A, 0xFF6A003E, 0xFF680800, 0xFF531D00, 0xFF323400, 0xFF0D4600, 0xFF004F00, 0xFF004C09, 0xFF003F4B, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFAEAEAE, 0xFF175FD6, 0xFF4341FF, 0xFF7529FA, 0xFF9E1DCA, 0xFFB4207B, 0xFFB13322, 0xFF964E00, 0xFF6A6C00, 0xFF398400, 0xFF0F9000, 0xFF008D33, 0xFF007B8C, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFFEFFFF, 0xFF66AFFF, 0xFF9390FF, 0xFFC578FF, 0xFFEE6CFF, 0xFFFF6FCA, 0xFFFF8271, 0xFFE69E25, 0xFFBABC00, 0xFF88D501, 0xFF5EE132, 0xFF47DD82, 0xFF4ACBDC, 0xFF4E4E4E, 0xFF000000, 0xFF000000,
- 0xFFFEFFFF, 0xFFC0DEFF, 0xFFD2D1FF, 0xFFE7C7FF, 0xFFF8C2FF, 0xFFFFC3E9, 0xFFFFCBC4, 0xFFF5D7A5, 0xFFE2E394, 0xFFCEED96, 0xFFBCF2AA, 0xFFB3F1CB, 0xFFB4E9F0, 0xFFB6B6B6, 0xFF000000, 0xFF000000,
- // emphasize red:
- 0xFF66423E, 0xFF000D58, 0xFF150075, 0xFF380075, 0xFF560058, 0xFF670027, 0xFF680000, 0xFF530D00, 0xFF341E00, 0xFF102B00, 0xFF003000, 0xFF002B00, 0xFF001C24, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFAF7E78, 0xFF19379A, 0xFF4320C1, 0xFF720FC1, 0xFF9A089A, 0xFFB10F59, 0xFFB2220F, 0xFF963700, 0xFF6C4D00, 0xFF3D5F00, 0xFF166500, 0xFF005F0C, 0xFF004B55, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFFFC0B8, 0xFF6878DB, 0xFF9361FF, 0xFFC24FFF, 0xFFEA49DB, 0xFFFF4F99, 0xFFFF634E, 0xFFE77808, 0xFFBC8F00, 0xFF8DA000, 0xFF65A708, 0xFF4DA04A, 0xFF4C8D95, 0xFF4F2F2B, 0xFF000000, 0xFF000000,
- 0xFFFFC0B8, 0xFFC1A2C6, 0xFFD399D6, 0xFFE792D6, 0xFFF78FC6, 0xFFFF92AB, 0xFFFF9A8C, 0xFFF6A26F, 0xFFE4AC5F, 0xFFD1B35F, 0xFFC0B66F, 0xFFB7B38B, 0xFFB6ABA9, 0xFFB7857E, 0xFF000000, 0xFF000000,
- // emphasize green:
- 0xFF395D2C, 0xFF002452, 0xFF000D6A, 0xFF140064, 0xFF2D0041, 0xFF3E0010, 0xFF3F0300, 0xFF301800, 0xFF162F00, 0xFF004200, 0xFF004C00, 0xFF004700, 0xFF003924, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF71A360, 0xFF005691, 0xFF1939B1, 0xFF4020A9, 0xFF61127B, 0xFF78183A, 0xFF792C00, 0xFF654800, 0xFF426600, 0xFF1B7E00, 0xFF008D00, 0xFF00860A, 0xFF007254, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFAEF099, 0xFF32A3CB, 0xFF5684EB, 0xFF7E6BE3, 0xFF9E5DB5, 0xFFB66472, 0xFFB77728, 0xFFA39400, 0xFF7FB200, 0xFF57CB00, 0xFF37D900, 0xFF1FD342, 0xFF1EBF8D, 0xFF27471C, 0xFF000000, 0xFF000000,
- 0xFFAEF099, 0xFF7BD0AD, 0xFF8AC3BA, 0xFF9AB9B7, 0xFFA8B3A4, 0xFFB1B689, 0xFFB2BE6A, 0xFFAACA50, 0xFF9BD643, 0xFF8BE146, 0xFF7DE65A, 0xFF74E475, 0xFF73DC94, 0xFF77AA65, 0xFF000000, 0xFF000000,
- // emphasize red + green:
- 0xFF3F3F25, 0xFF000B46, 0xFF00005D, 0xFF18005A, 0xFF2F003F, 0xFF40000E, 0xFF410000, 0xFF320A00, 0xFF191A00, 0xFF002800, 0xFF002F00, 0xFF002A00, 0xFF001B1C, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF797A55, 0xFF003581, 0xFF201F9F, 0xFF450D9C, 0xFF640478, 0xFF7B0A36, 0xFF7C1E00, 0xFF683200, 0xFF474900, 0xFF225B00, 0xFF036400, 0xFF005D00, 0xFF004A4A, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFBABB8B, 0xFF3E75B7, 0xFF605ED6, 0xFF854CD2, 0xFFA443AE, 0xFFBB4A6C, 0xFFBD5D21, 0xFFA87200, 0xFF878900, 0xFF619B00, 0xFF42A400, 0xFF2B9D34, 0xFF2A8A7F, 0xFF2C2D15, 0xFF000000, 0xFF000000,
- 0xFFBABB8B, 0xFF879E9D, 0xFF9595AA, 0xFFA48DA8, 0xFFB18999, 0xFFBB8C7E, 0xFFBB945F, 0xFFB39D48, 0xFFA5A63B, 0xFF96AE3D, 0xFF89B14C, 0xFF7FAF67, 0xFF7FA686, 0xFF80805A, 0xFF000000, 0xFF000000,
- // emphasize blue:
- 0xFF47477C, 0xFF001A8C, 0xFF0B0AA9, 0xFF2900A3, 0xFF410081, 0xFF4D004A, 0xFF49000D, 0xFF340400, 0xFF141500, 0xFF002800, 0xFF003300, 0xFF00331B, 0xFF002A58, 0xFF000000, 0xFF00000A, 0xFF00000A,
- 0xFF8584CD, 0xFF0B49E2, 0xFF3533FF, 0xFF5D1AFF, 0xFF7D0CD4, 0xFF8D0B8B, 0xFF86173A, 0xFF6B2C00, 0xFF414200, 0xFF195B00, 0xFF006904, 0xFF006A4C, 0xFF005E9E, 0xFF00000A, 0xFF00000A, 0xFF00000A,
- 0xFFC9C8FF, 0xFF4E8CFF, 0xFF7876FF, 0xFFA05CFF, 0xFFC14EFF, 0xFFD14DE4, 0xFFCB5A92, 0xFFAF6E4C, 0xFF848525, 0xFF5C9E2D, 0xFF3BAD5B, 0xFF2BADA5, 0xFF32A1F7, 0xFF343362, 0xFF00000A, 0xFF00000A,
- 0xFFC9C8FF, 0xFF96AFFF, 0xFFA8A6FF, 0xFFB89BFF, 0xFFC696FF, 0xFFCC95FF, 0xFFCA9AEA, 0xFFBEA3CD, 0xFFACACBD, 0xFF9CB7C0, 0xFF8FBDD3, 0xFF88BDF2, 0xFF8BB8FF, 0xFF8B8AD6, 0xFF00000A, 0xFF00000A,
- // emphasize red + blue:
- 0xFF46344C, 0xFF00085C, 0xFF0B007A, 0xFF260077, 0xFF3D005C, 0xFF4A0030, 0xFF480000, 0xFF340000, 0xFF140F00, 0xFF001D00, 0xFF002400, 0xFF002200, 0xFF001829, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF846B8C, 0xFF0A30A1, 0xFF3419C8, 0xFF5907C5, 0xFF7800A1, 0xFF880166, 0xFF860E23, 0xFF6B2300, 0xFF403900, 0xFF1C4C00, 0xFF005400, 0xFF00521A, 0xFF00445C, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFC7A7D2, 0xFF4C6BE8, 0xFF7754FF, 0xFF9C42FF, 0xFFBB39E7, 0xFFCC3CAB, 0xFFCA4968, 0xFFAE5E23, 0xFF837500, 0xFF5E8700, 0xFF3F9023, 0xFF2E8E5F, 0xFF3080A2, 0xFF332338, 0xFF000000, 0xFF000000,
- 0xFFC7A7D2, 0xFF948EDB, 0xFFA685EB, 0xFFB57DEA, 0xFFC27ADB, 0xFFC97BC2, 0xFFC880A7, 0xFFBD898A, 0xFFAB927A, 0xFF9C9A7B, 0xFF8F9D8A, 0xFF889CA3, 0xFF8997BE, 0xFF8A7093, 0xFF000000, 0xFF000000,
- // emphasize green + blue:
- 0xFF304144, 0xFF00155A, 0xFF000471, 0xFF11006B, 0xFF2A0049, 0xFF36001C, 0xFF350000, 0xFF250300, 0xFF0C1300, 0xFF002600, 0xFF003100, 0xFF002F00, 0xFF002531, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF647D80, 0xFF00429E, 0xFF152CBC, 0xFF3C13B4, 0xFF5C0586, 0xFF6D074B, 0xFF6B1509, 0xFF572900, 0xFF364000, 0xFF0E5900, 0xFF006700, 0xFF006424, 0xFF005766, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF9EBEC3, 0xFF2D83E1, 0xFF4E6CFF, 0xFF7653F8, 0xFF9745C9, 0xFFA7478D, 0xFFA5554A, 0xFF916A12, 0xFF6F8100, 0xFF479A00, 0xFF27A82A, 0xFF16A566, 0xFF1898A9, 0xFF1F2E30, 0xFF000000, 0xFF000000,
- 0xFF9EBEC3, 0xFF6FA6CF, 0xFF7D9CDC, 0xFF8E92D8, 0xFF9B8CC5, 0xFFA28DAD, 0xFFA19391, 0xFF999C7A, 0xFF8BA56D, 0xFF7AAF70, 0xFF6DB584, 0xFF66B49C, 0xFF67AEB8, 0xFF6A8386, 0xFF000000, 0xFF000000,
- // emphasize red + green + blue:
- 0xFF343434, 0xFF00084B, 0xFF000061, 0xFF14005F, 0xFF2B0044, 0xFF380017, 0xFF360000, 0xFF270000, 0xFF0E0F00, 0xFF001D00, 0xFF002400, 0xFF002200, 0xFF001721, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFF6A6A6A, 0xFF003088, 0xFF1B19A7, 0xFF4007A3, 0xFF5F007F, 0xFF6F0144, 0xFF6D0E02, 0xFF592300, 0xFF383900, 0xFF134B00, 0xFF005400, 0xFF00520F, 0xFF004451, 0xFF000000, 0xFF000000, 0xFF000000,
- 0xFFA6A6A6, 0xFF356BC5, 0xFF5654E3, 0xFF7B42E0, 0xFF9B39BB, 0xFFAB3C80, 0xFFA9493D, 0xFF955E04, 0xFF737500, 0xFF4E8700, 0xFF2F900E, 0xFF1E8E4A, 0xFF20808D, 0xFF232323, 0xFF000000, 0xFF000000,
- 0xFFA6A6A6, 0xFF788EB3, 0xFF8585C0, 0xFF957DBE, 0xFFA279AF, 0xFFA87A96, 0xFFA8807B, 0xFF9F8964, 0xFF919257, 0xFF829A59, 0xFF759D68, 0xFF6E9C80, 0xFF6F979C, 0xFF707070, 0xFF000000, 0xFF000000,
- // colorburst
- 0xFF010900
- };
-
- int chosenColor; // During screen rendering, this value is the index into the color array.
- public DirectBitmap Screen = new DirectBitmap(256, 240); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster.
- public DirectBitmap NTSCScreen = new DirectBitmap(256 * 8, 240); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster.
- public DirectBitmap BorderedScreen = new DirectBitmap(341, 262); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster.
- public DirectBitmap BorderedNTSCScreen = new DirectBitmap(341 * 8, 262); // This uses a class called "DirectBitmap". It's pretty much just the same as Bitmap, but I don't need to unlock/lock the bits, so it's faster.
-
- //Debugging
- public bool Logging; // If set, the tracelogger will record all instructions ran.
- public bool LoggingPPU;
- public StringBuilder DebugLog; // This is where the tracelogger is recording.
-
- public Emulator() // The instantiator for this class
- {
- RAM = new byte[0x800];
- A = 0; // The A, X, and Y registers are all initialized with 0 when the console boots up.
- X = 0;
- Y = 0;
- VRAM = new byte[0x800];
- OAM = new byte[0x100];
- OAM2 = new byte[32];
- for (int oam2_init = 0; oam2_init < OAM2.Length; oam2_init++)
- {
- OAM2[oam2_init] = 0xFF;
- }
-
- SeventyTwoPinConnector = new bool[72];
- ConnectorPinFloating = new bool[72];
- // set up RAM and PPU RAM Pattern
- int i = 0;
- while (i < 0x800)
- {
- int j = i & 0x2;
- bool swap = (i & 0x1F) >= 0x10;
- if (j < 0x2 == !swap)
- {
- VRAM[i] = 0xF0;
- RAM[i] = 0xF0;
- }
- else
- {
- VRAM[i] = 0x0F;
- RAM[i] = 0x0F;
- }
- i++;
- }
-
- bool BlarggPalette = false; // There's a PPU test cartridge that expects a very specific palette when you power on the console.
- if (BlarggPalette)
- {
- //use the palette that Blargg's NES uses
- PaletteRAM[0x00] = 0x09;
- PaletteRAM[0x01] = 0x01;
- PaletteRAM[0x02] = 0x00;
- PaletteRAM[0x03] = 0x01;
- PaletteRAM[0x04] = 0x00;
- PaletteRAM[0x05] = 0x02;
- PaletteRAM[0x06] = 0x02;
- PaletteRAM[0x07] = 0x0D;
- PaletteRAM[0x08] = 0x08;
- PaletteRAM[0x09] = 0x10;
- PaletteRAM[0x0A] = 0x08;
- PaletteRAM[0x0B] = 0x24;
- PaletteRAM[0x0C] = 0x00;
- PaletteRAM[0x0D] = 0x00;
- PaletteRAM[0x0E] = 0x04;
- PaletteRAM[0x0F] = 0x2C;
- PaletteRAM[0x10] = 0x09;
- PaletteRAM[0x11] = 0x01;
- PaletteRAM[0x12] = 0x34;
- PaletteRAM[0x13] = 0x03;
- PaletteRAM[0x14] = 0x00;
- PaletteRAM[0x15] = 0x04;
- PaletteRAM[0x16] = 0x00;
- PaletteRAM[0x17] = 0x14;
- PaletteRAM[0x18] = 0x08;
- PaletteRAM[0x19] = 0x3A;
- PaletteRAM[0x1A] = 0x00;
- PaletteRAM[0x1B] = 0x02;
- PaletteRAM[0x1C] = 0x00;
- PaletteRAM[0x1D] = 0x20;
- PaletteRAM[0x1E] = 0x2C;
- PaletteRAM[0x1F] = 0x08;
- }
- else // Except my actual console has a different palette than Blargg, so I use this palette instead.
- {
- // use the palette that my NES uses
- PaletteRAM[0x00] = 0x00;
- PaletteRAM[0x01] = 0x00;
- PaletteRAM[0x02] = 0x28;
- PaletteRAM[0x03] = 0x00;
- PaletteRAM[0x04] = 0x00;
- PaletteRAM[0x05] = 0x08;
- PaletteRAM[0x06] = 0x00;
- PaletteRAM[0x07] = 0x00;
- PaletteRAM[0x08] = 0x00;
- PaletteRAM[0x09] = 0x01;
- PaletteRAM[0x0A] = 0x01;
- PaletteRAM[0x0B] = 0x20;
- PaletteRAM[0x0C] = 0x00;
- PaletteRAM[0x0D] = 0x08;
- PaletteRAM[0x0E] = 0x00;
- PaletteRAM[0x0F] = 0x02;
- PaletteRAM[0x10] = 0x00;
- PaletteRAM[0x11] = 0x00;
- PaletteRAM[0x12] = 0x00;
- PaletteRAM[0x13] = 0x00;
- PaletteRAM[0x14] = 0x00;
- PaletteRAM[0x15] = 0x02;
- PaletteRAM[0x16] = 0x21;
- PaletteRAM[0x17] = 0x00;
- PaletteRAM[0x18] = 0x00;
- PaletteRAM[0x19] = 0x00;
- PaletteRAM[0x1A] = 0x00;
- PaletteRAM[0x1B] = 0x00;
- PaletteRAM[0x1C] = 0x00;
- PaletteRAM[0x1D] = 0x10;
- PaletteRAM[0x1E] = 0x00;
- PaletteRAM[0x1F] = 0x00;
- }
-
- programCounter = 0xFFFF; // Technically, this value is nondeterministic. It also doesn't matter where it is, as it will be initialized in the RESET instruction.
- PPU_Scanline = 0; // The PPU begins on dot 0 of scanline 0
- PPU_Dot = 0;
-
- PPU_OddFrame = true; // And this is technically considered an "odd" frame when it comes to even/odd frame timing.
-
- APU_DMC_SampleAddress = 0xC000;
- APU_DMC_AddressCounter = 0xC000;
-
- APU_DMC_SampleLength = 1;
- APU_DMC_ShifterBitsRemaining = 8;
-
- switch (APUAlignment & 4)
- {
- default:
- case 0:
- {
- APU_ChannelTimer_DMC = 1022;
- APU_PutCycle = true;
- }
- break;
- case 1:
- {
- APU_ChannelTimer_DMC = 1022;
- APU_PutCycle = false;
- }
- break;
- case 2:
- {
- APU_ChannelTimer_DMC = 1020;
- APU_PutCycle = true;
- }
- break;
- case 3:
- {
- APU_ChannelTimer_DMC = 1020;
- APU_PutCycle = false;
- }
- break;
- }
-
- DoReset = true; // This is used to force the first instruction at power on to be the RESET instruction.
- PPU_RESET = false; // I'm not even 100% certain my console has this behavior. I'll set it to false for now.
- }
-
- public bool PPU_RESET;
-
- public void ResetButton()
- {
- ResetMode = false; // Clear the CIC reset mode.
- ResetModeCounter = 0;
- Reset();
- }
-
- // when pressing the reset button, this function runs
- public void Reset()
- {
- // The A, X, and Y registers are unchanged through reset.
- // most flags go unchanged as well, but the I flag is set to 1
- flag_Interrupt = true;
- // Triangle phase gets reset, though I'm not yet emulating audio.
- APU_DMC_Output &= 1;
- // All the bits of $4015 are cleared
- APU_Status_DMCInterrupt = false;
- APU_Status_FrameInterrupt = false;
- APU_Status_DelayedDMC = false;
- APU_Status_DMC = false;
- APU_Status_Noise = false;
- APU_Status_Triangle = false;
- APU_Status_Pulse2 = false;
- APU_Status_Pulse1 = false;
- APU_DMC_BytesRemaining = 0;
- APU_LengthCounter_Noise = 0;
- APU_LengthCounter_Triangle = 0;
- APU_LengthCounter_Pulse2 = 0;
- APU_LengthCounter_Pulse1 = 0;
- APU_Framecounter = 0; // reset the frame counter
-
- // PPU registers
- PPUControl_NMIEnabled = false;
- PPUControlIncrementMode32 = false;
- PPU_Spritex16 = false;
- PPU_PatternSelect_Sprites = false;
- PPU_PatternSelect_Background = false;
- PPU_t = 0;
-
- PPU_Mask_Greyscale = false;
- PPU_Mask_EmphasizeRed = false;
- PPU_Mask_EmphasizeGreen = false;
- PPU_Mask_EmphasizeBlue = false;
- PPU_Mask_8PxShowBackground = false;
- PPU_Mask_8PxShowSprites = false;
- PPU_Mask_ShowBackground = false;
- PPU_Mask_ShowSprites = false;
-
- PPU_Update2005Delay = 0;
- PPU_FineXScroll = 0;
-
- //$2006 is unchanged
-
- PPU_ReadBuffer = 0;
- PPU_OddFrame = false;
-
- PPU_Dot = 0;
- PPU_Scanline = 0;
-
- DoDMCDMA = false;
- DoOAMDMA = false;
- operationCycle = 0;
- CPU_SYNC = true;
-
- switch (APUAlignment & 4)
- {
- default:
- case 0:
- {
- APU_ChannelTimer_DMC = 1022;
- APU_PutCycle = true;
- }
- break;
- case 1:
- {
- APU_ChannelTimer_DMC = 1022;
- APU_PutCycle = false;
- }
- break;
- case 2:
- {
- APU_ChannelTimer_DMC = 1020;
- APU_PutCycle = true;
- }
- break;
- case 3:
- {
- APU_ChannelTimer_DMC = 1020;
- APU_PutCycle = false;
- }
- break;
- }
-
- DoReset = true;
- PPU_RESET = false; // I'm not even 100% certain my console has this behavior. I'll set it to false for now.
- // in theory, the CPU/PPU clock would be given random values. Let's just assume no changes.
- }
-
- public bool CPU_Read; // DMC DMA Has some specific behavior depending on if the CPU is currently reading or writing. DMA Halting fails / DMA $2007 bug.
-
-
- // The BRK instruction is reused in the IRQ, NMI, and RESET logic. These bools are used both to start the instruction, and also to make sure the correct logic is used.
- public bool DoBRK; // Set if the opcode is 00
- public bool DoNMI; // Set if a Non Maskable Interrupt is occurring
- public bool DoIRQ; // Set if an Interrupt Request is occurring
-
- public bool DoReset; // Set when resetting the console, or power on.
- public bool DoOAMDMA; // If set, the Object Acctribute Memory's Direct Memory Access will occur.
- public bool FirstCycleOfOAMDMA; // The first cycle caa behave differently.
- public bool DoDMCDMA; // If set, the Delta Modulation Channel's Direct Memory Access will occur.
- public byte DMCDMADelay; // There's actually a slight delay between the audio chip preparing the DMA, and the CPU actually running it.
- public byte CannotRunDMCDMARightNow = 0;
-
- public byte DMAPage; // When running an OAM DMA, this is used to determine which "page" to read bytes from. Typically, this is page 2 (address $200 through $2FF)
- public byte DMAAddress; // While this DMA runs, this value is incremented until it overflows.
-
- public bool FrameAdvance_ReachedVBlank; // For debugging. If frame advancing, this is set when VBlank occurs.
-
- public bool APU_ControllerPortsStrobing; // Set to true/false depending on the value written to $4016. When true, the buttons pressed are recorded in the shift registers.
- public bool APU_ControllerPortsStrobed; // This bool prevents strobing from rushing through the TAS input log.
- // This gets set to false if the controllers are unstrobed, or if the controller ports are read.
-
- public byte ControllerPort1; // The buttons currently pressed on controller 1. These are in the "A, B, Select, Start, Up, Down, Left, Right" order.
- public byte ControllerPort2; // The buttons currently pressed on controller 2. These are in the "A, B, Select, Start, Up, Down, Left, Right" order.
- public byte ControllerShiftRegister1; // Controllers are read 1 bit at a time. First the A Button is read, then B, and so on.
- public byte ControllerShiftRegister2; // Whenever the shift register is read, all the bits are shifted to the left, and a '1' replaces bit 0.
- public byte Controller1ShiftCounter; // Subsequent CPU cycles reading from $4016 do not update the shift register.
- public byte Controller2ShiftCounter; // Subsequent CPU cycles reading from $4017 do not update the shift register.
-
- public bool LagFrame; // True if the controller port was not strobed in a frame.
- public bool TASTimelineClockFiltering; // Primarily used in the TASTimeline if you are using subframe Inputs.
-
- public void _CoreFrameAdvance()
- {
- // If we're running this emulator 1 frame at a time, this waits until VBlank and then returns.
- FrameAdvance_ReachedVBlank = false;
- LagFrame = true; // Many emulators detect "lag frames" by checking if the controller ports were strobed during this frame.
- while (!FrameAdvance_ReachedVBlank)
- {
- _EmulatorCore();
- }
- }
-
- public int CycleCountForCycleTAS = 0; // If we're running a intercycle cart swapping TAS, we need to keep track of which cycle we're on.
- public void _CoreCycleAdvance()
- {
- // this runs 12 master clock cycles, or 1 CPU cycle.
- int i = 0;
- while (i < 12)
- {
- _EmulatorCore();
- i++;
- }
- CycleCountForCycleTAS++;
- }
-
- public void _EmulatorCore()
- {
- // counters count down to 0, run the appropriate chip's logic, and the counter is reset.
- // If multiple counters read 0 at the same time, there's an order of events.
- // The order of events:
- // CPU
- // PPU
- // APU
-
- if (CPUClock == 12)
- {
- CPUClock = 0; // there is 1 CPU cycle for every 12 master clock cycles
-
- if(ResetMode)
- {
- ResetModeCounter++;
- if(ResetModeCounter == 1477840) // Approximately how long it takes for the CIC chip to reset the console. Keep in mind, this value lowers as the temperature increases.
- {
- ResetModeCounter = 0;
- Reset(); // Reset via the CIC, not the reset button.
- }
- }
-
- _6502(); // This is where I run the CPU
- totalCycles++; // for debugging mostly
- Cart.MapperChip.CPUClock(); // If the mapper chip does every cpu cycle... (see FME-7)
- }
- if (CPUClock == 4)
- {
- NMILine |= PPUControl_NMIEnabled && PPUStatus_VBlank;
- if (operationCycle == 0 && !(PPUStatus_VBlank && PPUControl_NMIEnabled))
- {
- NMILine = false;
- }
- }
- if (CPUClock == 7) //M2 going low.
- {
- IRQLine = IRQ_LevelDetector;
- if (APU_Status_FrameInterrupt && !APU_FrameCounterInhibitIRQ)
- {
- IRQ_LevelDetector = true; // if the APU frame counter flag is never cleared, you will get another IRQ when the I flag is cleared.
- }
- Cart.MapperChip.CPUClockRise(); // If the mapper chip does something when M2 rises... (see MMC3)
- }
- if (PPUClock == 4)
- {
- PPUClock = 0; // there is 1 PPU cycle for every 12 master clock cycles
-
- _EmulatePPU();
- if (PPUBus != 0)
- {
- DecayPPUDataBus();
- }
- }
- if (PPUClock == 2)
- {
- _EmulateHalfPPU();
- }
-
-
- if (CPUClock == 0)
- {
-
- _EmulateAPU();
- APU_PutCycle = !APU_PutCycle;
-
- // the APU is actually clocked every 24 master clock cycles.
- // yet there's a lot of timing that happens every cpu cycle anyway??
- // If the timing needs to be exactly n and a half APU cycles, then I'll just multiply the numbers by 2 and clock this twice as fast.
- }
-
- if(CICClock == 5) // The CIC clock is NOT tied to the master clock. It would actually clock approximately every 5.369318 master clock cycles, but I'm not going to worry about that for now.
- {
- CICClock = 0;
- if (!Cart.MapperChip.CheckCIC())
- {
- ResetMode = true;
- }
- }
-
- // Increment the clocks.
- PPUClock++;
- CPUClock++;
- CICClock++;
- if (Cart.FDS != null)
- {
- Cart.FDS.Clock();
- }
- }
-
- public void EmulateUntilEndOfRead()
- {
- // this is used during reads from some ppu registers.
- // run 1.75 ppu cycles. (the actual duty cycle here would result in 1 and 7/8 ppu cycles, but my emulator doesn't worry about half-master-clock-cycles.
- for (int i = 0; i < 7; i++)
- {
- _EmulatorCore();
- }
- }
-
- public void EmulateNMasterClockCycles(int n)
- {
- // This does run the risk of recursion, so don't use a value of 12 or more with this.
- for (int i = 0; i < n; i++)
- {
- _EmulatorCore();
- }
- }
-
- // Audio Processing Unit Variables //
-
- // APU Status is at address $4015
- public bool APU_Status_DMCInterrupt; // Bit 7 of $4015
- public bool APU_Status_FrameInterrupt;// Bit 6 of $4015
- public bool APU_Status_DMC; // Bit 5 of $4015
- public bool APU_Status_DelayedDMC; // Bit 5 of $4015, but with a slight delay.
- public bool APU_Status_Noise; // Bit 3 of $4015
- public bool APU_Status_Triangle; // Bit 2 of $4015
- public bool APU_Status_Pulse2; // Bit 1 of $4015
- public bool APU_Status_Pulse1; // Bit 0 of $4015
-
- public bool Clearing_APU_FrameInterrupt;
-
-
- public byte APU_DelayedDMC4015; // When writing to $4015, there's a 3 or 4 cycle delay between the APU actually changing this value.
- public bool APU_ImplicitAbortDMC4015; // An edge case of the DMC DMA, where regardless of the buffer being empty, there will be a 1-cycle DMA that gets aborted 2 cycles after the load DMA ends
- public bool APU_SetImplicitAbortDMC4015;// This is used to make that happen.
-
- public byte[] APU_Register = new byte[0x10]; // Instead of making a series of variables, I made an array here for some reason.
-
- public bool APU_FrameCounterMode; // Bit 7 of $4017 : Determines if the APU frame counter is using the 4 step or 5 step modes.
- public bool APU_FrameCounterInhibitIRQ; // Bit 6 of $4017 : If set, prevents the APU from creating IRQ's
-
- public byte APU_FrameCounterReset = 0xFF; // When resetting the APU Frame counter by writing to address $4017, there's a 3 (or 4) CPU cycle delay. (3 if it's an even cpu cycle, 4 if odd.)
- public ushort APU_Framecounter = 0; // Increments every APU cycle. Since there are events that happen at half-step intervals, I actually increment this every CPU cycle and multiplied all intervals by 2.
- public bool APU_QuarterFrameClock = false;// This is clocked approximately 4 times a frame, depending on the frame counter mode.
- public bool APU_HalfFrameClock = false; // This is clocked approximately twice a frame, depending on the frame counter mode.
-
- public bool APU_Envelope_StartFlag = false;
- public bool APU_Envelope_DividerClock = false;
- public byte APU_Envelope_DecayLevel = 0;
-
- public byte APU_LengthCounter_Pulse1 = 0; // The length counter for the APU's Pulse 1 channel.
- public byte APU_LengthCounter_Pulse2 = 0; // The length counter for the APU's Pulse 2 channel.
- public byte APU_LengthCounter_Triangle = 0; // The length counter for the APU's Triangle channel.
- public byte APU_LengthCounter_Noise = 0; // The length counter for the APU's Noise channel.
-
- // When a length counter's reloaded value is set by writing to $4003, $4007, $400B, or $400F, this LookUp Table is used to determine the length based on the value written.
- public static readonly byte[] APU_LengthCounterLUT = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 };
-
- public bool APU_LengthCounter_HaltPulse1 = false; // set if Bit 5 of $4000 is 1
- public bool APU_LengthCounter_HaltPulse2 = false; // set if Bit 5 of $4004 is 1
- public bool APU_LengthCounter_HaltTriangle = false; // set if Bit 7 of $4008 is 1
- public bool APU_LengthCounter_HaltNoise = false; // set if Bit 5 of $400C is 1
-
- public bool APU_LengthCounter_ReloadPulse1 = false; // When writing to $4003 (if the pulse 1 channel is enabled) this is set to true. The value is reloaded in the next APU cycle.
- public bool APU_LengthCounter_ReloadPulse2 = false; // When writing to $4007 (if the pulse 2 channel is enabled) this is set to true. The value is reloaded in the next APU cycle.
- public bool APU_LengthCounter_ReloadTriangle = false;// When writing to $400B (if the triangle channel is enabled) this is set to true. The value is reloaded in the next APU cycle.
- public bool APU_LengthCounter_ReloadNoise = false; // When writing to $400F (if the noise channel is enabled) this is set to true. The value is reloaded in the next APU cycle.
-
- public byte APU_LengthCounter_ReloadValuePulse1 = 0; // When the pulse 1 channel is reloaded, the length counter will be set to this value. Modified by writing to $4003.
- public byte APU_LengthCounter_ReloadValuePulse2 = 0; // When the pulse 2 channel is reloaded, the length counter will be set to this value. Modified by writing to $4007.
- public byte APU_LengthCounter_ReloadValueTriangle = 0;// When the triangle channel is reloaded, the length counter will be set to this value. Modified by writing to $400B.
- public byte APU_LengthCounter_ReloadValueNoise = 0; // When the noise channel is reloaded, the length counter will be set to this value. Modified by writing to $400F.
-
- public ushort APU_ChannelTimer_Pulse1 = 0; // Decrements every "get" cycle.
- public ushort APU_ChannelTimer_Pulse2 = 0; // Decrements every "get" cycle.
- public ushort APU_ChannelTimer_Triangle = 0;// Decrements every CPU cycle.
- public ushort APU_ChannelTimer_Noise = 0; // Decrements every "get" cycle.
- public ushort APU_ChannelTimer_DMC = 0; // Decrements every CPU cycle.
-
-
- // $4010
- public bool APU_DMC_EnableIRQ = false; // Will the DMC create IRQ's? Set by writing to address $4010
- public bool APU_DMC_Loop = false; // Will DPCM samples loop?
- public ushort APU_DMC_Rate = 428; // The default sample rate is the slowest.
- // LookUp Table for how many CPU cycles are between each bit of the DPCM sample being played. (8 bits per byte, so to calculate how many cycles there are between each DMA, multiply these numbers by 8)
- public static readonly ushort[] APU_DMCRateLUT = { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 };
-
- // $4011 (and DPCM stuff)
- public byte APU_DMC_Output; // Directly writing here (Address $4011) will set the DMC output. This is how you play PCM audio.
-
- // $4012
- public ushort APU_DMC_SampleAddress = 0xC000; // Where the DPCM sample is being read from.
-
- // $4013
- public ushort APU_DMC_SampleLength = 0; // How many bytes are being played in this DPCM sample? (multiplied by 64, and add 1)
-
- public ushort APU_DMC_BytesRemaining = 0; // How many bytes are left in the sample. When a sample starts or loops, this is set to APU_DMC_SampleLength.
- public byte APU_DMC_Buffer = 0; // The value that goes into the shift register.
- public ushort APU_DMC_AddressCounter = 0xC000; // What byte is fetched in the next DMA for DPCM audio? When a sample starts or loops, this is set to APU_DMC_SampleAddress.
- public byte APU_DMC_Shifter = 0; // The 8 bits of the sample that were fetched from the DMA.
- public byte APU_DMC_ShifterBitsRemaining = 8; // This tracks how many bits are left before needing to run another DMA
- public bool DPCM_Up; // If the next bit of the DPCM sample is a 1, the output goes up. Otherwise it goes down.
-
- public bool APU_Silent = true; // If the APU is not making any noise, this is set.
-
- void _EmulateAPU()
- {
- // This runs every 12 master clock cycles, though has different logic for even/odd CPU cycles.
- if (!APU_ControllerPortsStrobing)
- {
- if (Controller1ShiftCounter > 0)
- {
- Controller1ShiftCounter--;
- if (Controller1ShiftCounter == 0)
- {
- ControllerShiftRegister1 <<= 1;
- ControllerShiftRegister1 |= 1;
- }
- }
- if (Controller2ShiftCounter > 0)
- {
- Controller2ShiftCounter--;
- if (Controller2ShiftCounter == 0)
- {
- ControllerShiftRegister2 <<= 1;
- ControllerShiftRegister2 |= 1;
- }
- }
- }
- else
- {
- Controller1ShiftCounter = 0;
- Controller2ShiftCounter = 0;
- }
-
- if (!APU_PutCycle)
- {
- // If this is a get cycle, transitioning to a put cycle.
-
- // controller reading is handled here in the APU chip.
-
- // If a 1 was written to $4016, we are strobing the controller.
- if (APU_ControllerPortsStrobing)
- {
- if (!APU_ControllerPortsStrobed)
- {
- LagFrame = false;
- APU_ControllerPortsStrobed = true;
- if (TASTimelineClockFiltering)
- {
- FrameAdvance_ReachedVBlank = true; // Obviously this isn't actually VBlank, but we want to stop emulating here anyway.
- }
- // this will be reset to false if:
- // 1.) the controllers are un-strobed. Ready for the next strobe.
- // 2.) the controller ports are read, while still strobed. This allows data to be streamed in through the A button.
-
- if (TAS_ReadingTAS) // This is specifically how I load inputs from a TAS, and has nothing to do with actual NES behavior.
- {
- if (TAS_InputSequenceIndex < TAS_InputLog.Length)
- {
- ControllerPort1 = (byte)(TAS_InputLog[TAS_InputSequenceIndex] & 0xFF);
- ControllerPort2 = (byte)((TAS_InputLog[TAS_InputSequenceIndex] & 0xFF00) >> 8);
- }
- else // if the TAS has ended, only provide 0 as the inputs.
- {
- ControllerPort1 = 0;
- ControllerPort2 = 0;
- }
- if (ClockFiltering)
- {
- if (TAS_InputSequenceIndex > 0 && TAS_InputSequenceIndex < TAS_ResetLog.Length && TAS_ResetLog[TAS_InputSequenceIndex])
- {
- ResetButton();
- }
- TAS_InputSequenceIndex++; // Instead of using 1 input per frame, this just advances to the next input
- }
-
- }
- // this sets up the shift registers with the value of the controller ports.
- // If not set by the TAS, these are probably set outside this script in the script for the form.
- ControllerShiftRegister1 = ControllerPort1;
- ControllerShiftRegister2 = ControllerPort2;
- }
- }
- else
- {
- APU_ControllerPortsStrobed = false;
- }
-
- // clock timers
- APU_ChannelTimer_Pulse1--; // every APU GET cycle.
- APU_ChannelTimer_Pulse2--;
- APU_ChannelTimer_Noise--;
-
-
- //this happens whether a sample is playing or not
- APU_ChannelTimer_DMC--;
- APU_ChannelTimer_DMC--; // the table is in CPU cycles, but the count is in APU cycles
- if (APU_ChannelTimer_DMC == 0)
- {
- APU_ChannelTimer_DMC = APU_DMC_Rate;
- DPCM_Up = (APU_DMC_Shifter & 1) == 1;
- if (DPCM_Up)
- {
- if (APU_DMC_Output <= 125) // this is 7 bit, and cannot go above 127
- {
- APU_DMC_Output += 2;
- }
- }
- else
- {
- if (APU_DMC_Output >= 2) // this is 7 bit, and cannot go below 0
- {
- APU_DMC_Output -= 2;
- }
- }
- APU_DMC_Shifter >>= 1; // shift the bits in the shift register
- APU_DMC_ShifterBitsRemaining--; // and decrement the "bits remaining" counter.
- if (APU_DMC_ShifterBitsRemaining == 0) // If there are no bits left,
- {
- APU_DMC_ShifterBitsRemaining = 8; // it's time for a DMC DMA!
-
- if (APU_DMC_BytesRemaining > 0 || APU_SetImplicitAbortDMC4015)
- {
- if (!DoDMCDMA && CannotRunDMCDMARightNow != 2)
- {
- // if playing a sample:
- DoDMCDMA = true;
- DMCDMA_Halt = true;
- }
- if (APU_SetImplicitAbortDMC4015)
- {
- APU_ImplicitAbortDMC4015 = true; // check for weird DMA abort behavior
- APU_SetImplicitAbortDMC4015 = false;
- }
- APU_DMC_Shifter = APU_DMC_Buffer; // and set up the shifter with the new values.
- APU_Silent = false; // The APU is not silent.
-
- }
- else
- {
- APU_Silent = true;
- }
- }
- }
- if (CannotRunDMCDMARightNow > 0)
- {
- CannotRunDMCDMARightNow -= 2;
- }
- }
- else
- {
- // If this is a put cycle, transitioning to a get cycle.
-
- if (Clearing_APU_FrameInterrupt)
- {
- Clearing_APU_FrameInterrupt = false;
- APU_Status_FrameInterrupt = false;
- IRQ_LevelDetector = false;
- }
- // DMC load from 4015
- if (DMCDMADelay > 0)
- {
- DMCDMADelay--; // there's a small delay beetween the write occurring and the DMA beginning
- if (DMCDMADelay == 0 && !DoDMCDMA) // if the DMA is already happening because of the timer
- {
- DoDMCDMA = true;
- DMCDMA_Halt = true;
- APU_DMC_Shifter = APU_DMC_Buffer;
- APU_Silent = false;
- }
- }
- }
- if (APU_DelayedDMC4015 > 0)
- {
- APU_DelayedDMC4015--;
- if (APU_DelayedDMC4015 == 0)
- {
- APU_Status_DMC = APU_Status_DelayedDMC;
- if (!APU_Status_DMC)
- {
- APU_DMC_BytesRemaining = 0;
- }
- }
- }
-
- APU_ChannelTimer_Triangle--; // every CPU cycle.
-
- // clock sequencer
- if ((APU_FrameCounterReset & 0x80) == 0)
- {
- APU_FrameCounterReset--;
- if ((APU_FrameCounterReset & 0x80) != 0)
- {
- APU_Framecounter = 0;
- }
- }
-
- APU_Framecounter++;
-
- // We're clocking the APU twice as fast in order to get the frame counter timing to allow the 'half APU cycle' timing.
- // these numbers are just multiplied by 2.
-
- if (APU_FrameCounterMode)
- {
- // 5 step
- switch (APU_Framecounter)
- {
- default: break;
- case 7457:
- APU_QuarterFrameClock = true;
- break;
- case 14913:
- APU_QuarterFrameClock = true;
- APU_HalfFrameClock = true;
- break;
- case 22371:
- APU_QuarterFrameClock = true;
- break;
- case 29829:
- break;
- case 37281:
- APU_QuarterFrameClock = true;
- APU_HalfFrameClock = true;
- break;
- case 37282:
- APU_Framecounter = 0;
- break;
- }
- }
- else
- {
- // 4 step
- switch (APU_Framecounter)
- {
- default: break;
- case 7457:
- APU_QuarterFrameClock = true;
- break;
- case 14913:
- APU_QuarterFrameClock = true;
- APU_HalfFrameClock = true;
- break;
- case 22371:
- APU_QuarterFrameClock = true;
- break;
- case 29828:
- APU_Status_FrameInterrupt = true;
- break;
- case 29829:
- APU_QuarterFrameClock = true;
- APU_Status_FrameInterrupt = true;
- IRQ_LevelDetector |= !APU_FrameCounterInhibitIRQ;
- APU_HalfFrameClock = true;
- break;
- case 29830:
- APU_Status_FrameInterrupt = !APU_FrameCounterInhibitIRQ;
- IRQ_LevelDetector |= !APU_FrameCounterInhibitIRQ;
-
- APU_Framecounter = 0;
-
- break;
- }
-
- }
-
-
-
-
-
- // perform quarter frame / half frame stuff
-
- if (APU_QuarterFrameClock)
- {
- APU_QuarterFrameClock = false;
- if (APU_Envelope_StartFlag)
- {
- APU_Envelope_StartFlag = false;
- APU_Envelope_DecayLevel = 15;
-
- }
- else
- {
- APU_Envelope_DividerClock = true;
- }
- }
-
- if (APU_HalfFrameClock)
- {
- if (APU_LengthCounter_ReloadPulse1 && APU_LengthCounter_Pulse1 == 0) { APU_LengthCounter_Pulse1 = APU_LengthCounter_ReloadValuePulse1; } else { APU_LengthCounter_ReloadPulse1 = false; }
- if (APU_LengthCounter_ReloadPulse2 && APU_LengthCounter_Pulse2 == 0) { APU_LengthCounter_Pulse2 = APU_LengthCounter_ReloadValuePulse2; } else { APU_LengthCounter_ReloadPulse2 = false; }
- if (APU_LengthCounter_ReloadTriangle && APU_LengthCounter_Triangle == 0) { APU_LengthCounter_Triangle = APU_LengthCounter_ReloadValueTriangle; } else { APU_LengthCounter_ReloadTriangle = false; }
- if (APU_LengthCounter_ReloadNoise && APU_LengthCounter_Noise == 0) { APU_LengthCounter_Noise = APU_LengthCounter_ReloadValueNoise; } else { APU_LengthCounter_ReloadNoise = false; }
- APU_HalfFrameClock = false;
- // length counters and sweep
- if (!APU_Status_Pulse1) { APU_LengthCounter_Pulse1 = 0; }
- if (!APU_Status_Pulse2) { APU_LengthCounter_Pulse2 = 0; }
- if (!APU_Status_Triangle) { APU_LengthCounter_Triangle = 0; }
- if (!APU_Status_Noise) { APU_LengthCounter_Noise = 0; }
-
- if (APU_LengthCounter_Pulse1 != 0 && !APU_LengthCounter_HaltPulse1 && !APU_LengthCounter_ReloadPulse1)
- {
- APU_LengthCounter_Pulse1--;
- }
- if (APU_LengthCounter_Pulse2 != 0 && !APU_LengthCounter_HaltPulse2 && !APU_LengthCounter_ReloadPulse2)
- {
- APU_LengthCounter_Pulse2--;
- }
- if (APU_LengthCounter_Triangle != 0 && !APU_LengthCounter_HaltTriangle && !APU_LengthCounter_ReloadTriangle)
- {
- APU_LengthCounter_Triangle--;
- }
- if (APU_LengthCounter_Noise != 0 && !APU_LengthCounter_HaltNoise && !APU_LengthCounter_ReloadNoise)
- {
- APU_LengthCounter_Noise--;
- }
- }
- else
- {
- if (APU_LengthCounter_ReloadPulse1) { APU_LengthCounter_Pulse1 = APU_LengthCounter_ReloadValuePulse1; }
- if (APU_LengthCounter_ReloadPulse2) { APU_LengthCounter_Pulse2 = APU_LengthCounter_ReloadValuePulse2; }
- if (APU_LengthCounter_ReloadTriangle) { APU_LengthCounter_Triangle = APU_LengthCounter_ReloadValueTriangle; }
- if (APU_LengthCounter_ReloadNoise) { APU_LengthCounter_Noise = APU_LengthCounter_ReloadValueNoise; }
- APU_LengthCounter_ReloadPulse1 = false;
- APU_LengthCounter_ReloadPulse2 = false;
- APU_LengthCounter_ReloadTriangle = false;
- APU_LengthCounter_ReloadNoise = false;
- }
-
- APU_LengthCounter_HaltPulse1 = ((APU_Register[0] & 0x20) != 0);
- APU_LengthCounter_HaltPulse2 = ((APU_Register[4] & 0x20) != 0);
- APU_LengthCounter_HaltTriangle = ((APU_Register[8] & 0x80) != 0);
- APU_LengthCounter_HaltNoise = ((APU_Register[0xC] & 0x20) != 0);
-
-
-
- } // and that's it for the APU cycle
-
- // PPU variables
-
- public byte PPUBus; // The databus of the Picture Processing Unit
- public int[] PPUBusDecay = new int[8];
- const int PPUBusDecayConstant = 1786830; // 20 frames. Approximately how long it takes for the PPU bus to decay on my console.
- public byte PPUOAMAddress; // The address used to index into Object Attribute Memory
- public bool PPUStatus_VBlank; // This is set during Vblank, and cleared at the end, or if $2002 is read. This value can be read in address $2002
- public bool PPUStatus_PendingSpriteZeroHit; // If a sprite zero hit occurs, this is set. This toggles PPUStatus_SpriteZeroHit on the next half-ppu-cycle.
- public bool PPUStatus_PendingSpriteZeroHit2; // Actually theres a 1.5 dot delay on this one.
- public bool PPUStatus_SpriteZeroHit; // If a sprite zero hit occurs, this is set. This value can be read in address $2002
- public bool PPUStatus_SpriteZeroHit_Delayed;
- public bool PPUStatus_SpriteOverflow; // If a scanline had more than 8 objects in range, this is set. This value can be read in address $2002
- public bool PPUStatus_SpriteOverflow_Delayed;
-
- public bool PPU_VSET; // This line is high for half a ppu cycle at the start of scanline 241.
- public bool PPU_VSET_Latch1; // A latch used in the timing for the VBlank flag.
- public bool PPU_VSET_Latch2; // A latch used in the timing for the VBlank flag.
- public bool PPU_Read2002; // This clears the VBlank flag.
-
- bool PPU_Spritex16; // Are sprites using 8x8 mode, or 8x16 mode? Set by writing to $2000
-
- public ushort PPU_Scanline; // Which scanline is the PPU currently on
- public ushort PPU_Dot; // Which dot of the scanline is the PPU currently on
-
- public bool PPU_VRegisterChangedOutOfVBlank; // when changing the v register (Read write address) out of vblank, palettes can become corrupted
- public bool PPU_OAMCorruptionRenderingDisabledOutOfVBlank; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted
- public bool PPU_PendingOAMCorruption;// The corruption doesn't take place until rendering is re-enabled.
- public byte PPU_OAMCorruptionIndex; // The object that gets corrupted depends on when the data was corrupted
- // OAM corruption during OAM evaluation happens with the instant write to $2001 using the databus value. Other parts of sprite evaluation apparently do not.
- public bool PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted
- public bool PPU_OAMCorruptionRenderingEnabledOutOfVBlank; // If enabling rendering outside vblank, there are alignment specific effects.
- public bool PPU_OAMEvaluationCorruptionOddCycle; // If rendering is disabled during OAM evaluation, it matters if it was on an odd or even cycle.
- public bool PPU_OAMEvaluationObjectInRange; // If rendering is disabled during OAM evaluation, it matters if the most recent object evaluated was in vertical range of this scanline.
- public bool PPU_OAMEvaluationObjectInXRange; // If rendering is disabled during OAM evaluation, it matters if the most recent object evaluated was in vertical range of this scanline.
-
- public bool PPU_PaletteCorruptionRenderingDisabledOutOfVBlank; // When rendering is disabled on specific dots of visible scanlines, OAM data can become corrupted
-
-
- byte PPU_AttributeLatchRegister;
- ushort PPU_BackgroundAttributeShiftRegisterL; // 8 bit latch for the background tile attributes low bit plane.
- ushort PPU_BackgroundAttributeShiftRegisterH; // 8 bit latch register for the background tile attributes high bit plane.
- ushort PPU_BackgroundPatternShiftRegisterL; // 16 bit shift register for the background tile pattern low bit plane.
- ushort PPU_BackgroundPatternShiftRegisterH; // 16 bit shift register for the background tile pattern high bit plane.
-
- //TempPPUAddr
- public byte PPU_FineXScroll; // Set when writing to address $2005. 3 bits. This is up to a 7 pixel offset when rendering the screen.
-
- byte[] PPU_SpriteShiftRegisterL = new byte[8]; // 8 bit shift register for a sprite's low bit plane. Secondary OAM can have up to 8 object in it.
- byte[] PPU_SpriteShiftRegisterH = new byte[8]; // 8 bit shift register for a sprite's high bit plane. Secondary OAM can have up to 8 object in it.
-
- byte[] PPU_SpriteAttribute = new byte[8]; // Secondary OAM attribute values. Secondary OAM can have up to 8 objects in it.
- byte PPU_SpriteAttr;
- byte PPU_SpritePattern; // Secondary OAM pattern values. Secondary OAM can have up to 8 objects in it.
- byte[] PPU_SpriteXposition = new byte[8]; // Secondary OAM x positions. Secondary OAM can have up to 8 objects in it.
-
- byte[] PPU_SpriteShifterCounter = new byte[8]; // This counter tracks how long until the objects are drawn.
-
-
- bool PPU_NextScanlineContainsSpriteZero; // If this upcoming scanline contains sprite zero
- bool PPU_CurrentScanlineContainsSpriteZero; // if the sprite evaluation for this current scanline contained sprite zero. Used for Sprite Zero Hit detection.
-
- public byte PPU_SpritePatternL; // Temporary value used in sprite evaluation.
- public byte PPU_SpritePatternH; // Temporary value used in sprite evaluation.
-
-
- bool PPU_Mask_Greyscale; // Set by writing to $2001. If set, only use color 00, 10, 20, or 30 when drawing a pixel.
- bool PPU_Mask_8PxShowBackground; // Set by writing to $2001. If set, the background will be visible in the 8 left-most pixels of the screen.
- bool PPU_Mask_8PxShowSprites; // Set by writing to $2001. If set, the sprites will be visible in the 8 left-most pixels of the screen.
- bool PPU_Mask_ShowBackground; // Set by writing to $2001. If set, the background will be visible. Anything that requires rendering to be enabled will run, even if it doesn't involve the background.
- bool PPU_Mask_ShowSprites; // Set by writing to $2001. If set, the sprites will be visible. Anything that requires rendering to be enabled will run, even if it doesn't involve sprites.
- bool PPU_Mask_EmphasizeRed; // Set by writing to $2001. Adjusts the colors on screen to be a bit more red.
- bool PPU_Mask_EmphasizeGreen; // Set by writing to $2001. Adjusts the colors on screen to be a bit more green.
- bool PPU_Mask_EmphasizeBlue; // Set by writing to $2001. Adjusts the colors on screen to be a bit more blue.
-
- bool PPU_Mask_ShowBackground_Delayed; // Sprite evaluation has a 1 ppu cycle delay on checking if rendering is enabled.
- bool PPU_Mask_ShowSprites_Delayed; // Sprite evaluation has a 1 ppu cycle delay on checking if rendering is enabled.
- bool PPU_Mask_ShowBackground_Instant; // OAM evaluation will stop immediately if writing to $2001
- bool PPU_Mask_ShowSprites_Instant; // OAM evaluation will stop immediately if writing to $2001
- byte PPU_RenderingCounter; // Checks how many ppu cycles rendering has been enabled. NOT HOW THE CONSOLE ACTUALLY WORKS.
-
- byte PPU_LowBitPlane; // Temporary value used in background shift register preparation.
- byte PPU_HighBitPlane;// Temporary value used in background shift register preparation.
- byte PPU_Attribute; // Temporary value used in background shift register preparation.
-
- public ushort PPU_PatternAddressRegister_CHR; // PAR
- public ushort PPU_PatternAddressRegister_NT; // PAR
- public ushort PPU_PatternAddressRegister_AT; // PAR
- public ushort PPU_PAR_MUX; // PAR
-
- bool PPU_CanDetectSpriteZeroHit; // Only 1 sprite zero hit is allowed per frame. This gets set if a sprite zero hit occurs, and cleared at the end of vblank.
-
- public bool PPU_OddFrame; // Every other frame is 1 ppu cycle shorter.
-
- public byte DotColor; // The pixel output is delayed by 2 dots.
- public byte PrevDotColor; // This is the value from last cycle.
- public byte PrevPrevDotColor; // And this is from 2 cycles ago.
- public byte PrevPrevPrevDotColor; // And this is from 2 cycles ago.
- public int PrevPrevPrevPrevDotColor; // This is used with NTSC signal decoding.
- public byte PaletteRAMAddress;
- public bool ThisDotReadFromPaletteRAM;
-
-
- public bool NMI_PinsSignal; // I'm using this to detect the rising edge of $2000.7 and $2002.7
- public bool NMI_PreviousPinsSignal; // I'm using this to detect the rising edge of $2000.7 and $2002.7
- public bool IRQ_LevelDetector; // If set, it's time to run an IRQ whenever this is detected
- public bool NMILine; // Set to true if $2000.7 and $2002.7 are both set. This is checked during the second half od a CPU cycle.
- public bool IRQLine; // Set during phi2 to true if the IRQ level detector is low.
-
- public bool CopyV = false; // set by writes to $2006. If it occurs on the same dot the scroll values are naturally incremented, some bugs occur.
- bool SkippedPreRenderDot341 = false;
-
- void _EmulatePPU()
- {
-
- // When writing to ppu registers, there's a slight delay before resulting action is taken.
- // This delay can vary depending on the CPU/PPU alignment.
-
- // after writing to $2005, there is either a 1 or 2 cycle delay.
- if (PPU_Update2005Delay > 0)
- {
- PPU_Update2005Delay--;
- if (PPU_Update2005Delay == 0)
- {
- if (!PPUAddrLatch)
- {
- // if this is the first write to $2005
- PPU_FineXScroll = (byte)(PPU_Update2005Value & 7); // This updates the fine X scroll
- PPU_t = (ushort)((PPU_t & 0b0111111111100000) | (PPU_Update2005Value >> 3)); // as well as changing the 't' register.
- }
- else
- {
- // if this is the second write to $2005
- PPU_t = (ushort)((PPU_t & 0b0000110000011111) | (((PPU_Update2005Value & 0xF8) << 2) | ((PPU_Update2005Value & 7) << 12))); // this also writes to 't'
- }
- PPUAddrLatch = !PPUAddrLatch; // flip the latch
- }
- }
-
- // Updating the scroll registers during screen rendering
- if (PPU_Scanline < 240 || PPU_Scanline == 261)// if this is the pre-render line, or any line before vblank
- {
- if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_RenderingCounter >= 1)
- {
- if (PPU_Dot == 256) //The Y scroll is incremented on dot 256.
- {
- PPU_IncrementScrollY();
- }
- else if (PPU_Dot == 257) //The X scroll is reset on dot 257.
- {
- PPU_ResetXScroll();
- }
- if (PPU_Dot >= 280 && PPU_Dot <= 304 && PPU_Scanline == 261) //numbers from the nesdev wiki
- {
- PPU_ResetYScroll(); //The Y scroll is reset on every dot from 280 through 304 on the pre-render scanline.
- }
- }
- }
-
- // Increment the PPU dot
- PPU_Dot++;
- if (PPU_Dot > 340) // There are only 341 dots per scanline
- {
- PPU_Dot = 0; // reset the dot back to 0
- PPU_Scanline++; // and increment the scanline
- // Sprite zero hits rely on the previous scanline's sprite evaluation.
-
- if (PPU_Scanline > 261) // There are 262 scanlines in a frame.
- {
- PPU_Scanline = 0; // reset to scanline 0.
- }
- }
-
- if (PPU_Scanline == 241) // If this is the first scanline of VBLank
- {
- if (PPU_Dot == 0)
- {
- // If Address $2002 is read during the next ppu cycle, the PPU Status flags aren't set.
- // These variables are used to check if Address $2002 is read during the next ppu cycle.
- // I usually refer to this as the $2002 race condition.
- // The more proper term would be "Vblank/NMI flag suppression".
-
- // oh- and also if we're running a fm2 TAS file, due to FCEUX's incorrect timing of the first frame, I need to prevent this from being set just a few cycles after power on.
- if (!SyncFM2)
- {
- PPU_PendingVBlank = true;
- }
- else
- {
- SyncFM2 = false;
- }
- }
- if (PPU_Dot == 1)
- {
- PPU_RESET = false;
-
- // else, address $2002 was read on this ppu cycle. no VBlank flag.
- if (!PPU_ShowScreenBorders)
- {
- FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else.
- }
- if (!ClockFiltering) // specifically for TASing stuff. Increment the index for the input log.
- {
- if (TAS_ReadingTAS && TAS_InputSequenceIndex > 0 && TAS_InputSequenceIndex < TAS_ResetLog.Length && TAS_ResetLog[TAS_InputSequenceIndex])
- {
- ResetButton();
- }
- // If this was using "SubFrame", TAS_InputSequenceIndex is incremented whenever the controller is strobed.
- // Instead, I increment the index here at the start of vblank.
- TAS_InputSequenceIndex++;
- }
-
-
- }
-
- }
- else if (PPU_Scanline == 242 && PPU_Dot == 1)
- {
- if (PPU_ShowScreenBorders && !(PPU_DecodeSignal || PPU_ShowRawNTSCSignal)) // if we're showing the boarders, we need to wait for 2 more scanlines to render.
- {
- FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else.
- }
- }
- else if (PPU_Scanline == 260 && PPU_Dot == 340)
- {
- PPU_OddFrame = !PPU_OddFrame; // I guess this could happen on pretty much any cycle?
-
- }
- else if (PPU_Scanline == 261 && PPU_Dot == 1)
- {
- // On dot 1 of the pre-render scanline, all of these flags are cleared.
- // You might be looking at the results of my "$2002 Flag Clear Timing" test from the AccuracyCoin test ROM and thinking, "Hold on. That can't be right!"
- // Well, it is. You see, PPUStatus_VBlank is read at the beginning of the read, while PPUStatus_SpriteZeroHit and PPUStatus_SpriteOverflow are read at the end of the read.
- // This means about 1 and 7/8 ppu cycles pass between the start of the read and the end, so thes values are seemingly cleared on different cycles, but they are in-fact cleared at the same time.
- PPUStatus_VBlank = false;
- PPU_CanDetectSpriteZeroHit = true;
- PPUStatus_SpriteZeroHit = false;
- PPUStatus_SpriteOverflow = false;
- PPUStatus_SpriteZeroHit_Delayed = false;
- }
-
- else if (PPU_Scanline == 261 && PPU_Dot == 0)
- {
- if (PPU_ShowScreenBorders && (PPU_DecodeSignal || PPU_ShowRawNTSCSignal)) // if we're showing the boarders, we need to wait for scanline 0.
- {
- FrameAdvance_ReachedVBlank = true; // Emulator specific stuff. Used for frame advancing to detect the frame has ended, and nothing else.
- }
- }
-
- PPU_VSET_Latch1 = !PPU_VSET; // VSET_Latch1 is latched with /VSET on the first half of a PPU cycle.
- if (PPU_VSET && !PPU_VSET_Latch2)
- {
- PPUStatus_VBlank = true;
- }
- if (PPU_Read2002)
- {
- PPU_Read2002 = false;
- PPUStatus_VBlank = false;
- }
-
- PPUStatus_SpriteOverflow_Delayed = PPUStatus_SpriteOverflow;
-
- Cart.MapperChip.PPUClock(); // If the mapper chip does something every ppu clock... (See MMC3)
-
- if (PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites))
- {
- if (PPU_Scanline == 0 && PPU_Dot == 0)
- {
- // On every other frame, dot 0 of scanline 0 is skipped.
- // this cycle is technically (0,0), but this still makes the Nametable fetch during the last cycle of the pre-render line
- PPU_Dot++;
- SkippedPreRenderDot341 = true;
- }
- }
-
-
- if (PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline == 0 && PPU_Dot == 2)
- {
- SkippedPreRenderDot341 = false; // This variable is used for some esoteric business on dot 1 of scanline 0.
- }
- // Okay, now that we're updated all those flags, let's render stuff to the screen!
-
- // let's establish the order of operations.
- // Sprite evaluation
- // then calculate the color for the next dot.
-
- //but to complicate things, the delay after writing to $2001 happens between those 2 steps, and also on a specific alignment, this delay is 1 cycle longer for sprite evaluation.
-
- // If this is NOT phase 1
- if ((CPUClock & 3) != 3)
- {
- // sprite evaluation has a 1 ppu cycle delay before recognizing these flags were set or cleared.
- PPU_Mask_ShowBackground_Delayed = PPU_Mask_ShowBackground;
- PPU_Mask_ShowSprites_Delayed = PPU_Mask_ShowSprites;
- }
-
- PPU_DATA_StateMachine();
- PPU_OAMLatch = PPU_OAMBuffer;
-
- // TODO: Does this use a state machine like $2007?
- CopyV = false;
- if (PPU_Update2006Delay > 0)
- {
- PPU_Update2006Delay--; // this counts down,
- if (PPU_Update2006Delay == 0) // and when it reaches zero
- {
- ushort temp_Prev_V = PPU_v;
- CopyV = true;
- PPU_v = PPU_t; // the PPU_ReadWriteAddress is updated!
- PPU_AddressBus = PPU_v; // This value is the same thing.
- if ((temp_Prev_V & 0x3FFF) >= 0x3F00 && (PPU_AddressBus & 0x3FFF) < 0x3F00) // Palette corruption check. Are we leaving Palette ram?
- {
- if ((PPU_Scanline < 240) && PPU_Dot <= 256) // if this dot is visible
- {
- if ((temp_Prev_V & 0xF) != 0) // also, Palette corruption only happens if the previous address did not end in a 0
- {
- PPU_VRegisterChangedOutOfVBlank = true;
- }
- }
- }
- }
- }
-
- if(OAM2ResetSignal > 0)
- {
- OAM2ResetSignal--; // This will never reach zero in this routine. See _EmulateHalfPPU()
- }
-
- if ((PPU_Scanline < 240 || PPU_Scanline == 261))// if this is the pre-render line, or any line before vblank
- {
- // Sprite evaluation
- if (PPU_Scanline < 241 || PPU_Scanline == 261)
- {
- PPU_Render_SpriteEvaluation(); // fill in secondary OAM, and set up various arrays of sprite properties.
-
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)
- {
- if (PPU_Dot == 63 || PPU_Dot == 255 || PPU_Dot == 339)
- {
- OAM2ResetSignal = 3; // 3 half-dots.
- }
- }
- }
- }
-
- if ((CPUClock & 3) == 3)
- {
- // on phase 1,
- // sprite evaluation has a 2 ppu cycle delay before recognizing these flags were set or cleared.
- PPU_Mask_ShowBackground_Delayed = PPU_Mask_ShowBackground;
- PPU_Mask_ShowSprites_Delayed = PPU_Mask_ShowSprites;
- }
-
- if (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites)
- {
- PPU_RenderingCounter = 0;
-
- PPU_AddressBus = PPU_v; // the address bus is always v when rendering is disabled.
- // TODO: Is this occuring one ppu cycle too late???
- // I specifically moved this here (outside of the following if statements) because it broke nes_reset_state_detect-letters.nes on alignment 1.
- }
- else if(PPU_RenderingCounter < 5)
- {
- PPU_RenderingCounter++;
- }
-
- // after sprite evaluation, but before screen rendering...
- if (PPU_Update2001Delay > 0) // if we wrote to 2001 recently
- {
- PPU_Update2001Delay--;
- if (PPU_Update2001Delay == 0) // if we've waited enough cycles, apply the changes
- {
- PPU_Mask_8PxShowBackground = (PPU_Update2001Value & 0x02) != 0;
- PPU_Mask_8PxShowSprites = (PPU_Update2001Value & 0x04) != 0;
- PPU_Mask_ShowBackground = (PPU_Update2001Value & 0x08) != 0;
- PPU_Mask_ShowSprites = (PPU_Update2001Value & 0x10) != 0;
-
- PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change
- PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites;
- }
- }
- if (PPU_Update2001OAMCorruptionDelay > 0) // if we wrote to 2001 recently
- {
- PPU_Update2001OAMCorruptionDelay--;
- if (PPU_Update2001OAMCorruptionDelay == 0) // if we've waited enough cycles, apply the changes
- {
- if (PPU_WasRenderingBefore2001Write && (PPU_Update2001Value & 0x08) == 0 && (PPU_Update2001Value & 0x10) == 0)
- {
- if ((PPU_Scanline < 240 || PPU_Scanline == 261)) // if this is the pre-render line, or any line before vblank
- {
- if (!PPU_PendingOAMCorruption) // due to OAM corruption occurring inside OAM evaluation before this even occurs, make sure OAM isn't already corrupt
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = true;
- }
- }
- }
- }
- }
- if (PPU_Update2001EmphasisBitsDelay > 0)
- {
- PPU_Update2001EmphasisBitsDelay--;
- if (PPU_Update2001EmphasisBitsDelay == 0)
- {
- PPU_Mask_Greyscale = (PPU_Update2001Value & 0x01) != 0;
- PPU_Mask_EmphasizeRed = (PPU_Update2001Value & 0x20) != 0;
- PPU_Mask_EmphasizeGreen = (PPU_Update2001Value & 0x40) != 0;
- PPU_Mask_EmphasizeBlue = (PPU_Update2001Value & 0x80) != 0;
- }
- }
-
- PrevPrevPrevDotColor = PrevPrevDotColor; // Drawing a color to the screen has a 3(?) ppu cycle delay between deciding the color, and drawing it.
- PrevPrevDotColor = PrevDotColor;
- PrevDotColor = DotColor; // These variables here just record the color, and swap them through these variables so it can be used 3 cycles after it was chosen.
- if ((PPU_Scanline < 240 || PPU_Scanline == 261) || (PPU_Scanline == 240 && PPU_Dot == 0))// if this is the pre-render line, or any line before vblank, or dot 0 of scanline 240
- {
- if ((PPU_Dot >= 1 && PPU_Dot <= 256) || (PPU_Dot >= 321 && PPU_Dot <= 336)) // if this is a visible pixel, or preparing the start of next scanline
- {
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) // if rendering background or sprites
- {
- PPU_Render_ShiftRegistersAndBitPlanes(); // update shift registers for the background.
- }
- }
- else if (PPU_Dot >= 337 || PPU_Dot == 0)
- {
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)) // if rendering background or sprites
- {
- PPU_Render_ShiftRegistersAndBitPlanes_DummyNT();
- }
- }
-
- if ((PPU_Dot > 0 && PPU_Dot <= 256)) // if this is a visible pixel, or preparing the start of next scanline
- {
- if (PPU_Scanline < 241)
- {
- PPU_Render_CalculatePixel(false); // this determines the color of the pixel being drawn.
- }
- UpdateSpriteShiftRegisters(); // update shift registers for the sprites.
- }
- else
- {
- if (PPU_ShowScreenBorders) // Draw the pixels in the boarder too.
- {
- PPU_Render_CalculatePixel(true); // this determines the color of the pixel being drawn.
- }
- }
-
-
- if (!PPU_ShowScreenBorders)
- {
- DrawToScreen();
-
- if (PPU_DecodeSignal)
- {
- if (PPU_Dot == 1 && PPU_Scanline == 0)
- {
- ntsc_signal_of_dot_0 = ntsc_signal;
- ntsc_signal_of_dot_0 += 4;
- ntsc_signal_of_dot_0 %= 12;
- }
- else if((PPU_Dot == 0))
- {
- ntsc_signal_of_dot_0 = ntsc_signal;
- }
- }
-
- if (PPU_DecodeSignal && (PPU_Dot == 0) && PPU_Scanline < 241)
- {
- chosenColor = PaletteRAM[0x00] & 0x3F;
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- // emphasis bits
- int emphasis = 0;
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- PrevPrevPrevPrevDotColor = chosenColor | emphasis; // set up samples for dot 1
- PPU_SignalDecode(chosenColor | emphasis);
- }
- if (PPU_DecodeSignal && (PPU_Dot == 260) && PPU_Scanline < 241)
- {
- PPU_SignalDecode(PrevPrevPrevPrevDotColor);
- }
- else if (PPU_DecodeSignal && (PPU_Dot == 261) && PPU_Scanline < 241)
- {
- RenderNTSCScanline();
- }
- }
- }
- else if (PPU_ShowScreenBorders)
- {
- PPU_Render_CalculatePixel(true); // this determines the color of the pixel being drawn.
- }
- if (PPU_ShowScreenBorders)
- {
- DrawToBorderedScreen();
- }
- ThisDotReadFromPaletteRAM = false;
-
- PPU_DATA_StateMachine2();
-
- if (PPU_DecodeSignal)
- {
- ntsc_signal += 8;
- ntsc_signal %= 12;
- }
-
- if (Logging && LoggingPPU)
- {
- Debug_PPU();
- }
-
- } // and that's all for the PPU cycle!
-
- byte FetchVideoMemory()
- {
- Cart.MapperChip.Connector_SetUpPPUAddressPins();
- Cart.MapperChip.Connector_SetUpPPUDataPins((byte)PPU_AddressBus); // If AccessPPU() doesn't update the pins, then we get back the address bus.
- Cart.MapperChip.Connector_CheckCIRAM();
- bool CE = !SeventyTwoPinConnector[56] && !ConnectorPinFloating[56] && !ConnectorPinFloating[57];
-
- // Always attempt to read from the cartridge. The data pins would not be updated if reading from the nametable.
- Cart.MapperChip.Connector_PPU_RW(PPU_READ, PPU_WRITE);
- Cart.MapperChip.AccessPPU();
- byte t = Cart.MapperChip.Connector_ReadPPUDataPins((byte)PPU_AddressBus);
-
- if (CE)
- {
- // We're reading from on-console VRAM, not the cartridge.
- // NOTE: In theory you could trigger bus conflicts with this. I'm currently just assuming the bus is free.
- ushort Address = (ushort)((PPU_AddressBus & 0x300) | PPU_OctalLatch);
- Address |= (ushort)(SeventyTwoPinConnector[21] ? 0x400 : 0);
- t = VRAM[Address];
- }
-
- PPU_AddressBus &= 0xFF00;
- PPU_AddressBus |= t;
-
- return t;
- }
-
- void WriteVideoMemory(byte input)
- {
- Cart.MapperChip.Connector_SetUpPPUAddressPins();
- Cart.MapperChip.Connector_CheckCIRAM();
- bool CE = !SeventyTwoPinConnector[56] && !ConnectorPinFloating[56] && !ConnectorPinFloating[57];
-
- if ((PPU_AddressBus & 0x3FFF) >= 0x3F00)
- {
- PaletteRAM[PPU_AddressBus & (((PPU_AddressBus & 0x3) == 0) ? 0x0F : 0x1F)] = input;
- }
- else if (CE)
- {
- // We're writing to on-console VRAM, not the cartridge.
- // NOTE: In theory you could trigger bus conflicts with this. I'm currently just assuming the bus is free.
- ushort Address = (ushort)((PPU_AddressBus & 0x300) | PPU_OctalLatch);
- Address |= (ushort)(SeventyTwoPinConnector[21] ? 0x400 : 0);
- VRAM[Address] = input;
- }
- else
- {
- Cart.MapperChip.Connector_SetUpPPUDataPins(input);
- Cart.MapperChip.Connector_PPU_RW(PPU_READ, PPU_WRITE);
- Cart.MapperChip.AccessPPU();
- }
- }
-
- bool PPUActiveForShiftRegisterUpdate;
- void _EmulateHalfPPU()
- {
- // Oh boy, it's time for half PPU cycles.
- if ((PPU_Scanline < 240 || PPU_Scanline == 261))// if this is the pre-render line, or any line before vblank
- {
- if ((PPU_Dot > 0 && PPU_Dot <= 257) || (PPU_Dot > 320 && PPU_Dot <= 336)) // if this is a visible pixel, or preparing the start of next scanline
- {
- if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) // if rendering background or sprites
- {
- PPU_UpdateBackgroundShiftRegisters(); // shift all the shift registers 1 bit
- }
- }
- }
-
- if(OAM2ResetSignal > 0)
- {
- OAM2ResetSignal--;
- if(OAM2ResetSignal == 0)
- {
- OAM2Address = 0;
- OAM2Overflowed = false;
- }
- }
-
- PPU_Render_CommitShiftRegistersAndBitPlanes();
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) || (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) // if rendering background or sprites
- {
- PPU_OAMBuffer = PPU_OAMBuffer_In; // Update the OAM Buffer.
- }
- PPU_VSET = false;
- if (PPU_PendingVBlank)
- {
- PPU_PendingVBlank = false;
- PPU_VSET = true;
- }
- // PPU_VSET_Latch1 gets inverted, and that becomes the state of PPU_VSET_Latch2
- PPU_VSET_Latch2 = !PPU_VSET_Latch1;
-
- PPUStatus_SpriteZeroHit_Delayed = PPUStatus_SpriteZeroHit;
- if (PPUStatus_PendingSpriteZeroHit2)
- {
- PPUStatus_PendingSpriteZeroHit2 = false;
- PPUStatus_SpriteZeroHit = true;
- }
- if (PPUStatus_PendingSpriteZeroHit)
- {
- PPUStatus_PendingSpriteZeroHit = false;
- PPUStatus_PendingSpriteZeroHit2 = true;
- }
-
- PPU_DATA_StateMachine_Half();
-
- }
-
- public bool PPU_2007_Read;
- public bool PPU_2007_Read_SR;
- public bool[] PPU_2007_Read_Latches = new bool[5];
- public bool PPU_2007_PD_RB;
- public bool PPU_2007_ReadALE;
- public bool PPU_2007_Read_H0_Latch;
- public bool PPU_2007_Read_XRB;
-
- public bool PPU_READ; // The lower 8 bits of the address bus are being used as data pins for a read.
-
- public bool PPU_2007_Write;
- public bool PPU_2007_Write_SR;
- public bool[] PPU_2007_Write_Latches = new bool[5];
- public bool PPU_2007_DB_PAR;
- public bool PPU_2007_WriteALE;
- public bool PPU_2007_TStep_Latch;
- public bool PPU_2007_TStep;
-
- public bool PPU_2007_BLNK_Latch;
- public bool PPU_2007_PaletteRAMEnable;
- public byte PPU_2007_WriteData;
-
- public bool PPU_WRITE; // The lower 8 bits of the address bus are being used as data pins for a write.
- void PPU_DATA_StateMachine()
- {
- bool BLNK = (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites) || (PPU_Scanline >= 240 && PPU_Scanline < 261);
- PPU_2007_BLNK_Latch = BLNK;
- bool H0_DASH = (PPU_Dot - 1 & 1) != 0;
-
- PPU_2007_PaletteRAMEnable = ((PPU_AddressBus & 0x3F00) == 0x3F00) && PPU_2007_BLNK_Latch;
- PPU_2007_Read_XRB = PPU_2007_Read && PPU_2007_PaletteRAMEnable;
-
- PPU_2007_Read_Latches[0] = PPU_2007_Read_SR;
- if(PPU_2007_Read)
- {
- PPU_2007_Read = false; // I put this in an if statement for easier debugging / breakpoint placement.
- }
- PPU_2007_Read_Latches[2] = !PPU_2007_Read_Latches[1];
- PPU_2007_Read_Latches[4] = !PPU_2007_Read_Latches[3];
- PPU_2007_PD_RB = PPU_2007_Read_Latches[4] && !PPU_2007_Read_Latches[2];
- PPU_2007_ReadALE = !PPU_2007_Read_Latches[4] && PPU_2007_Read_Latches[2];
- PPU_2007_Read_H0_Latch = (PPU_Dot - 1 & 1) != 0;
-
-
- PPU_READ = (PPU_2007_PD_RB || (!BLNK && PPU_2007_Read_H0_Latch)); // even ppu cycles outside of blanking always read. Also read if we are reading $2007.
-
- PPU_2007_Write_Latches[0] = PPU_2007_Write_SR;
- if (PPU_2007_Write)
- {
- PPU_2007_Write = false; // I put this in an if statement for easier debugging / breakpoint placement.
- }
- PPU_2007_Write_Latches[2] = !PPU_2007_Write_Latches[1];
- PPU_2007_Write_Latches[4] = !PPU_2007_Write_Latches[3];
- PPU_2007_WriteALE = !PPU_2007_Write_Latches[4] && PPU_2007_Write_Latches[2];
-
- PPU_2007_TStep_Latch = PPU_2007_DB_PAR;
-
- bool b = (!BLNK && !H0_DASH); // If you are on an even dot out of a blanking period
- PPU_ALE = (PPU_2007_ReadALE || PPU_2007_WriteALE || b);
-
- if ((PPU_2007_ReadALE || PPU_2007_WriteALE))
- {
- if (!PPU_READ) // TODO: this if statement doesn't seem to change the results of the 2007 stress test in any way.
- {
- PPU_AddressBus = PPU_v;
- PPU_OctalLatch = (byte)PPU_AddressBus;
- }
- }
- }
- void PPU_DATA_StateMachine2()
- {
-
- if (PPU_2007_PD_RB)
- {
- PPU_ReadBuffer = FetchVideoMemory();
- if (PPU_ALE)
- {
- PPU_OctalLatch = (byte)PPU_AddressBus;
- }
- }
- }
- void PPU_DATA_StateMachine_Half()
- {
- PPU_2007_TStep = (PPU_2007_TStep_Latch || PPU_2007_PD_RB);
- if (PPU_2007_TStep) // If this occurs inside PPU_DATA_StateMachine() instead, the timing is wrong, and this breaks SMB1's title screen.
- {
- if (!PPU_2007_BLNK_Latch)
- {
- PPU_IncrementScrollY();
- }
- else
- {
- PPU_v += (ushort)(PPUControlIncrementMode32 ? 32 : 1);
- PPU_v &= 0x7FFF;
- }
- }
-
- PPU_ALE = (PPU_2007_ReadALE || PPU_2007_WriteALE);
- if (PPU_2007_PD_RB)
- {
- PPU_ReadBuffer = FetchVideoMemory();
- if (PPU_ALE)
- {
- // pretty sure this can never happen, but keep it just in case.
- PPU_OctalLatch = (byte)PPU_AddressBus;
- }
- }
- PPU_2007_Read_Latches[1] = !PPU_2007_Read_Latches[0];
- PPU_2007_Read_Latches[3] = !PPU_2007_Read_Latches[2];
- if (!PPU_2007_Read_Latches[3])
- {
- PPU_2007_Read_SR = false;
- }
-
- PPU_2007_Write_Latches[1] = !PPU_2007_Write_Latches[0];
- PPU_2007_Write_Latches[3] = !PPU_2007_Write_Latches[2];
- if (!PPU_2007_Write_Latches[3])
- {
- PPU_2007_Write_SR = false;
- }
- PPU_2007_DB_PAR = PPU_2007_Write_Latches[1] && !PPU_2007_Write_Latches[3];
- PPU_WRITE = !PPU_2007_PaletteRAMEnable && PPU_2007_DB_PAR;
- if (PPU_2007_DB_PAR) // Using PAR instead of PPU_WRITE, since I re-use WriteVideoMemory() for writes to palette RAM.
- {
- WriteVideoMemory(PPU_2007_WriteData);
- }
- }
-
-
-
- void DrawToScreen()
- {
- if (PPU_Dot > 3 && PPU_Dot <= 259 && PPU_Scanline < 241) // the process of drawing a dot to the screen actually has a 2 ppu cycle delay, which the emphasis bits happen after
- {
- // in other words, the geryscale/emphasis bits can affect the color that was decided 2 ppu cycles ago.
- chosenColor = PrevPrevPrevDotColor;
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- // emphasis bits
- int emphasis = 0;
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- int scanline0OddFrameOffset = 0;
- if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites))
- {
- scanline0OddFrameOffset = 1;
- }
- if (!PPU_DecodeSignal)
- {
- if (!PPU_ShowScreenBorders)
- {
- if (scanline0OddFrameOffset == 1 && PPU_Dot == 4)
- {
- // do nothing. This would be off screen.
- }
- else
- {
- Screen.SetPixel(PPU_Dot - 4 - scanline0OddFrameOffset, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color.
- }
- }
- else
- {
- Screen.SetPixel(PPU_Dot - 4 - scanline0OddFrameOffset, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color.
- }
- }
- else
- {
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- PPU_SignalDecode(chosenColor | emphasis);
- PrevPrevPrevPrevDotColor = chosenColor | emphasis;
- }
- }
- if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot == 259)
- {
- // draw the backdrop.
- chosenColor = PaletteRAM[0];
- // emphasis bits
- int emphasis = 0;
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- if (!PPU_DecodeSignal)
- {
- Screen.SetPixel(255, PPU_Scanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color.
- }
- else
- {
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- PPU_SignalDecode(chosenColor | emphasis);
- PrevPrevPrevPrevDotColor = chosenColor | emphasis;
- }
- }
- }
-
- void DrawToBorderedScreen()
- {
-
- int dot = PPU_Dot;
- int scanline = PPU_Scanline;
- int emphasis = 0;
-
- dot -= 3;
- if (dot < 0)
- {
- dot = 341 + dot;
- scanline--;
- if (scanline < 0)
- {
- scanline = 261;
- }
- }
-
- int boarderedDot = 0;
- int boarderedScanline = scanline;
-
- if (PPU_ShowScreenBorders && dot == 325)
- {
- ntsc_signal_of_dot_0 = ntsc_signal;
- }
- if (PPU_DecodeSignal && dot == 277)
- {
- RenderNTSCScanline();
- }
-
- if (scanline < 241 || ((scanline == 241 && dot < 277)) || scanline == 261)
- {
-
- if (dot >= 1 && dot <= 256) // visible pixels.
- {
- if (scanline == 261)
- {
- chosenColor = 0x0F;
- }
- else
- {
-
- chosenColor = PrevPrevPrevDotColor;
-
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- // emphasis bits
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- }
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- else if (dot >= 257 && dot <= 267) // right boarder
- {
- if (scanline == 261)
- {
- chosenColor = 0x0F;
- }
- else
- {
- // backdrop.
- chosenColor = PrevPrevPrevDotColor;
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- // emphasis bits
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- }
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- else if (dot >= 268 && dot <= 276) // front porch
- {
- // black.
- chosenColor = 0x0F;
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- else if (dot >= 277 && dot <= 301) // horizontal sync
- {
- // black.
- chosenColor = 0x0F;
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else if (dot >= 302 && dot <= 305) // back porch
- {
- // black.
- chosenColor = 0x0F;
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else if (dot >= 306 && dot <= 320) // colorburst
- {
- // extremely dark olive.
- chosenColor = Signal_COLORBURST;
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else if (dot >= 321 && dot <= 325) // back porch
- {
- // black.
- chosenColor = 0x0F;
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else if (dot == 326) // pulse
- {
-
- // backdrop in greyscale
- if (ThisDotReadFromPaletteRAM)
- {
- chosenColor = PrevPrevPrevDotColor;
- }
- else
- {
- chosenColor = PrevPrevPrevDotColor & 0x30;
- }
-
- // emphasis bits
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
-
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else // right boarder
- {
- // backdrop.
- if (scanline == 261 && dot == 0)
- {
- chosenColor = 0x0F;
- }
- else
- {
- chosenColor = PrevPrevPrevDotColor;
-
- if (PPU_Mask_Greyscale) // if the ppu greyscale mode is active,
- {
- chosenColor &= 0x30; //To force greyscale, bitwise AND this color with 0x30
- }
- // emphasis bits
- if (PPU_Mask_EmphasizeRed) { emphasis |= 0x40; } // if emhpasizing r, add 0x40 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeGreen) { emphasis |= 0x80; } // if emhpasizing g, add 0x80 to the index into the palette LUT.
- if (PPU_Mask_EmphasizeBlue) { emphasis |= 0x100; } // if emhpasizing b, add 0x100 to the index into the palette LUT.
- }
- if (dot != 0)
- {
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
-
- }
- else
- {
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- }
- }
- else
- {
- if (scanline >= 245 && scanline <= 247)
- {
- // black.
- chosenColor = 0x0F;
- if (dot >= 277)
- {
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else
- {
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- }
- else
- {
- // colorburst happens on this line too.
- if (dot >= 306 && dot <= 320) // colorburst
- {
- // extremely dark olive.
- chosenColor = Signal_COLORBURST;
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else
- {
- // black.
- chosenColor = 0x0F;
- if (dot >= 277)
- {
- boarderedDot = dot - 277;
- boarderedScanline = scanline + 1;
- }
- else
- {
- boarderedDot = dot + 64;
- boarderedScanline = scanline;
- }
- }
- }
- }
- if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot < 277)
- {
- boarderedDot--;
- }
- if (boarderedScanline == 0x106)
- {
- boarderedScanline = 0;
- }
- if (PPU_DecodeSignal)
- {
- PPU_SignalDecode(chosenColor | emphasis);
- }
- else
- {
- BorderedScreen.SetPixel(boarderedDot, boarderedScanline, unchecked((int)NesPalInts[chosenColor | emphasis])); // this sets the pixel on screen to the chosen color.
- }
- }
-
-
- public bool PPU_DecodeSignal;
- public bool PPU_ShowScreenBorders;
- static float[] Voltages =
- { 0.228f, 0.312f, 0.552f, 0.880f, // Signal low
- 0.616f, 0.840f, 1.100f, 1.100f, // Signal high
- 0.192f, 0.256f, 0.448f, 0.712f, // Signal low, attenuated
- 0.500f, 0.676f, 0.896f, 0.896f // Signal high, attenuated
- };
- public byte ntsc_signal;
- public byte ntsc_signal_of_dot_0;
- public float[] NTSC_Samples = new float[257 * 8 + 24];
- public float[] Bordered_NTSC_Samples = new float[341 * 8 + 24];
- static float[] Levels =
- {
- (Voltages[0] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[1] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[2] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[3] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[4] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[5] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[6] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[7] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[8] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[9] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[10] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[11] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[12] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[13] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[14] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f,
- (Voltages[15] - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f
- };
- float Saturation = 0.75f;
- int SignalBufferWidth = 12;
- static double hue = 0;
- static float chroma_saturation_correction = 2.4f;
- static double[] SinTable =
- {
- Math.Sin(Math.PI* (0 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (1 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (2 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (3 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (4 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (5 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (6 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (7 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (8 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (9 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (10 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Sin(Math.PI* (11 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction
- };
- static double[] CosTable =
- {
- Math.Cos(Math.PI* (0 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (1 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (2 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (3 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (4 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (5 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (6 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (7 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (8 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (9 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (10 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction,
- Math.Cos(Math.PI* (11 + 3 - 0.5 + hue) / 6) * chroma_saturation_correction
- };
- bool InColorPhase(int col, int DecodePhase)
- {
- return (col + DecodePhase) % 12 < 6;
- }
- static float ntsc_black = 0.312f, ntsc_white = 1.100f;
- static int Signal_COLORBURST = 512;
- static int Signal_SYNC = 513;
- static float Colorburst_High = (0.524f - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f;
- static float Colorburst_Low = (0.148f - Voltages[1]) / (Voltages[6] - Voltages[1]) / 12f;
-
- void PPU_SignalDecode(int nesColor)
- {
- bool boardered = PPU_ShowScreenBorders;
- byte phase = ntsc_signal;
- int i = 0;
- while (i < 8)
- {
- float sample = 0;
- // Decode the NES color.
- if (nesColor == Signal_COLORBURST)
- {
- sample = InColorPhase(0x8, phase) ? Colorburst_High : Colorburst_Low;
- }
- else
- {
- int colInd = (nesColor & 0x0F); // 0..15 "cccc"
- int level = (nesColor >> 4) & 3; // 0..3 "ll"
- int emphasis = (nesColor >> 6); // 0..7 "eee"
- if (colInd > 13) { level = 1; } // For colors 14..15, level 1 is forced.
- int attenuation = (
- ((((emphasis & 1) != 0) && InColorPhase(0xC, phase)) ||
- (((emphasis & 2) != 0) && InColorPhase(0x4, phase)) ||
- (((emphasis & 4) != 0) && InColorPhase(0x8, phase))) && (colInd < 0xE)) ? 8 : 0;
- float low = Levels[0 + level + attenuation];
- float high = Levels[4 + level + attenuation];
- if (colInd == 0) { low = high; } // For color 0, only high level is emitted
- if (colInd > 12) { high = low; } // For colors 13..15, only low level is emitted
- sample = InColorPhase(colInd, phase) ? high : low;
- }
- if (boardered)
- {
- int dot = PPU_Dot - 3;
- if (dot < 0)
- {
- dot = 341 + dot;
- }
- if (dot >= 277)
- {
- dot -= 277;
- }
- else
- {
- dot += 64;
- }
- if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Dot < 277)
- {
- dot--;
- }
- Bordered_NTSC_Samples[dot * 8 + i] = sample;
- }
- else if (PPU_Dot <= 256 + 3)
- {
- if (PPU_Dot == 0)
- {
- NTSC_Samples[i] = sample;
- }
- else
- {
- NTSC_Samples[(PPU_Dot - 3) * 8 + i] = sample;
- }
- }
- phase++;
- phase %= 12;
- i++;
- }
- }
- public bool PPU_ShowRawNTSCSignal;
-
- void RenderNTSCScanline()
- {
- byte phase = ntsc_signal_of_dot_0;
- bool bordered = PPU_ShowScreenBorders; // this value could change at any moment, so it would be nice to avoid errors due to array lengths.
-
- int scanline0OddFrameOffset = 0;
- if (PPU_Scanline == 0 && PPU_OddFrame && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && !bordered)
- {
- scanline0OddFrameOffset = 8;
- }
-
- int width = bordered ? BorderedNTSCScreen.Width : NTSCScreen.Width;
-
- int i = 0;
- while (i < width + scanline0OddFrameOffset)
- {
- double R = 0;
- double G = 0;
- double B = 0;
- if (!PPU_ShowRawNTSCSignal)
- {
- int center = i + 8;
- int begin = center - 6;
- int end = center + 6;
- double Y = 0;
- double U = 0;
- double V = 0;
- for (int p = begin; p < end; ++p) // Collect and accumulate samples
- {
- float sample = bordered ? (Bordered_NTSC_Samples[p]) : (NTSC_Samples[p]);
- Y += sample;
- int rotation = (phase + p) % 12;
- U += (sample * SinTable[rotation]);
- V += (sample * CosTable[rotation]);
- }
-
- //U *= (0.35355339 * 2);
- //V *= (0.35355339 * 2);
-
- U = U * 0.5f + 0.5f;
- V = V * 0.5f + 0.5f;
-
- bool DebugYUV = false;
- if (DebugYUV)
- {
- Y = 0.5;
- U = (i + 0.0f) / width;
- V = 1 - (PPU_Scanline / 240f);
-
- U -= 0.5f;
- V -= 0.5f;
-
- U *= (0.35355339 * 2);
- V *= (0.35355339 * 2);
-
- U += 0.5f;
- V += 0.5f;
- }
-
- // convert YUV to RGB
- R = 1.164 * (Y - 16 / 256.0) + 1.596 * (V - 128 / 256.0);
- G = 1.164 * (Y - 16 / 256.0) - 0.392 * (U - 128 / 256.0) - 0.813 * (V - 128 / 256.0);
- B = 1.164 * (Y - 16 / 256.0) + 2.017 * (U - 128 / 256.0);
-
- // other values ?
- //double R = 1.164 * (Y - 16 / 256.0) + 1.14 * (V - 128 / 256.0);
- //double G = 1.164 * (Y - 16 / 256.0) - (1 / 1.14) * (U - 128 / 256.0) - (1 / (1.14 * 1.78)) * (V - 128 / 256.0);
- //double B = 1.164 * (Y - 16 / 256.0) + (1.14 * 1.78) * (U - 128 / 256.0);
-
- // convert YUV to normalized RGB
- //double R = 1.164 * (Y - 16 / 256.0) + 1 * (V - 128 / 256.0);
- //double G = 1.164 * (Y - 16 / 256.0) - 0.31764705882 * (U - 128 / 256.0) - 0.68359375 * (V - 128 / 256.0);
- //double B = 1.164 * (Y - 16 / 256.0) + 1 * (U - 128 / 256.0);
-
- if (R < 0) { R = 0; }
- if (R > 1) { R = 1; }
- if (G < 0) { G = 0; }
- if (G > 1) { G = 1; }
- if (B < 0) { B = 0; }
- if (B > 1) { B = 1; }
- }
- if (PPU_ShowScreenBorders)
- {
- if (PPU_ShowRawNTSCSignal)
- {
- R = Bordered_NTSC_Samples[i] * 12;
- G = Bordered_NTSC_Samples[i] * 12;
- B = Bordered_NTSC_Samples[i] * 12;
- if (R < 0) { R = 0; }
- if (R > 1) { R = 1; }
- if (G < 0) { G = 0; }
- if (G > 1) { G = 1; }
- if (B < 0) { B = 0; }
- if (B > 1) { B = 1; }
- }
- BorderedNTSCScreen.SetPixel(i, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color.
- }
- else
- {
- if (PPU_ShowRawNTSCSignal)
- {
- R = NTSC_Samples[i + 8] * 12;
- G = NTSC_Samples[i + 8] * 12;
- B = NTSC_Samples[i + 8] * 12;
- if (R < 0) { R = 0; }
- if (R > 1) { R = 1; }
- if (G < 0) { G = 0; }
- if (G > 1) { G = 1; }
- if (B < 0) { B = 0; }
- if (B > 1) { B = 1; }
- }
- if (scanline0OddFrameOffset == 0)
- {
- NTSCScreen.SetPixel(i, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color.
- }
- else
- {
- if (i >= 8)
- {
- NTSCScreen.SetPixel(i - 8, PPU_Scanline, Color.FromArgb((byte)(R * 255), (byte)(G * 255), (byte)(B * 255))); // this sets the pixel on screen to the chosen color.
- }
- }
- }
- i++;
- }
- }
-
- // If OAM corruption is pending, it occurs on the first rendered dot.
- public void CorruptOAM()
- {
- // basically 8 entries of OAM are getting replaced (this is considered a single "row" of OAM)
- // PPU_OAMCorruptionIndex is the row that gets corrupted.
- if (PPU_OAMCorruptionIndex == 0x20)
- {
- PPU_OAMCorruptionIndex = 0;
- }
- int i = 0;
- while (i < 8) // 8 entries in a row
- {
- OAM[PPU_OAMCorruptionIndex * 8 + i] = OAM[i]; // The corrupted row is replaced with the values from row 0
- i++;
- }
- OAM2[PPU_OAMCorruptionIndex] = OAM2[0]; // Also corrupt this byte.
- // this all happens in a single cycle.
- }
-
-
-
- void IncrementOAM2Address()
- {
- if(!OAM2Overflowed)
- {
- OAM2Address++;
- if(OAM2Address == 0x20)
- {
- OAM2Overflowed = true;
- OAM2Address = 0;
- }
- }
- }
-
-
-
- bool OamCorruptedOnOddCycle;
- public byte PPU_OAMBuffer_In; // The value going into the OAM Buffer.
- public byte PPU_OAMBuffer; // Updated on the second half of a dot.
- public byte PPU_OAMLatch; // The value read from $2004, which is latched on the first half of a dot. (after the buffer is prepared)
-
- public ushort PPU_OAM_VerticalOffset; // Is this sprite in range of this scanline?
- bool NineObjectsOnThisScanline;
- bool OAM2Overflowed;
- byte OAM2ResetSignal;
- void PPU_Render_SpriteEvaluation()
- {
- bool SpriteEval_ReadOnly_PreRenderLine = false;
- if (PPU_Scanline == 261)
- {
- SpriteEval_ReadOnly_PreRenderLine = true;
- }
- if ((PPU_Mask_ShowBackground_Instant || PPU_Mask_ShowSprites_Instant))
- {
- if (PPU_PendingOAMCorruption) // OAM corruption occurs on the visible dot after rendering was enabled. It also can happen on the pre-render line.
- {
- PPU_PendingOAMCorruption = false;
- if (!PPU_OAMCorruptionRenderingEnabledOutOfVBlank)
- {
- CorruptOAM();
- }
- PPU_OAMCorruptionRenderingEnabledOutOfVBlank = false;
- }
- }
-
- if ((PPU_Dot >= 0 && PPU_Dot <= 64)) // Dots 1 through 64, not on the pre-render line. (and also dot 0 for OAM corruption purposes)
- {
-
- // this step is clearing secondary OAM, and writing FF to each byte in the array.
- if ((PPU_Dot & 1) == 1)
- { //odd cycles
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed))
- {
- if (SpriteEval_ReadOnly_PreRenderLine)
- {
- PPU_OAMBuffer_In = OAM2[OAM2Address];
- }
- else
- {
- PPU_OAMBuffer_In = 0xFF;
- }
- if (PPU_Dot == 1)
- {
- OAM2Address = 0; // if this is dot 1, reset the secondary OAM address // in preparation for the next section, let's clear these flags too
- SpriteEvaluationTick = 0;
- OAMAddressOverflowedDuringSpriteEvaluation = false;
- }
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
- }
- }
- else
- { //even cycles
- if (PPU_Dot > 0)
- {
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed))
- {
- if (!SpriteEval_ReadOnly_PreRenderLine)
- {
- OAM2[OAM2Address] = PPU_OAMBuffer; // store FF in secondary OAM
- }
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
-
- IncrementOAM2Address();
-
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant && PPU_Dot == 64)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- }
- }
- else
- {
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
- }
- }
- else
- {
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed))
- {
- IncrementOAM2Address();
- }
- }
- }
- }
- else if ((PPU_Dot >= 65 && PPU_Dot <= 256)) // Dots 65 through 256, not on the pre-render line
- {
- if (PPU_Dot == 65)
- {
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed))
- {
- NineObjectsOnThisScanline = false;
- OamCorruptedOnOddCycle = false;
- OAMAddressOverflowedDuringSpriteEvaluation = false;
- }
- }
- if (PPU_Mask_ShowBackground_Instant || PPU_Mask_ShowSprites_Instant || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant) // if rendering is enabled, or was *just* disabled mid evaluation
- {
- if ((PPU_Dot & 1) == 1)
- { //odd cycles
- byte PrevSpriteEvalTemp = PPU_OAMBuffer;
- PPU_OAMBuffer_In = OAM[PPUOAMAddress]; // read from OAM
-
- // If rendering was disabled *this* cycle (the odd cycle) then the even cycle will run normally, and the *next odd cycle* will have the OAM address increment. Presumably, that's when we record secondOAMAddr.
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant)
- {
- PPU_OAMEvaluationCorruptionOddCycle = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- if (!SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress++;
- }
- OamCorruptedOnOddCycle = true;
-
- }
- }
- else
- { //even cycles
- if (!OAMAddressOverflowedDuringSpriteEvaluation)
- {
- byte PreIncVal = PPUOAMAddress; // for checking if PPUOAMAddress overflows
- if (!OAM2Overflowed && !SpriteEval_ReadOnly_PreRenderLine) // If secondary OAM is not yet full,
- {
- OAM2[OAM2Address] = PPU_OAMBuffer; // store this value at the secondary oam address.
- }
- byte OAM2READ = OAM2[OAM2Address];
- if (SpriteEvaluationTick == 0) // tick 0: check if this object's y position is in range for this scanline
- {
- PPU_OAMEvaluationObjectInXRange = false;
- PPU_OAM_VerticalOffset = (ushort)((PPU_Scanline & 0xFF) - PPU_OAMBuffer);
- if (!NineObjectsOnThisScanline && !SpriteEval_ReadOnly_PreRenderLine && PPU_OAM_VerticalOffset < (PPU_Spritex16 ? 16 : 8))
- {
- PPU_OAMEvaluationObjectInRange = true;
- // if this sprite is within range.
- if (!OAM2Overflowed)
- {
- if (!OamCorruptedOnOddCycle)
- {
- if (!SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress++; // +1
- }
- IncrementOAM2Address();
- }
- // Sprite zero hits actually have nothing to do with reading the object at OAM index 0. Rather, if an object is within range of the scanline on dot 66.
- // typically, the object processed on dot 66 is OAM[0], though it's possible using precisely timed writes to $2003 to have PPUOAMAddress start processing here from a different value.
- if (PPU_Dot == 66)
- {
- PPU_NextScanlineContainsSpriteZero = true; // this value will be transferred to PPU_PreviousScanlineContainsSpriteZero at the end of the scanline, and that variable is used in sp 0 hit detection.
- }
- }
- else
- {
- NineObjectsOnThisScanline = true;
- PPUOAMAddress++;
- if (!PPUStatus_SpriteOverflow)// if secondary OAM is full, yet another object is on this scanline
- {
- PPUStatus_SpriteOverflow = true; // set the sprite overflow flag
- }
- }
- if (!SpriteEval_ReadOnly_PreRenderLine)
- {
- SpriteEvaluationTick++; // increment the tick for next even ppu cycle.
- }
- }
- else
- {
- if (PPU_Dot == 66)
- {
- PPU_NextScanlineContainsSpriteZero = false; // this value will be transferred to PPU_PreviousScanlineContainsSpriteZero at the end of the scanline, and that variable is used in sp 0 hit detection.
- }
- PPU_OAMEvaluationObjectInRange = false;
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- if (OAM2Overflowed && !NineObjectsOnThisScanline)// this behavior stops after finding the ninth object.
- {
- if ((PPUOAMAddress & 0x3) == 3)
- {
- PPUOAMAddress++; // A real hardware bug.
- }
- else
- {
- PPUOAMAddress += 4; // +4
- PPUOAMAddress++; // A real hardware bug.
- }
- }
- else
- {
- PPUOAMAddress += 4; // +4
- PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits
- }
- }
- }
- }
- else // ticks 1, 2, or 3
- {
- if (SpriteEvaluationTick == 3) // tick 3: X position.
- {
- PPU_OAMEvaluationObjectInRange = false;
- // OAM X coordinate.
- // This also runs the "vertical in range check", though typically the result doesn't matter.
- if (PPU_Scanline - PPU_OAMBuffer >= 0 && PPU_Scanline - PPU_OAMBuffer < (PPU_Spritex16 ? 16 : 8))
- {
- // if this sprite is within range.
- PPU_OAMEvaluationObjectInXRange = true;
- if (!OAM2Overflowed)
- {
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress++; // +1
- }
- }
- else
- {
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress += 4; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?)
- }
- }
- }
- else
- {
- PPU_OAMEvaluationObjectInXRange = false;
- if (!OAM2Overflowed)
- {
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress += 1; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?)
- PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits
- }
- }
- else
- {
- PPUOAMAddress += 1; // +1 (In theory, this should be +4, though my experiments only reflect my consoles behavior if this is +1?)
- PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits
- }
- }
- }
- else // ticks 1 and 2 don't make any checks. Only increment the OAM address.
- {
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress++; // +1
- }
- }
- SpriteEvaluationTick++; // increment the tick for next even ppu cycle.
- SpriteEvaluationTick &= 3; // and reset the tick to 0 if it reaches 4.
- if (!OAM2Overflowed && !SpriteEval_ReadOnly_PreRenderLine) // if secondary OAM is not full
- {
- IncrementOAM2Address();
- }
- }
- OamCorruptedOnOddCycle = false;
-
- if (PPUOAMAddress < PreIncVal && PPUOAMAddress < 4) // If an overflow occured
- {
- OAMAddressOverflowedDuringSpriteEvaluation = true; // set this flag.
- }
- PPU_OAMBuffer_In = OAM2READ; // When overflowed, the ppu reads instead of writing to OAM2. (Run this regardless of if OAM2 is full or not.)
- }
- else
- { // OAM Address Overflowed During Sprite Evaluation
- // fail to write to SecondaryOAM
- // boo womp.
-
- // also update the PPUOAMAddress.
- if (!OamCorruptedOnOddCycle && !SpriteEval_ReadOnly_PreRenderLine)
- {
- PPUOAMAddress += 4; // +4
- PPUOAMAddress &= 0xFC; // also mask away the lower 2 bits
- }
- PPU_OAMBuffer_In = OAM2[OAM2Address]; // When overflowed, the ppu reads instead of writing to OAM2.
- }
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant && !PPU_OAMEvaluationCorruptionOddCycle) // if we just disabled rendering mid OAM evaluation, the address is incremented yet again.
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
-
- if ((OAM2Address & 3) != 0 && !OAMAddressOverflowedDuringSpriteEvaluation && !SpriteEval_ReadOnly_PreRenderLine)
- {
- OAM2Address &= 0xFC;
- OAM2Address += 4; // ??? Why is this using OAM2???
- }
- if (PPUClock == 0 || PPUClock == 3)
- {
- PPU_OAMCorruptionIndex = (byte)(OAM2Address); // this value will be used when rendering is re-enabled and the corruption occurs
- }
- if (PPUClock == 1 || PPUClock == 2)
- {
- PPU_OAMCorruptionIndex = (byte)(OAM2Address); // this value will be used when rendering is re-enabled and the corruption occurs
- }
- if (PPU_Dot == 256)
- {
- PPU_OAMCorruptionIndex = OamCorruptedOnOddCycle ? (byte)0 : (byte)1; //I have no idea.
- }
-
- }
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- }
- }
-
- }
- else if (PPU_Dot >= 257 && PPU_Dot <= 320) // this also happens on the pre-render line.
- {
- PPU_CurrentScanlineContainsSpriteZero = PPU_NextScanlineContainsSpriteZero;
-
- if ((PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed))
- {
- PPUOAMAddress = 0; // this is reset during every one of these cycles, 257 through 320
- }
-
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank && (PPUClock == 0 || PPUClock == 3))
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
-
- if (PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
-
- int selectedShifter = ((PPU_Dot - 1) & 0x38) >> 3;
-
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed)
- {
- PPU_OAMBuffer_In = OAM2[OAM2Address];
- }
- else
- {
- PPU_OAMBuffer_In = OAM[PPUOAMAddress]; // The OAM buffer is updated with Primary OAM when rendering is disabled.
- }
- switch ((PPU_Dot - 1) & 7)
- {
- // So each scanline can only have up to 8 sprites.
- // Each sprite has a Y position, Pattern, Attributes, and X position.
- // So there's an 8-index-long array for each of those.
- // Each index in the array is for a different sprite.
-
- // Sprites also have 2 "bit plane" shift registers.
- // These are the 8 pixels to draw for the object on this scanline.
- // Again, there are 8 objects, so there are 2 8-index-long arrays of bit planes.
-
- // each case is a different ppu cycle.
- // case 0.
- // next cycle, case 1.
- // next cycle, case 2, and so on.
- // case 7 then leads back to case 0.
-
- case 0: // Y position dot 257, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- // set this object's Y position in the array
- PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- PPU_PAR_MUX = PPU_PatternAddressRegister_NT;
- PPU_AddressBus = PPU_PAR_MUX;
- IncrementOAM2Address();
- }
- break;
- case 1: // Pattern dot 258, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- PPU_OAM_VerticalOffset = (ushort)((PPU_Scanline & 0xFF) - PPU_OAMBuffer);
-
- // set this object's pattern in the array
- PPU_Render_ShiftRegistersAndBitPlanes(); // Dummy Nametable Fetch
- IncrementOAM2Address();
- }
- break;
- case 2: // Attribute dot 259, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- PPU_SpritePattern = PPU_OAMBuffer;
-
- // set this object's attribute in the array
-
- PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- PPU_PAR_MUX = PPU_PatternAddressRegister_NT;
- PPU_AddressBus = PPU_PAR_MUX;
- IncrementOAM2Address();
- }
- break;
- case 3: // X position dot 260, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- PPU_SpriteAttribute[selectedShifter] = PPU_OAMBuffer;
- PPU_SpriteAttr = PPU_OAMBuffer;
- // set this object's X position in the array
- PPU_Render_ShiftRegistersAndBitPlanes(); // Dummy Nametable Fetch
- }
- // notably, the secondary OAM address does not get incremented until case 7
- break;
- case 4: // X position (again) dot 261, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- // set this object's X position in the array... again.
- PPU_SpriteShifterCounter[selectedShifter] = PPU_OAMBuffer;
-
- PPU_SpriteXposition[selectedShifter] = PPU_OAMBuffer;
- // But also: Find the PPU address of this sprite's graphical data inside the Pattern Tables.
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR &= 0b1111111110111;
- PPU_PAR_MUX = PPU_PatternAddressRegister_CHR;
- PPU_AddressBus = PPU_PAR_MUX;
- }
- break;
- case 5: // X position (again) dot 262, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- // set this object's X position in the array... again.
- PPU_SpriteXposition[selectedShifter] = PPU_OAMBuffer;
- // but also: set up the bit plane shift register.
-
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR &= 0b1111111110111;
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch);
-
- PPU_SpritePatternL = FetchVideoMemory();
- if (((PPU_SpriteAttribute[selectedShifter] >> 6) & 1) == 1) // Attributes are set up to flip X
- {
- PPU_SpritePatternL = Flip(PPU_SpritePatternL);
- }
- PPU_SpriteShiftRegisterL[selectedShifter] = PPU_SpritePatternL;
-
- // in-range check. (The pre-render line ends up checking scanline 5 due to the `& 0xFF`.
- if (!(PPU_OAM_VerticalOffset < (PPU_Spritex16 ? 16 : 8)))
- {
- PPU_SpriteShiftRegisterL[selectedShifter] = 0; // clear the value in this shift register if this object isn't in range.
- }
- }
- break;
- case 6: // X position (again) dot 263, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- // set this object's X position in the array... again.
- PPU_SpriteXposition[selectedShifter] = PPU_OAMBuffer;
- // but also: add 8 to the PPU address. The other bit plane is 8 addresses away.
- PPU_AddressBus |= 8;
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR |= 8;
- PPU_PAR_MUX = PPU_PatternAddressRegister_CHR;
- PPU_AddressBus = PPU_PAR_MUX;
- }
- break;
- case 7: // X position (again) dot 264, (+8), (+16) ...
- if (PPU_Mask_ShowBackground_Delayed || PPU_Mask_ShowSprites_Delayed) // if rendering has been enabled for at least one cycle
- {
- // set this object's X position in the array... again.
- PPU_SpriteXposition[selectedShifter] = PPU_OAMBuffer; // read X pos again
- // but also: set up the second bit plane
-
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR |= 8;
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch);
-
- PPU_SpritePatternH = FetchVideoMemory();
- if (((PPU_SpriteAttribute[selectedShifter] >> 6) & 1) == 1) // Attributes are set up to flip X
- {
- PPU_SpritePatternH = Flip(PPU_SpritePatternH);
- }
- PPU_SpriteShiftRegisterH[selectedShifter] = PPU_SpritePatternH;
-
- // in-range check. (The pre-render line ends up checking scanline 5 due to the `& 0xFF`.
- if (!(PPU_OAM_VerticalOffset < (PPU_Spritex16 ? 16 : 8)))
- {
- PPU_SpriteShiftRegisterH[selectedShifter] = 0; // clear the value in this shift register if this object isn't in range.
- }
- IncrementOAM2Address();
- }
- break;
- }
- if (PPU_ALE && !PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
-
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank && (PPUClock == 1 || PPUClock == 2))
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
-
- }
- else
- {
- // cycles 320 to 340
- if (PPU_OAMCorruptionRenderingDisabledOutOfVBlank || PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = false;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = false;
- PPU_PendingOAMCorruption = true;
- PPU_OAMCorruptionIndex = OAM2Address; // this value will be used when rendering is re-enabled and the corruption occurs
- }
- if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground))
- {
- PPU_OAMBuffer_In = OAM2[OAM2Address];
- }
- else
- {
- PPU_OAMBuffer_In = OAM[PPUOAMAddress]; // The OAM buffer is updated with Primary OAM when rendering is disabled.
- }
- if (PPU_Dot == 339)
- {
- for (int i = 0; i < 8; i++)
- {
- if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground))
- {
- }
- else
- {
- PPU_SpriteShifterCounter[i] = 0;
- }
- }
- }
- }
- // and that's all for sprite evaluation!
- }
- void PPU_Render_CalculatePixel(bool borders)
- {
- // dots 1 through 256
- if (PPU_Dot > 256)
- {
- borders = true;
- }
- if (PPU_Dot <= 256 || borders)
- {
- // there are 8 palettes in the PPU
- // 4 are for the background, and the other 4 are for sprites.
- byte Palette = 0;
- // each of these palettes have 4 colors
- byte Color = 0;
- if (!borders)
- {
- if (PPU_Mask_ShowBackground && (PPU_Dot > 8 || PPU_Mask_8PxShowBackground)) // if rendering is enables for this pixel
- {
- byte col0 = (byte)(((PPU_BackgroundPatternShiftRegisterL >> (15 - PPU_FineXScroll))) & 1); // take the bit from the shift register for the pattern low bit plane
- byte col1 = (byte)(((PPU_BackgroundPatternShiftRegisterH >> (15 - PPU_FineXScroll))) & 1); // take the bit from the shift register for the pattern high bit plane
- Color = (byte)((col1 << 1) | col0);
-
- byte pal0 = (byte)(((PPU_BackgroundAttributeShiftRegisterL) >> (7 - PPU_FineXScroll)) & 1); // take the bit from the shift register for the attribute low bit plane
- byte pal1 = (byte)(((PPU_BackgroundAttributeShiftRegisterH) >> (7 - PPU_FineXScroll)) & 1); // take the bit from the shift register for the attribute high bit plane
- Palette = (byte)((pal1 << 1) | pal0);
-
- if (Color == 0 && Palette != 0) // color 0 of all palettes are mirrors of color 0 of palette 0
- {
- Palette = 0;
- }
- }
- }
- // pretty much the same thing, but for sprites instead of background
- byte SpritePalette = 0;
- byte SpriteColor = 0;
- bool SpritePriority = false; // if set, this sprite will be in front of background tiles. Otherwise, it will only take priority if the background is using color 0.
- if (!borders)
- {
- if (PPU_Mask_ShowSprites && (PPU_Dot > 8 || PPU_Mask_8PxShowSprites))
- {
- int i = 0;
-
- // check all 8 objects in secondary OAM
- while (i < 8)
- {
- if (PPU_SpriteShifterCounter[i] == 0 || SkippedPreRenderDot341) // if the shifter counter == 0 (the shifter counter is decremented each ppu cycle)
- {
- bool SpixelL = ((PPU_SpriteShiftRegisterL[i]) & 0x80) != 0; // take the bit from the shift register for the pattern low bit plane
- bool SpixelH = ((PPU_SpriteShiftRegisterH[i]) & 0x80) != 0; // take the bit from the shift register for the pattern high bit plane
- SpriteColor = 0;
- if (SpixelL) { SpriteColor = 1; }
- if (SpixelH) { SpriteColor |= 2; }
-
- SpritePalette = (byte)((PPU_SpriteAttribute[i] & 0x03) | 0x04); // read the palette from secondary OAM attributes.
- SpritePriority = ((PPU_SpriteAttribute[i] >> 5) & 1) == 0; // read the priority from secondary OAM attributes.
-
- }
- else // if no objects are in range of this pixel...
- {
- i++; // try the next one
- continue;
- }
-
- if (SpriteColor != 0) // if we found an object, exit the loop. This means, objects earlier in secondary OAM hive higher priority over sprites later in secondary OAM
- {
- break;
- }
-
- i++; // This pixel wasn't a part of the previous object. Try the next slot in secondary oam.
- }
-
- // if we hit sprite zero and both rendering background and sprites are enabled...
- if (PPU_CanDetectSpriteZeroHit && i == 0 && PPU_CurrentScanlineContainsSpriteZero && PPU_Mask_ShowBackground && PPU_Mask_ShowSprites)
- {
- if (Color != 0 && SpriteColor != 0) // if both the background and sprites are visible on this pixel
- {
- if ((PPU_Mask_8PxShowSprites || PPU_Dot > 8) && PPU_Dot < 256) // and if this isn't on pixel 256, or in the first 8 pixels being masked away from the nametable, if that setting is enabled...
- {
- PPUStatus_PendingSpriteZeroHit = true; // we did it! sprite zero hit achieved... the flag is set on teh next half-ppu-cycle.
- PPU_CanDetectSpriteZeroHit = false; // another sprite zero hit cannot occur until the end of next vblank.
- if (Logging) // and for some debug logging...
- {
- string S = DebugLog.ToString(); // let's add text to the current line letting me know a sprite zero hit occured, and on which dot
- if (S.Length > 0)
- {
- S = S.Substring(0, S.Length - 2); // trim off \n
- DebugLog = new StringBuilder(S);
- DebugLog.Append(" ! Sprite Zero Hit ! (Dot " + PPU_Dot + ")\r\n");
-
- }
- }
- }
- }
- }
-
- // which do we draw, the background or the sprite?
- if (Color == 0 && SpriteColor != 0) // Well, if the background was using color 0, and the sprite wasn't, always draw the sprite.
- {
- Color = SpriteColor; // I'm just reusing this background color variable.
- Palette = SpritePalette; // I'm also just reusing the background palette variable.
- }
- else if (SpriteColor != 0) // the background color isn't zero...
- {
- if (SpritePriority) // if the sprite has priority, always draw the sprite.
- {
- Color = SpriteColor; // I'm just reusing this cackground color variable.
- Palette = SpritePalette; // I'm also just reusing the background palette variable.
- }
- }
- }
- }
- if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline < 240) // if rendering is enabled...
- {
- PaletteRAMAddress = (byte)(Palette << 2 | Color); // the Palette RAM address is determined by the palette and color we found.
- }
- else
- {
- // rendering is disabled...
- if ((PPU_v & 0x3F1F) >= 0x3F00) // if v points to palette ram:
- {
- PaletteRAMAddress = (byte)(PPU_v & 0x1F); // The palette RAM address is simply wherever the v register is. (bitwise and with $1F due to palette RAM mirroring)
- if ((PaletteRAMAddress & 3) == 0)
- {
- PaletteRAMAddress &= 0x0F; // the transparent colors for sprites and backgrounds are shared.
- }
- }
- else
- {
- // EXT Pins
- PaletteRAMAddress = 0; // I'm not really emulating the EXT pins, and as far as I'm aware they aren't used in any games, official or homebrew.
- // This is typically why the background color is using Palette[0] when rendering is disabled.
- }
- }
-
- if (PPU_PaletteCorruptionRenderingDisabledOutOfVBlank || PPU_VRegisterChangedOutOfVBlank)
- {
- PPU_VRegisterChangedOutOfVBlank = false;
- PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = false;
- // PPU palette corruption!
-
- CorruptPalettes(Color, Palette);
- // This corruption also results in a single discolored pixel, and this occurs on all alignments.
- // I'm not entirely sure how this works, and I think it's the *next* pixel that gets corrupt? More research needed.
-
- }
-
- DotColor = (byte)((PaletteRAM[0x00 | PaletteRAMAddress]) & 0x3F); // Get the color by reading from Palette RAM
-
- // though this is actually drawn to the screen 2 ppu cycles from now.
- }
- }
-
- void CorruptPalettes(byte Color, byte Palette)
- {
- // Depending on the index into a color palette being used to select a color being drawn when rendering was disabled during a nametable fetch on a visible pixel with the PPU V Register (bitwise AND with $3FFF) being >= $3C00...
- // Palettes get "corrupted" with a specific pattern.
- // This pattern is determined by:
- // The lowest nybble of the PPU's V register,
- // The color index into the palette,
- // and if this is using a sprite palette. (TODO: emulate this part)
-
- // All of this was determined by observations with a custom test cart.
- // It is entirely possible that the logic defined in this functions is incorrect, or possibly there are more factors at play.
- // As far as I can tell though, this is "good enough" emulation of palette corruption.
-
- if ((CPUClock & 3) != 2)
- {
- // this behavior occurs on other alignments, but seems consistent on alignment 2, and very hit or miss on other alignments.
- // Currently, I'm only emulating this on alignment 2, but I'll probably change this in the future.
- return;
- }
-
-
- byte[] CorruptedPalette = new byte[PaletteRAM.Length];
- for (int i = 0; i < CorruptedPalette.Length; i++)
- {
- CorruptedPalette[i] = PaletteRAM[i];
- }
-
- switch (Color)
- {
- case 0:
- // simply take the low nybble from the V register. that's the color to corrupt.
- CorruptedPalette[PPU_v & 0xF] = (byte)((PaletteRAM[0] & PaletteRAM[PPU_v & 0xC]) | (PaletteRAM[0] & PaletteRAM[PPU_v & 0xF]) | (PaletteRAM[PPU_v & 0xC] & PaletteRAM[PPU_v & 0xF]));
- // TODO: Nybble 7 can corrupt color F. It's inconsistent though, so I'll need to circle back to this.
-
- break;
- case 1:
-
- // To be honest, I'm not sure what's going on, so forgive the lack of comments.
- // There's almost a pattern, but again- unsure on why this is how it behaves.
- // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research.
-
- switch (PPU_v & 0xF)
- {
- case 0:
- CorruptedPalette[0x0] = (byte)((PaletteRAM[0x1] & PaletteRAM[0xD]) | PaletteRAM[0x0]);
- CorruptedPalette[0x4] = PaletteRAM[0x5];
- CorruptedPalette[0x8] = PaletteRAM[0x9];
- CorruptedPalette[0xC] = PaletteRAM[0xD];
- break;
- case 1:
- break;
- case 2:
- CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0xD]) & PaletteRAM[0x3]);
- CorruptedPalette[0x3] = (byte)((PaletteRAM[0x1] | PaletteRAM[0x2]) & PaletteRAM[0x3]);
- CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]);
- CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]);
- CorruptedPalette[0xE] = PaletteRAM[0xD];
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 3:
- CorruptedPalette[0x3] &= (byte)(PaletteRAM[0x1] | PaletteRAM[0xD]);
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 4:
- CorruptedPalette[0x0] = PaletteRAM[0x1];
- CorruptedPalette[0x4] = (byte)((PaletteRAM[0x5] & PaletteRAM[0xD]) | PaletteRAM[0x4]);
- CorruptedPalette[0x8] = PaletteRAM[0x9];
- CorruptedPalette[0xC] = PaletteRAM[0xD];
- break;
- case 5:
- break;
- case 6:
- CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x7]) & PaletteRAM[0xD]);
- CorruptedPalette[0x7] = (byte)((PaletteRAM[0x7] | PaletteRAM[0x6]) & PaletteRAM[0x5]);
- CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]);
- CorruptedPalette[0xE] = PaletteRAM[0xD];
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 7:
- CorruptedPalette[0x7] &= (byte)(PaletteRAM[0x5] | PaletteRAM[0xD]);
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 8:
- CorruptedPalette[0x0] = PaletteRAM[0x1];
- CorruptedPalette[0x4] = PaletteRAM[0x5];
- CorruptedPalette[0x8] = (byte)((PaletteRAM[0x9] & PaletteRAM[0xD]) | PaletteRAM[0x8]);
- CorruptedPalette[0xC] = PaletteRAM[0xD];
- break;
- case 9:
- break;
- case 0xA:
- CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xD]) & PaletteRAM[0x7]);
- CorruptedPalette[0xA] = (byte)((PaletteRAM[0xB] | PaletteRAM[0xD]) & PaletteRAM[0xA]);
- CorruptedPalette[0xB] = (byte)((PaletteRAM[0x9] | PaletteRAM[0xA]) & PaletteRAM[0xB]);
- CorruptedPalette[0xE] = PaletteRAM[0xD];
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 0xB:
- CorruptedPalette[0xB] &= (byte)(PaletteRAM[0x9] | PaletteRAM[0xD]);
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 0xC:
- CorruptedPalette[0x0] = PaletteRAM[0x1];
- CorruptedPalette[0x4] = PaletteRAM[0x5];
- CorruptedPalette[0x8] = PaletteRAM[0x9];
- CorruptedPalette[0xC] = PaletteRAM[0xD];
- break;
- case 0xD:
- break;
- case 0xE:
- CorruptedPalette[0x2] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xD]) & PaletteRAM[0x7]);
- CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]);
- CorruptedPalette[0xE] = PaletteRAM[0xD];
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- case 0xF:
- CorruptedPalette[0xF] = PaletteRAM[0xD];
- break;
- }
-
-
- // In some tests with case A, bit 3 ($08) of color 3 can remove bit 2 ($04) from the value of color 0 for the purposes of the bitwise AND. It's inconsistent though.
-
-
- break;
- case 2:
-
- // To be honest, I'm not sure what's going on, so forgive the lack of comments.
- // There's almost a pattern, but again- unsure on why this is how it behaves.
- // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research.
-
- switch (PPU_v & 0xF)
- {
- case 0:
- CorruptedPalette[0x0] = (byte)(PaletteRAM[0x0] | (PaletteRAM[0x2] & PaletteRAM[0xE]));
- CorruptedPalette[0x4] = PaletteRAM[0x6];
- CorruptedPalette[0x8] = PaletteRAM[0xA];
- CorruptedPalette[0xC] = PaletteRAM[0xE];
- break;
- case 1:
- CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1] | PaletteRAM[0xE]) & (PaletteRAM[0x3] | PaletteRAM[0xE]));
- CorruptedPalette[0x3] = (byte)((PaletteRAM[0x2] | PaletteRAM[0xE] | 0x3C) & PaletteRAM[0x3]);
- CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x7]) & PaletteRAM[0x5]);
- CorruptedPalette[0x9] = (byte)((PaletteRAM[0xA] | PaletteRAM[0xB]) & PaletteRAM[0x9]);
- CorruptedPalette[0xD] = PaletteRAM[0xE];
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 2:
- break;
- case 3:
- CorruptedPalette[0x3] &= (byte)(PaletteRAM[0x2] | PaletteRAM[0xE]);
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 4:
- CorruptedPalette[0x0] = PaletteRAM[0x2];
- CorruptedPalette[0x4] = (byte)(PaletteRAM[0x4] | (PaletteRAM[0x6] & PaletteRAM[0xE]));
- CorruptedPalette[0x8] = PaletteRAM[0xA];
- CorruptedPalette[0xC] = PaletteRAM[0xE];
- break;
- case 5:
- CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x5] = (byte)((PaletteRAM[0xE] | PaletteRAM[0x6]) & PaletteRAM[0x5]);
- CorruptedPalette[0x7] = (byte)((PaletteRAM[0xE] | PaletteRAM[0x6]) & PaletteRAM[0x7]);
- CorruptedPalette[0xD] = PaletteRAM[0xE];
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 6:
- break;
- case 7:
- CorruptedPalette[0x7] &= (byte)(PaletteRAM[0x6] | PaletteRAM[0xE]);
- //CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 8:
- CorruptedPalette[0x0] = PaletteRAM[0x2];
- CorruptedPalette[0x4] = PaletteRAM[0x6];
- CorruptedPalette[0x8] = (byte)(PaletteRAM[0x8] | (PaletteRAM[0xA] & PaletteRAM[0xE]));
- CorruptedPalette[0xC] = PaletteRAM[0xE];
- break;
- case 9:
- CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]);
- CorruptedPalette[0x9] = (byte)((PaletteRAM[0xE] | PaletteRAM[0xA] | 0x01) & PaletteRAM[0x9]);
- CorruptedPalette[0xB] = (byte)((PaletteRAM[0xE] | PaletteRAM[0xA] | 0x31) & PaletteRAM[0xB]);
- CorruptedPalette[0xD] = PaletteRAM[0xE];
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 0xA:
- break;
- case 0xB:
- CorruptedPalette[0xB] &= (byte)(PaletteRAM[0xA] | PaletteRAM[0xE]);
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 0xC:
- CorruptedPalette[0x0] = PaletteRAM[0x2];
- CorruptedPalette[0x4] = PaletteRAM[0x6];
- CorruptedPalette[0x8] = PaletteRAM[0xA];
- CorruptedPalette[0xC] = PaletteRAM[0xE];
- break;
- case 0xD:
- CorruptedPalette[0x1] = (byte)((PaletteRAM[0x2] | PaletteRAM[0x1]) & PaletteRAM[0x3]);
- CorruptedPalette[0x5] = (byte)((PaletteRAM[0x6] | PaletteRAM[0x5]) & PaletteRAM[0x7]);
- CorruptedPalette[0x9] = (byte)((PaletteRAM[0xA] | PaletteRAM[0x9]) & PaletteRAM[0xB]);
- CorruptedPalette[0xD] = PaletteRAM[0xE];
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- case 0xE:
- break;
- case 0xF:
- CorruptedPalette[0xF] = PaletteRAM[0xE];
- break;
- }
-
-
- break;
- case 3:
-
- // To be honest, I'm not sure what's going on, so forgive the lack of comments.
- // There's almost a pattern, but again- unsure on why this is how it behaves.
- // and also it's likely this isn't entirely accurate, either due to mistyping something, or not enough research.
-
- switch (PPU_v & 0xF)
- {
- case 0:
- CorruptedPalette[0x0] = (byte)((PaletteRAM[0x3] | (PaletteRAM[0xF] & PaletteRAM[0x0])));
- CorruptedPalette[0x4] &= PaletteRAM[0x7];
- CorruptedPalette[0x8] &= (byte)(PaletteRAM[0x9] | PaletteRAM[0xA] | PaletteRAM[0xB] | PaletteRAM[0xF] | 0x22); // magic number... Probably a temperature thing? I've seen 02, 22, 2C, or 2E
- CorruptedPalette[0xC] = PaletteRAM[0xF];
- break;
- case 1:
- CorruptedPalette[0x1] = (byte)((PaletteRAM[0x1] | PaletteRAM[0xF]) & PaletteRAM[0x3]);
- CorruptedPalette[0x5] = PaletteRAM[0x7];
- CorruptedPalette[0x9] = PaletteRAM[0xB];
- CorruptedPalette[0xD] = PaletteRAM[0xF];
- break;
- case 2:
- CorruptedPalette[0x2] = (byte)((PaletteRAM[0x3] | PaletteRAM[0xF]) & PaletteRAM[0x3]);
- CorruptedPalette[0x6] = PaletteRAM[0x7];
- CorruptedPalette[0xA] = PaletteRAM[0xB];
- CorruptedPalette[0xE] = PaletteRAM[0xF];
- break;
- case 3:
- break;
- case 4:
- CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x7); // magic number... I've only seen it as 07 though.
- CorruptedPalette[0x4] &= (byte)(PaletteRAM[0x7] | PaletteRAM[0xF]);
- CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | PaletteRAM[0xF] | (PaletteRAM[0xC] ^ 0xFF));
- CorruptedPalette[0xC] = (byte)((PaletteRAM[0x7] & PaletteRAM[0xF]) | PaletteRAM[0xC]);
- break;
- case 5:
- CorruptedPalette[0x1] = PaletteRAM[0x3];
- CorruptedPalette[0x5] = (byte)((PaletteRAM[0x5] | PaletteRAM[0xF]) & PaletteRAM[0x7]);
- CorruptedPalette[0x9] = PaletteRAM[0xB];
- CorruptedPalette[0xD] = PaletteRAM[0xF];
- break;
- case 6:
- CorruptedPalette[0x2] = PaletteRAM[0x3];
- CorruptedPalette[0x6] = (byte)((PaletteRAM[0x6] | PaletteRAM[0xF]) & PaletteRAM[0x7]);
- CorruptedPalette[0xA] = PaletteRAM[0xB];
- CorruptedPalette[0xE] = PaletteRAM[0xF];
- break;
- case 7:
- break;
- case 8:
- CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x23); // magic number... I've only seen it as 23 though.
- CorruptedPalette[0x4] = (byte)(PaletteRAM[0x7]);
- CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | PaletteRAM[0xF] | (PaletteRAM[0xC] ^ 0xFF));
- CorruptedPalette[0xC] = (byte)((PaletteRAM[0xB] & PaletteRAM[0xF]) | PaletteRAM[0xC]);
- break;
- case 9:
- CorruptedPalette[0x1] = PaletteRAM[0x3];
- CorruptedPalette[0x5] = PaletteRAM[0x7];
- CorruptedPalette[0x9] = (byte)((PaletteRAM[0x9] | PaletteRAM[0xF]) & PaletteRAM[0xB]);
- CorruptedPalette[0xD] = PaletteRAM[0xF];
- break;
- case 0xA:
- CorruptedPalette[0x2] = PaletteRAM[0x3];
- CorruptedPalette[0x6] = PaletteRAM[0x7];
- CorruptedPalette[0xA] = (byte)((PaletteRAM[0xA] | PaletteRAM[0xF]) & PaletteRAM[0xB]);
- CorruptedPalette[0xE] = PaletteRAM[0xF];
- break;
- case 0xB:
- break;
- case 0xC:
- CorruptedPalette[0x0] &= (byte)(((PaletteRAM[0xF] ^ 0xFF)) | PaletteRAM[0x1] | PaletteRAM[0x2] | PaletteRAM[0x3] | 0x37); // magic number... I've only seen it as 23 though.
- CorruptedPalette[0x4] = PaletteRAM[0x7];
- CorruptedPalette[0x8] &= (byte)(PaletteRAM[0xB] | 0x2F); // Magic number. I've seen 2F and 2E
- CorruptedPalette[0xC] = PaletteRAM[0xF];
- break;
- case 0xD:
- CorruptedPalette[0x1] = PaletteRAM[0x3];
- CorruptedPalette[0x5] = PaletteRAM[0x7];
- CorruptedPalette[0x9] = PaletteRAM[0xB];
- CorruptedPalette[0xD] = PaletteRAM[0xF];
- break;
- case 0xE:
- CorruptedPalette[0x2] = PaletteRAM[0x3];
- CorruptedPalette[0x6] = PaletteRAM[0x7];
- CorruptedPalette[0xA] = PaletteRAM[0xB];
- CorruptedPalette[0xE] = PaletteRAM[0xF];
- break;
- case 0xF:
- break;
- }
-
- break;
-
-
- }
- for (int i = 0; i < CorruptedPalette.Length; i++)
- {
- PaletteRAM[i] = CorruptedPalette[i];
- }
-
-
- }
-
-
- byte PPU_RenderTemp; // a variable used in the following function to store information between ppu cycles.
- bool PPU_Commit_NametableFetch;
- bool PPU_Commit_AttributeFetch;
- bool PPU_Commit_PatternLowFetch;
- bool PPU_Commit_PatternHighFetch;
-
- void PPU_Render_ShiftRegistersAndBitPlanes()
- {
- byte cycleTick; // for the switch statement below, this checks which case to run on a given ppu cycle.
- cycleTick = (byte)((PPU_Dot+7) & 7);
-
- if (PPU_Dot >= 257 && PPU_Dot <= 320)
- {
- cycleTick = 1;
- }
-
- if (PPU_ALE && PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
-
- switch (cycleTick)
- {
- case 0:
- PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- PPU_PAR_MUX = PPU_PatternAddressRegister_NT;
- PPU_AddressBus = PPU_PAR_MUX;
- break;
- case 1:
- // fetch byte from Nametable
- PPU_PatternAddressRegister_NT = (ushort)(0x2000 + (PPU_v & 0x0FFF)); // this happens again.
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_NT & 0xFF00) | PPU_OctalLatch);
- PPU_RenderTemp = FetchVideoMemory();
- PPU_Commit_NametableFetch = true;
- break;
- case 2:
- PPU_PatternAddressRegister_AT = (ushort)(0x23C0 | (PPU_v & 0x0C00) | ((PPU_v >> 4) & 0x38) | ((PPU_v >> 2) & 0x07));
- PPU_PAR_MUX = PPU_PatternAddressRegister_AT;
- PPU_AddressBus = PPU_PAR_MUX;
- break;
- case 3:
- // fetch attribute byte from attribute table
- PPU_PatternAddressRegister_AT = (ushort)(0x23C0 | (PPU_v & 0x0C00) | ((PPU_v >> 4) & 0x38) | ((PPU_v >> 2) & 0x07)); // this happens again.
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_AT & 0xFF00) | PPU_OctalLatch);
- PPU_RenderTemp = FetchVideoMemory();
- PPU_Commit_AttributeFetch = true;
- // now we only have the 2 bits we're looking for
- break;
- case 4:
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR &= 0b1111111110111;
- PPU_PAR_MUX = PPU_PatternAddressRegister_CHR;
- PPU_AddressBus = PPU_PAR_MUX;
- break;
- case 5:
- // fetch pattern bits from value read off the nametable
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch);
- PPU_RenderTemp = FetchVideoMemory();
- PPU_Commit_PatternLowFetch = true;
- break;
- case 6:
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR |= 8;
- PPU_PAR_MUX = PPU_PatternAddressRegister_CHR;
- PPU_AddressBus = PPU_PAR_MUX;
- break;
- case 7:
- // fetch pattern bits with the new address
- PPU_AddressBus = (ushort)((PPU_PatternAddressRegister_CHR & 0xFF00) | PPU_OctalLatch);
- PPU_RenderTemp = FetchVideoMemory();
- PPU_Commit_PatternHighFetch = true;
- break;
- }
-
- if (PPU_ALE && !PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
-
- }
-
- void PPU_Render_CommitShiftRegistersAndBitPlanes()
- {
- if (PPU_Commit_NametableFetch)
- {
- PPU_Commit_NametableFetch = false;
- PPU_PatternAddressRegister_CHR &= 0b1000000001111;
- if (PPU_Dot < 256 || PPU_Dot > 320)
- {
- PPU_PatternAddressRegister_CHR |= (ushort)( (byte)(PPU_AddressBus) << 4);
- }
- else
- {
- PPU_PatternAddressRegister_CHR |= (ushort)(PPU_SpritePattern << 4);
- }
- }
- if (PPU_Commit_AttributeFetch)
- {
- PPU_Commit_AttributeFetch = false;
- PPU_Attribute = PPU_RenderTemp;
- // 1 byte of attribute data is 4 tiles worth. determine which tile this is for.
- if ((PPU_v & 3) >= 2) // If this is on the right tile
- {
- PPU_Attribute = (byte)(PPU_Attribute >> 2);
- }
- if ((((PPU_v & 0b0000001111100000) >> 5) & 3) >= 2) // If this is on the bottom tile
- {
- PPU_Attribute = (byte)(PPU_Attribute >> 4);
- }
- PPU_Attribute = (byte)(PPU_Attribute & 3);
- }
- if (PPU_Commit_PatternLowFetch)
- {
- PPU_Commit_PatternLowFetch = false;
- PPU_LowBitPlane = PPU_RenderTemp;
- }
- if (PPU_Commit_PatternHighFetch)
- {
- PPU_Commit_PatternHighFetch = false;
- PPU_HighBitPlane = PPU_RenderTemp;
- PPU_LoadShiftRegisters();
- PPU_IncrementScrollX();
- }
- }
-
- void PPU_Render_ShiftRegistersAndBitPlanes_DummyNT()
- {
-
- if (PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
-
- if (PPU_Dot == 0)
- {
- PPU_CheckPAR();
- PPU_PatternAddressRegister_CHR &= 0b1111111110111;
- if (PPU_Scanline != 261) // This would not occur on the pre-render line.
- {
- PPU_AddressBus = PPU_PatternAddressRegister_CHR;
- }
- }
- else
- {
- byte cycleTick; // for the switch statement below, this checks which case to run on a given ppu cycle.
- cycleTick = (byte)(PPU_Dot - 337);
-
- switch (cycleTick)
- {
- case 0:
- PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- break;
- case 1:
- // fetch byte from Nametable
- PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- PPU_RenderTemp = FetchVideoMemory();
- PPU_Commit_NametableFetch = true;
- break;
- case 2:
- PPU_AddressBus = (ushort)(0x2000 + (PPU_v & 0x0FFF));
- break;
- case 3:
- // fetch attribute byte from attribute table
- PPU_RenderTemp = FetchVideoMemory();
- //IGNORED NT FETCH: This actually doesn't update the NT register.
- break;
- }
- }
- if (PPU_ALE && !PPU_READ)
- {
- Cart.Emu.PPU_OctalLatch = (byte)PPU_AddressBus;
- }
- }
-
- public void PPU_CheckPAR()
- {
- // Some bits in PAR change based on context:
- if(PPU_Dot < 256 || PPU_Dot > 320)
- {
- // Which pattern table do we use for nametable fetches?
- PPU_PatternAddressRegister_CHR &= 0b0111111111000;
- PPU_PatternAddressRegister_CHR |= (ushort)(PPU_PatternSelect_Background ? 0b1000000000000 : 0);
- PPU_PatternAddressRegister_CHR |= (ushort)((PPU_v & 0b0111000000000000) >> 12);
- }
- else
- {
- // Which pattern table do we use for sprite fetches?
- if(!PPU_Spritex16)
- {
- bool flipy = (PPU_SpriteAttr & 0x80) != 0;
- PPU_PatternAddressRegister_CHR &= 0b0111111111000;
- PPU_PatternAddressRegister_CHR |= (ushort)(PPU_PatternSelect_Sprites ? 0b1000000000000 : 0);
- PPU_PatternAddressRegister_CHR |= (ushort)(flipy ? 7-(PPU_OAM_VerticalOffset & 0x7) : (PPU_OAM_VerticalOffset & 0x7));
- }
- else
- {
- bool flipy = (PPU_SpriteAttr & 0x80) != 0;
- PPU_PatternAddressRegister_CHR &= 0b0111111101000;
- PPU_PatternAddressRegister_CHR |= (ushort)(((PPU_SpritePattern & 1) != 0) ? 0b1000000000000 : 0); // Bit 0 of the OAM2 Pattern
- PPU_PatternAddressRegister_CHR |= (ushort)(flipy ? 7 - (PPU_OAM_VerticalOffset & 0x7) : (PPU_OAM_VerticalOffset & 0x7));
- PPU_PatternAddressRegister_CHR |= (ushort)(((PPU_OAM_VerticalOffset & 0x08) ^ (flipy ? 8 : 0)) <<1);
- }
- }
- }
-
- // in sprite evaluation, if a sprite is horizontally mirrored, we need to flip all the order of the bits in the shift register.
- public byte Flip(byte b)
- {
- b = (byte)(((b & 0xF0) >> 4) | ((b & 0xF) << 4));
- b = (byte)(((b & 0xCC) >> 2) | ((b & 0x33) << 2));
- b = (byte)(((b & 0xAA) >> 1) | ((b & 0x55) << 1));
- return b;
- }
-
- void PPU_UpdateBackgroundShiftRegisters()
- {
- PPU_BackgroundPatternShiftRegisterL = (ushort)(PPU_BackgroundPatternShiftRegisterL << 1); // shift 1 bit to the left. Bring in a 0.
- PPU_BackgroundPatternShiftRegisterH = (ushort)((PPU_BackgroundPatternShiftRegisterH << 1) | 1); // shift 1 bit to the left. Bring in a 1.
- PPU_BackgroundAttributeShiftRegisterL = (ushort)((PPU_BackgroundAttributeShiftRegisterL << 1) | (PPU_AttributeLatchRegister & 1)); // shift 1 bit to the left. Bring in Attribute low bit.
- PPU_BackgroundAttributeShiftRegisterH = (ushort)((PPU_BackgroundAttributeShiftRegisterH << 1) | ((PPU_AttributeLatchRegister & 10) >> 1)); // shift 1 bit to the left. Bring in Attribute high bit.
- }
-
- void UpdateSpriteShiftRegisters()
- {
- if (PPU_Dot <= 256) // the shift registers for sprites are shifted after the rendering process.
- {
- // shift all 8 sprite shift registers.
- int i = 0;
- while (i < 8)
- {
- if (PPU_SpriteShifterCounter[i] > 0 && !SkippedPreRenderDot341)
- {
- PPU_SpriteShifterCounter[i]--; // decrement the X position of all objects in secondary OAM. When this is zero, the ppu can draw it.
- }
- else
- {
- if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground)) // this happens if rendering either sprites or background.
- {
- PPU_SpriteShiftRegisterL[i] = (byte)(PPU_SpriteShiftRegisterL[i] << 1); // shift 1 bit to the left.
- PPU_SpriteShiftRegisterH[i] = (byte)(PPU_SpriteShiftRegisterH[i] << 1); // shift 1 bit to the left.
- }
- }
- i++;
- }
- }
- }
-
- void PPU_LoadShiftRegisters()
- {
- // this runs as the first step of PPU_Render_ShiftRegistersAndBitPlanes(), using the values determined by the previous 8 steps of PPU_Render_ShiftRegistersAndBitPlanes().
- PPU_BackgroundPatternShiftRegisterL = (ushort)((PPU_BackgroundPatternShiftRegisterL & 0xFF00) | PPU_LowBitPlane);
- PPU_BackgroundPatternShiftRegisterH = (ushort)((PPU_BackgroundPatternShiftRegisterH & 0xFF00) | PPU_HighBitPlane);
- PPU_AttributeLatchRegister = PPU_Attribute;
- }
-
- void PPU_IncrementScrollX()
- {
- // used when setting up shift registers for the background
- // update the v register. Either increment it, or reset the scroll
- if ((PPU_v & 0x001F) == 31)
- {
- PPU_v &= 0xFFE0; // resetting the scroll
- PPU_v ^= 0x0400;
- }
- else
- {
- PPU_v++; // increment
- }
- PPU_v &= 0x7FFF;
- }
-
- void PPU_IncrementScrollY()
- {
- if (CopyV)
- {
- PPU_v = (ushort)(PPU_Update2006Value_Temp & PPU_Update2006Value); // This isn't actually accurate. More research needed.
- }
- else
- {
- if ((PPU_v & 0x7000) != 0x7000)
- {
- PPU_v += 0x1000;
- }
- else
- {
- PPU_v &= 0x0FFF;
- int y = (PPU_v & 0x03E0) >> 5;
- if (y == 29)
- {
- y = 0; // reset the Y value and also flip some other bit in the 'v' register
- PPU_v ^= 0x0800;
- }
- else if (y == 31)
- {
- y = 0; // reset the Y value
- }
- else
- {
- y++; // increment the Y value
- }
- PPU_v = (ushort)((PPU_v & 0xFC1F) | (y << 5));
- }
- }
- PPU_v &= 0x7FFF;
- }
-
- void PPU_ResetXScroll()
- {
- // If a write to $2000 occurs during this ppu cycle, PPU_t will be the incorrect value!
- // The value of PPU_t will be corrected on the next ppu cycle, but it's already too late.
- // This is the "scanline bug" : https://www.nesdev.org/wiki/PPU_glitches#PPUCTRL
- // The bug is only visible if the nametable mirroring is vertical.
- PPU_v = (ushort)((PPU_v & 0b0111101111100000) | (PPU_t & 0b0000010000011111));
- }
- void PPU_ResetYScroll()
- {
- // The exact same issue from PPU_ResetXScroll() can happen here too, except this corrupts an entire frame.
- // The bug is only visible if the nametable mirroring is horizontal.
- //PPU_t = (ushort)((PPU_t & 0b0111110000011111) | (0b000001111000000)); //Uncomment this line to experiment with the "Attirbutes as tiles" bug.
- PPU_v = (ushort)((PPU_v & 0b0000010000011111) | (PPU_t & 0b0111101111100000));
- }
-
- void DecayPPUDataBus()
- {
- int i = 0;
- while (i < PPUBusDecay.Length)
- {
- if (PPUBusDecay[i] > 0)
- {
- PPUBusDecay[i]--;
- if (PPUBusDecay[i] == 0)
- {
- PPUBus &= DecayBitmask[i];
- }
- }
- i++;
- }
- }
- byte[] DecayBitmask = { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F };
-
- // The object attribute memory DMA!
- bool OAMDMA_Aligned = false;
- bool OAMDMA_Halt = false;
- bool DMCDMA_Halt = false;
- byte OAM_InternalBus; // a data bus that's used for the OAM DMA
- ushort OAMAddressBus; // the address bus of the OAM DMA
-
- // The DMAs (Direct Memory Accesses) Have "get" and "put" cycles.
- // they can also be "halted" in which case, it will always read instead of write.
-
- // the following functions,
- // OAMDMA_Get() : Get cycles are reads
- // OAMDMA_Halted() : Halted gets and halted puts are both reads from the current address bus
- // OAMDMA_Put() : Put cycles are writes to OAM.
-
- // DMCDMA_Get() : Get cycles are reads
- // DMCDMA_Halted() : Halted gets and halted puts are both reads from the current address bus
- // DMCDMA_Put() : Put cycles are writes to the DMC shifter.
-
- void OAMDMA_Get()
- {
- OAMAddressBus = (ushort)(DMAPage << 8 | DMAAddress);
- OAMDMA_Aligned = true;
- // the fetch happens regardless of halt
- OAM_InternalBus = Fetch(OAMAddressBus);
- }
- void OAMDMA_Halted()
- {
- Fetch(addressBus); // if halted, just read from the current address bus.
- }
-
- void OAMDMA_Put()
- {
-
- if (OAMDMA_Aligned) // if the DMA is aligned
- {
- Store(OAM_InternalBus, 0x2004); // write to OAM
- DMAAddress++;
- if (DMAAddress == 0) // if we overflow the DMA address
- {
- DoOAMDMA = false; // we have completed the DMA.
- OAMDMA_Aligned = false;
- return;
- }
- }
- else // if this is an alignment cycle
- {
- Fetch(addressBus); // just read from the current address bus
- }
-
- }
-
- void DMCDMA_Get()
- {
- // now reload the DMC buffer.
- APU_DMC_Buffer = Fetch(APU_DMC_AddressCounter);
-
- APU_DMC_AddressCounter++;
- if (APU_DMC_AddressCounter == 0)
- {
- APU_DMC_AddressCounter = 0x8000;
- }
- if (APU_DMC_BytesRemaining > 0)
- {
- // due to writes to $4015 setting the BytesRemaining to 0 if disabled, this could potentially underflow without the if statement.
- APU_DMC_BytesRemaining--;
- }
-
- if (APU_DMC_BytesRemaining == 0)
- {
- //reset sample
-
- if (!APU_DMC_Loop)
- {
- APU_Status_DMC = false;
- if (APU_DMC_EnableIRQ) // if the DMC should fire an IRQ when it completes...
- {
- IRQ_LevelDetector = true;
- APU_Status_DMCInterrupt = true;
- }
- }
- else
- {
- StartDMCSample();
- }
- }
- DoDMCDMA = false;
- OAMDMA_Aligned = false;
- CannotRunDMCDMARightNow = 2;
-
- }
-
- void DMCDMA_Halted()
- {
- Fetch(addressBus);
- }
- void DMCDMA_Put()
- {
- Fetch(addressBus);
- }
-
- // Typically in the last CPU cycle of an instruction, the console will check if the NMI edge detector or IRQ level detector is set. In which case, it's time to run an interrupt.
- // The timing on this is different for branch instructions, and the BRK instruction doesn't do this at all.
- void PollInterrupts()
- {
- NMI_PreviousPinsSignal = NMI_PinsSignal;
- NMI_PinsSignal = NMILine;
- if (NMI_PinsSignal && !NMI_PreviousPinsSignal)
- {
- DoNMI = true;
- }
- DoIRQ = IRQLine && !flag_Interrupt;
- }
-
- void PollInterrupts_CantDisableIRQ()
- {
- NMI_PreviousPinsSignal = NMI_PinsSignal;
- NMI_PinsSignal = NMILine;
- if (NMI_PinsSignal && !NMI_PreviousPinsSignal)
- {
- DoNMI = true;
- }
- if (!DoIRQ)
- {
- DoIRQ = IRQLine && !flag_Interrupt;
- }
- }
-
- void CompleteOperation()
- {
- CPU_SYNC = true;
- operationCycle = 0xFF; // this will be incremented to 0.
- addressBus = programCounter;
- CPU_Read = true;
- IgnoreH = false;
- }
-
- public void _6502()
- {
-
- if ((DoDMCDMA && (APU_Status_DMC || APU_ImplicitAbortDMC4015) && CPU_Read) || (DoOAMDMA && CPU_Read)) // Are we running a DMA? Did it fail? Also some specific behavior can force a DMA to abort. Did that occur?
- {
- if (
- (opCode == 0x93 && operationCycle == 4) ||
- (opCode == 0x9B && operationCycle == 3) ||
- (opCode == 0x9C && operationCycle == 3) ||
- (opCode == 0x9E && operationCycle == 3) ||
- (opCode == 0x9F && operationCycle == 3)
- )
- {
- IgnoreH = true;
- }
-
- if (DoOAMDMA && FirstCycleOfOAMDMA)
- {
- FirstCycleOfOAMDMA = false;
- if (!APU_PutCycle) // if the first cycle of an OAM DMA is a get cycle, it's a halt cycle.
- {
- OAMDMA_Halt = true;
- }
- }
-
- if (APU_PutCycle)
- {
- // Put cycle (write)
- if (DoDMCDMA && DoOAMDMA) // if we're running both a DMC and OAM DMA.
- {
- if (DMCDMA_Halt && OAMDMA_Halt) // both halt cycles
- {
- OAMDMA_Halted();
- }
- else if (!OAMDMA_Halt && DMCDMA_Halt) // only DMC halted
- {
- OAMDMA_Put();
- }
- else if (OAMDMA_Halt && !DMCDMA_Halt) // only OAM halted
- {
- DMCDMA_Put(); // Can this logically ever happen?
- }
- else // none halted : OAM DMA has priority
- {
- OAMDMA_Put();
- }
- }
- else // only performing a single DMA
- {
- if (DoDMCDMA) // only running DMC DMA
- {
- if (DMCDMA_Halt)
- {
- DMCDMA_Halted();
- }
- else
- {
- DMCDMA_Put();
- }
- }
- else // only running OAM DMA
- {
- if (OAMDMA_Halt)
- {
- OAMDMA_Halted();
- }
- else
- {
- OAMDMA_Put();
- }
- }
- }
- }
- else
- {
- // Get cycle (read)
- if (DoDMCDMA && DoOAMDMA) // if we're running both a DMC and OAM DMA.
- {
- if (DMCDMA_Halt && OAMDMA_Halt) // both halt cycles
- {
- DMCDMA_Halted();
- }
- else if (!OAMDMA_Halt && DMCDMA_Halt) // only DMC halted
- {
- OAMDMA_Get();
- }
- else if (OAMDMA_Halt && !DMCDMA_Halt) // only OAM halted
- {
- DMCDMA_Get();
- }
- else // none halted : DMC DMA has priority
- {
- DMCDMA_Get();
- }
- }
- else
- {
- // only performing a single DMA
- if (DoDMCDMA) // only running DMC DMA
- {
- if (DMCDMA_Halt)
- {
- DMCDMA_Halted();
- }
- else
- {
- DMCDMA_Get();
- }
- }
- else // only running OAM DMA
- {
- if (OAMDMA_Halt)
- {
- OAMDMA_Halted();
- }
- else
- {
- OAMDMA_Get();
- }
- }
- }
-
- DMCDMA_Halt = false; // both halt cycles get cleared after a get cycle.
- OAMDMA_Halt = false;
- }
-
- }
- else if (CPU_SYNC) // We are not running any DMAs, and this is the first cycle of an instruction.
- {
- CPU_SYNC = false;
- // cycle 0. fetch opcode:
- addressBus = programCounter;
-
- opCode = Fetch(addressBus); // Fetch the value at the program counter. This is the opcode.
-
-
- if (DoNMI) // If an NMI is occurring,
- {
- opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for NMIs)
- }
- else if (DoIRQ) // If an IRQ is occurring,
- {
- opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for IRQs)
- }
- else if (DoReset) // If a RESET is occurring,
- {
- opCode = 0; // replace the opcode with 0. (A BRK, which has modified behavior for RESETs)
- }
- else if (opCode == 0) // Otherwise, if an interrupt is not occurring, and the opcode is already 0
- {
- DoBRK = true; // There's also specific behavior for the BRK instruction if it is in-fact a BRK, and not an interrupt.
- }
-
-
- if (Logging && !LoggingPPU) // For debugging only.
- {
- Debug(); // This is where the tracelogger occurs.
- }
- if ((!DoNMI && !DoIRQ && !DoReset)) // If we aren't running any interrupts...
- {
- programCounter++; // the PC is incremented to the next address
- addressBus = programCounter;
- }
-
- operationCycle = 1; // set this for use in the following CPU cycle.
-
- }
- else
- {
- // a really big switch statement.
- // depending on the value of the opcode, different behavior will take place.
- // this is how instructions work.
-
- // All instructions are labeled. If it's an undocumented opcode, I also write "***" next to it.
-
- switch (opCode)
- {
- case 0x00: //BRK
- switch (operationCycle)
- {
- case 1:
- if (!DoBRK)
- {
- Fetch(addressBus); //dummy fetch without incrementing PC.
- }
- else
- {
- GetImmediate(); //dummy fetch and PC increment
- }
- break;
- case 2:
- if (!DoReset)
- {
- Push((byte)(programCounter >> 8));
- }
- else
- {
- ResetReadPush();
- }
- break;
- case 3:
- if (!DoReset)
- {
- Push((byte)programCounter);
- }
- else
- {
- ResetReadPush();
- }
- break;
- case 4:
- if (!DoReset)
- {
- status = flag_Carry ? (byte)0x01 : (byte)0;
- status |= flag_Zero ? (byte)0x02 : (byte)0;
- status |= flag_Interrupt ? (byte)0x04 : (byte)0;
- status |= flag_Decimal ? (byte)0x08 : (byte)0;
- status |= DoBRK ? (byte)0x10 : (byte)0;
- status |= 0x20;
- status |= flag_Overflow ? (byte)0x40 : (byte)0;
- status |= flag_Negative ? (byte)0x80 : (byte)0;
- Push(status);
- }
- else
- {
- ResetReadPush();
- }
- PollInterrupts(); // check for NMI?
- break;
- case 5:
- if (DoNMI)
- {
- programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFA)));
- }
- else if (DoReset)
- {
- programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFC)));
- }
- else
- {
- programCounter = (ushort)((programCounter & 0xFF00) | (Fetch(0xFFFE)));
- }
-
- break;
- case 6:
- if (DoNMI)
- {
- programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFB) << 8));
- }
- else if (DoReset)
- {
- programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFD) << 8));
- }
- else
- {
- programCounter = (ushort)((programCounter & 0xFF) | (Fetch(0xFFFF) << 8));
- }
-
- CompleteOperation(); // notably, BRK does not check the NMI edge detector at the end of the instruction
- DoReset = false;
-
- DoNMI = false;
- DoIRQ = false;
- IRQLine = false;
-
- DoBRK = false;
-
- flag_Interrupt = true;
-
-
-
- break;
- }
- break;
-
- case 0x01: //(ORA, X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x02: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x03: //(SLO, X) ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x04: //NOP zp ***
- if (operationCycle == 1)
- {
- GetAddressZeroPage();
- }
- else
- {
- // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- }
- break;
-
- case 0x05: //ORA zp
- if (operationCycle == 1)
- {
- GetAddressZeroPage();
- }
- else
- {
- // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- }
- break;
-
- case 0x06: //ASL, zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_ASL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x07: //SLO zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x08: //PHP
-
- if (operationCycle == 1)
- {
- //dummy fetch
- Fetch(addressBus);
- }
- else
- {
- PollInterrupts();
- // read from address
- status = flag_Carry ? (byte)0x01 : (byte)0;
- status += flag_Zero ? (byte)0x02 : (byte)0;
- status += flag_Interrupt ? (byte)0x04 : (byte)0;
- status += flag_Decimal ? (byte)0x08 : (byte)0;
- status += 0x10; //always set in PHP
- status += 0x20; //always set in PHP
- status += flag_Overflow ? (byte)0x40 : (byte)0;
- status += flag_Negative ? (byte)0x80 : (byte)0;
- Push(status);
- CompleteOperation();
- }
- break;
-
- case 0x09: //ORA Imm
- PollInterrupts();
- GetImmediate();
- Op_ORA(dl);
- CompleteOperation();
- break;
-
- case 0x0A: //ASL A
- PollInterrupts();
- Fetch(addressBus); // dummy read
- Op_ASL_A();
- CompleteOperation();
- break;
-
- case 0x0B: //ANC Imm ***
- PollInterrupts();
- GetImmediate();
- A = (byte)(A & dl);
- flag_Carry = A >= 0x80;
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0x0C: //NOP Absolute ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x0D: //ORA Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x0E: //ASL, Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ASL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x0F: //SLO Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x10: //BPL
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (flag_Negative)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x11: //(ORA) Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x12: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x13: //(SLO) Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x14: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x15: //ORA zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x16: //ASL, zp X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ASL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x17: //SLO zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x18: //CLC
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Carry = false;
- CompleteOperation();
- break;
-
- case 0x19: //ORA Abs, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x1A: //NOP ***
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
-
- case 0x1B: //SLO Abs Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x1C: //NOP Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x1D: //ORA Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_ORA(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x1E: //ASL, Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_ASL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
-
- case 0x1F: //SLO Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_SLO(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x20: //JSR
-
- switch (operationCycle)
- {
- // this is pretty cursed, though according to visual6502, this is apparently what happens.
- case 1: // fetch the byte that will be PC low
- addressBus = programCounter;
- dl = Fetch(addressBus);
- programCounter++;
- break;
- case 2: // transfer stack pointer to address bus, and alu to stack pointer. I'm just reusing `dl` here, but this instruction actually uses the Arithmetic Logic Unit for this.
- addressBus = (ushort)(0x100 | stackPointer);
- stackPointer = dl;
- CPU_Read = false;
- Fetch(addressBus); // dummy read
- break;
- case 3: // push PC high to stack via address bus
- Store((byte)((programCounter & 0xFF00) >> 8), addressBus);
- addressBus = (ushort)((byte)(addressBus - 1) | 0x100);
- break;
- case 4: // push PC low to stack via address bus
- Store((byte)(programCounter & 0xFF), addressBus);
- addressBus = (ushort)((byte)(addressBus - 1) | 0x100);
- specialBus = (byte)addressBus;
- CPU_Read = true;
- break;
- case 5: // fetch PC High, transfer stack pointer to PC low, address bus to stack pointer.
- PollInterrupts();
- addressBus = programCounter;
- programCounter = (ushort)((Fetch(addressBus) << 8) | stackPointer);
- stackPointer = specialBus;
- CompleteOperation();
- break;
- }
- break;
-
- case 0x21: //(AND, X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x22: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x23: //(RLA, X) ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x24: //BIT Zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- dl = Fetch(addressBus);
- flag_Zero = (A & dl) == 0;
- flag_Negative = (dl & 0x80) != 0;
- flag_Overflow = (dl & 0x40) != 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0x25: //AND zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x26: //ROL zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_ROL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x27: //RLA zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x28: //PLP
- switch (operationCycle)
- {
- case 1: //dummy fetch
- Fetch(addressBus);
- break;
- case 2: //increment S
- addressBus = (ushort)(0x100 + stackPointer);
- Fetch(addressBus); // dummy read
- stackPointer++;
- break;
- case 3: // read from address
- PollInterrupts();
- addressBus = (ushort)(0x100 + stackPointer);
- status = Fetch(addressBus);
- flag_Carry = (status & 1) == 1;
- flag_Zero = ((status & 0x02) >> 1) == 1;
- flag_Interrupt = ((status & 0x04) >> 2) == 1;
- flag_Decimal = ((status & 0x08) >> 3) == 1;
- flag_Overflow = ((status & 0x40) >> 6) == 1;
- flag_Negative = ((status & 0x80) >> 7) == 1;
- CompleteOperation();
- break;
- }
- break;
-
- case 0x29: //AND Imm
- PollInterrupts();
- GetImmediate();
- Op_AND(dl);
- CompleteOperation();
- break;
-
- case 0x2A: //ROL A
- PollInterrupts();
- Fetch(addressBus); // dummy read
- Op_ROL_A();
- CompleteOperation();
- break;
-
- case 0x2B: //ANC Imm *** (same as 0x0B)
- PollInterrupts();
- GetImmediate();
- A = (byte)(A & dl);
- flag_Carry = A >= 0x80;
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0x2C: //BIT Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- dl = Fetch(addressBus);
- flag_Zero = (A & dl) == 0;
- flag_Negative = (dl & 0x80) != 0;
- flag_Overflow = (dl & 0x40) != 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0x2D: //AND Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x2E: //ROL Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ROL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x2F: //RLA Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x30: //BMI
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (!flag_Negative)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x31: //(AND), Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x32: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
- case 0x33: //(RLA), Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x34: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x35: //AND zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x36: //ROL zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ROL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x37: //RLA zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x38: //SEC
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Carry = true;
- CompleteOperation();
- break;
-
- case 0x39: //AND Abs, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x3A: //NOP ***
- PollInterrupts();
- addressBus = programCounter; Fetch(addressBus);
- CompleteOperation();
- break;
-
- case 0x3B: //RLA Abs, Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x3C: //NOP Absolute, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x3D: //AND Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_AND(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x3E: //ROL Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_ROL(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x3F: //RLA Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_RLA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x40: //RTI
- switch (operationCycle)
- {
- case 1:
- GetImmediate();
- break;
- case 2:
- addressBus = (ushort)(0x100 | stackPointer);
- Fetch(addressBus);
- addressBus = (ushort)((byte)(addressBus + 1) | 0x100);
- break;
- case 3:
- status = Fetch(addressBus);
- flag_Carry = (status & 1) != 0;
- flag_Zero = (status & 0x02) != 0;
- flag_Interrupt = (status & 0x04) != 0;
- flag_Decimal = (status & 0x08) != 0;
- flag_Overflow = (status & 0x40) != 0;
- flag_Negative = (status & 0x80) != 0;
-
- addressBus = (ushort)((byte)(addressBus + 1) | 0x100);
- break;
- case 4:
- dl = Fetch(addressBus);
- programCounter = (ushort)((programCounter & 0xFF00) | dl); //technically not accurate, as this happens in cycle 5
- addressBus = (ushort)((byte)(addressBus + 1) | 0x100);
- break;
- case 5:
- PollInterrupts();
- dl = Fetch(addressBus);
- programCounter = (ushort)((programCounter & 0xFF) | (dl << 8));
- stackPointer = (byte)addressBus;
- CompleteOperation();
- break;
-
- }
- break;
-
- case 0x41: //(EOR X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x42: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x43: //(SRE, X) ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x44: //NOP zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x45: //EOR zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x46: //LSR zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_LSR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x47: //SRE zp ***
-
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x48: //PHA
-
- switch (operationCycle)
- {
- case 1: //dummy fetch
- dl = Fetch(addressBus);
- break;
- case 2: // read from address
- PollInterrupts();
- Push(A);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x49: //EOR Imm
- PollInterrupts();
- GetImmediate();
- Op_EOR(dl);
- CompleteOperation();
- break;
-
- case 0x4A: //LSR A
- PollInterrupts();
- Fetch(addressBus); // dummy read
- Op_LSR_A();
- CompleteOperation();
- break;
-
- case 0x4B: //ASR Imm ***
- PollInterrupts();
- GetImmediate();
- A = (byte)(A & dl);
- Op_LSR_A();
- CompleteOperation();
- break;
-
- case 0x4C: //JMP
- if (operationCycle == 1)
- {
- GetAddressAbsolute();
-
- }
- else
- {
- PollInterrupts();
- GetAddressAbsolute();
- programCounter = addressBus;
- CompleteOperation();
- }
- break;
-
- case 0x4D: //EOR Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x4E: //LSR abs
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_LSR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x4F: //SRE abs ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x50: //BVC
-
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (flag_Overflow)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x51: //(EOR), Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x52: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x53: //(SRE) Y ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x54: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x55: //EOR zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x56: //LSR zp, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_LSR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x57: //SRE zp X ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x58: //CLI
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Interrupt = false;
- CompleteOperation();
- break;
-
- case 0x59: //EOR Abs Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x5A: //NOP ***
- PollInterrupts();
- addressBus = programCounter; Fetch(addressBus);
- CompleteOperation();
- break;
-
- case 0x5B: //SRE abs, Y ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x5C: //NOP Absolute, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x5D: //EOR Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_EOR(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x5E: //LSR abs, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_LSR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x5F: //SRE abs, X ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_SRE(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x60: //RTS
-
-
- switch (operationCycle)
- {
- case 1:
- GetImmediate();
- break;
- case 2:
- addressBus = (ushort)(0x100 | stackPointer);
- Fetch(addressBus);
- addressBus = (ushort)((byte)(addressBus + 1) | 0x100);
- break;
- case 3:
- dl = Fetch(addressBus);
- programCounter = (ushort)((programCounter & 0xFF00) | dl); //technically not accurate, as this happens in cycle 5
- addressBus = (ushort)((byte)(addressBus + 1) | 0x100);
- break;
- case 4:
- dl = Fetch(addressBus);
- programCounter = (ushort)((programCounter & 0xFF) | (dl << 8));
- break;
- case 5:
- PollInterrupts();
- stackPointer = (byte)addressBus;
- GetImmediate();
- CompleteOperation();
- break;
-
- }
- break;
-
- case 0x61: //(ADC X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x62: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x63: //(RRA X) ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x64: //NOP zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x65: //ADC Zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x66: //ROR zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_ROR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x67: //RRA zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
- case 0x68: //PLA
-
- switch (operationCycle)
- {
- case 1: //dummy fetch
- addressBus = programCounter;
- Fetch(addressBus);
- break;
- case 2: // read from address
- addressBus = (ushort)(0x100 | (stackPointer));
- Fetch(addressBus); // dummy read
- stackPointer++;
- break;
- case 3: // read from address
- PollInterrupts();
- addressBus = (ushort)(0x100 | (stackPointer));
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0x69: //ADC Imm
- PollInterrupts();
- GetImmediate();
- Op_ADC(dl);
- CompleteOperation();
- break;
-
- case 0x6A: //ROR A
- PollInterrupts();
- Fetch(addressBus); // dummy read
- Op_ROR_A();
- CompleteOperation();
- break;
-
- case 0x6B: // ARR ***
- PollInterrupts();
- GetImmediate();
- A = (byte)(A & dl);
- Op_ROR_A();
- flag_Zero = A == 0;
- flag_Carry = ((A & 0x40) >> 6) == 1;
- flag_Overflow = (((A & 0x20) >> 5) ^ ((A & 0x40) >> 6)) == 1;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
-
- case 0x6C: //JMP (indirect)
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3:
- specialBus = Fetch(addressBus); // Okay, this doesn't actually use the SB register. I'm just reusing that variable.
- break;
- case 4:
- PollInterrupts();
- dl = Fetch((ushort)((addressBus & 0xFF00) | (byte)(addressBus + 1)));
- programCounter = (ushort)((dl << 8) | specialBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x6D: //ADC Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x6E: //ROR Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ROR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x6F: //RRA Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x70: //BVS
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (!flag_Overflow)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x71: //(ADC), Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x72: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x73: //(RRA) Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x74: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x75: //ADC zp, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x76: //ROR zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_ROR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x77: //RRA zp X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x78: //SEI
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Interrupt = true;
- CompleteOperation();
- break;
- case 0x79: //ADC Abs, Y
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x7A: //NOP ***
- PollInterrupts();
- addressBus = programCounter;
- Fetch(addressBus);
- CompleteOperation();
- break;
-
- case 0x7B: //RRA Abs, Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x7C: //NOP Absolute, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x7D: //ADC Abs, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_ADC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x7E: //ROR Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_ROR(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x7F: //RRA Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_RRA(dl, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x80: //NOP Immediate ***
- PollInterrupts();
- GetImmediate();
- CompleteOperation();
- break;
-
-
- case 0x81: //(STA X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5: // read from address
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x82: //NOP Immediate ***
- PollInterrupts();
- GetImmediate();
- CompleteOperation();
- break;
-
- case 0x83: //(SAX X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5: // read from address
- PollInterrupts();
- Store((byte)(A & X), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x84: //STY zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- CPU_Read = false;
- break;
- case 2: // read from address
- PollInterrupts();
- Store(Y, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x85: //STA zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- CPU_Read = false;
- break;
- case 2:
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x86: //STX zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- CPU_Read = false;
- break;
- case 2:
- PollInterrupts();
- Store(X, addressBus);
- CompleteOperation();
- break;
- }
- break;
- case 0x87: //SAX zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- CPU_Read = false;
- break;
- case 2:
- PollInterrupts();
- Store((byte)(A & X), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x88: //DEY
-
- PollInterrupts();
- Y--;
- flag_Zero = Y == 0;
- flag_Negative = Y >= 0x80;
- Fetch(addressBus); // dummy read
- CompleteOperation();
-
- break;
-
- case 0x89: //NOP Immediate ***
- PollInterrupts();
- GetImmediate();
- CompleteOperation();
-
- break;
-
- case 0x8A: //TXA
- PollInterrupts();
- A = X;
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- Fetch(addressBus); // dummy read
- CompleteOperation();
- break;
-
- case 0x8B: //ANE
- PollInterrupts();
- GetImmediate();
- //A = (((A | 0xFF) & X) & temp);
- // Magic = FF
- A = (byte)((A | 0xFF) & X & dl); // 0xEE is also known as "MAGIC", and can supposedly be different depending on the CPU's temperature.
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
-
- case 0x8C: //STY Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store(Y, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x8D: //STA Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x8E: //STX Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3:
- PollInterrupts();
- Store(X, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x8F: //SAX Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store((byte)(A & X), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x90: //BCC
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (flag_Carry)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0x91: //(STA), Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x92: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0x93: // (SHA) Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- if (operationCycle == 4)
- {
- CPU_Read = false;
- }
- break;
- case 5: // read from address
- PollInterrupts();
- if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00))
- {
- // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable"
- addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHA behavior. The A register isn't used here!
- }
- // pd = the high byte of the target address + 1
- if (IgnoreH)
- {
- H = 0xFF;
- }
- Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHA behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD.
- CompleteOperation();
- break;
- }
-
-
- break;
-
- case 0x94: //STY zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store(Y, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x95: //STA zp, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x96: //STX zp, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffY();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store(X, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x97: //SAX zp, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffY();
- if (operationCycle == 2) { CPU_Read = false; }
- break;
- case 3: // read from address
- PollInterrupts();
- Store((byte)(A & X), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x98: //TYA
- PollInterrupts();
- A = Y;
- Fetch(addressBus); // dummy read
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0x99: //STA Abs, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4: // read from address
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x9A: //TXS
- PollInterrupts();
- stackPointer = X;
- Fetch(addressBus); // dummy read
- CompleteOperation();
- break;
-
-
- case 0x9B: //SHS, Abs Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4: // read from address
- PollInterrupts();
- if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00))
- {
- // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable"
- addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHS behavior. The A register isn't used here!
- }
- // pd = the high byte of the target address + 1
- stackPointer = (byte)(A & X);
- if (IgnoreH)
- {
- H = 0xFF;
- }
- Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHS behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD.
- CompleteOperation();
- break;
- }
- break;
-
- case 0x9C: //SHY Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4:
- PollInterrupts();
- if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00))
- {
- // if adding X to the target address crossed a page boundary, this opcode has "gone unstable"
- addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) & Y) << 8);
- }
- if (IgnoreH)
- {
- H = 0xFF;
- }
- Store((byte)(Y & H), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x9D: //STA Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4:
- PollInterrupts();
- Store(A, addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x9E: // SHX Abs, Y***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4:
- PollInterrupts();
- // Not even close to what the documentation says this instruction does.
- if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00))
- {
- // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable"
- addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) & X) << 8);
- }
- if (IgnoreH)
- {
- H = 0xFF;
- }
- Store((byte)(X & H), addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0x9F: // SHA Abs, Y***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(false);
- if (operationCycle == 3) { CPU_Read = false; }
- break;
- case 4: // read from address
- PollInterrupts();
- if ((temporaryAddress & 0xFF00) != (addressBus & 0xFF00))
- {
- // if adding Y to the target address crossed a page boundary, this opcode has "gone unstable"
- addressBus = (ushort)((byte)addressBus | ((addressBus >> 8) /*& A*/ & X) << 8); // Alternate SHA behavior. The A register isn't used here!
- }
- if (IgnoreH)
- {
- H = 0xFF;
- }
- Store((byte)(A & (X | 0xF5) & H), addressBus); // Alternate SHA behavior. X is ORed with a magic number. On my console, it's $F5 for a few hours, then it flickers from $F5 and $FD.
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA0: //LDY imm
- PollInterrupts();
- GetImmediate();
- Y = dl;
- flag_Zero = Y == 0;
- flag_Negative = Y >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0xA1: //(LDA, X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA2: //LDX imm
- PollInterrupts();
- GetImmediate();
- X = dl;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0xA3: //(LAX, X) ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5:
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA4: //LDY zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Y = Fetch(addressBus);
- flag_Zero = Y == 0;
- flag_Negative = Y >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA5: //LDA zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA6: //LDX zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- X = Fetch(addressBus);
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA7: //LAX zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xA8: //TAY
- PollInterrupts();
- Y = A;
- Fetch(addressBus); // dummy read
- flag_Zero = A == 0;
- flag_Negative = Y >= 0x80;
- CompleteOperation();
- break;
-
- case 0xA9: //LDA Imm
- PollInterrupts();
- GetImmediate();
- A = dl;
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
-
- case 0xAA: //TAX
- PollInterrupts();
- X = A;
- Fetch(addressBus); // dummy read
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
-
- case 0xAB: //LXA ***
- PollInterrupts();
- GetImmediate();
- A = (byte)((A | 0xFF) & dl); // 0xEE is also known as "MAGIC", and can supposedly be different depending on the CPU's temperature.
- X = A; // this instruction is basically XAA but using LAX behavior, so X is also affected..
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0x00;
- CompleteOperation();
- break;
-
- case 0xAC: //LDY Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Y = Fetch(addressBus);
- flag_Negative = Y >= 0x80;
- flag_Zero = Y == 0x00;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xAD: //LDA Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- if(addressBus > 0x4000 && addressBus < 0x6000)
- {
-
- }
- A = Fetch(addressBus);
- flag_Negative = A >= 0x80;
- flag_Zero = A == 0x00;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xAE: //LDX Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- X = Fetch(addressBus);
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0x00;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xAF: //LAX Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0x00;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB0: //BCS
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (!flag_Carry)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB1: //(LDA), Y
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5:
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB2: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0xB3: //(LAX), Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
- case 0xB4: //LDY zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Y = Fetch(addressBus);
- flag_Zero = Y == 0;
- flag_Negative = Y >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB5: //LDA zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB6: //LDX zp, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffY();
- break;
- case 3: // read from address
- PollInterrupts();
- X = Fetch(addressBus);
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB7: //LAX zp, Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffY();
- break;
- case 3: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xB8: //CLV
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Overflow = false;
- CompleteOperation();
- break;
-
- case 0xB9: //LDA abs , Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Zero = A == 0;
- flag_Negative = A >= 0x80;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xBA: //TSX
-
- PollInterrupts();
- X = stackPointer;
- Fetch(addressBus); // dummy read
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0;
- CompleteOperation();
- break;
-
- case 0xBB: //LAE Abs, Y***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- dl = Fetch(addressBus);
- A = (byte)(dl & stackPointer);
- X = (byte)(dl & stackPointer);
- stackPointer = (byte)(dl & stackPointer);
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xBC: //LDY abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Y = Fetch(addressBus);
- flag_Negative = Y >= 0x80;
- flag_Zero = Y == 0;
- CompleteOperation();
- break;
- }
- break;
-
-
- case 0xBD: //LDA abs, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- flag_Negative = A >= 0x80;
- flag_Zero = A == 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xBE: //LDX abs , Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- X = Fetch(addressBus);
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xBF: //LAX Abs, Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- A = Fetch(addressBus);
- X = A;
- flag_Negative = X >= 0x80;
- flag_Zero = X == 0;
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC0: //CPY Imm
- PollInterrupts();
- GetImmediate();
- Op_CPY(dl);
- CompleteOperation();
-
- break;
-
- case 0xC1: //(CMP X),
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC2: //NOP Immediate ***
- PollInterrupts();
- GetImmediate();
- CompleteOperation();
-
- break;
-
- case 0xC3: //(DCP, X) ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- dl--;
- Store(dl, addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC4: //CPY zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_CPY(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC5: //CMP zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC6: //DEC zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2:
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3:
- Store(dl, addressBus); //dummy write
- break;
- case 4: // read from address
- PollInterrupts();
- Op_DEC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xC7: //DCP zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2:
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3:
- Store(dl, addressBus); //dummy write
- break;
- case 4: // read from address
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
-
- case 0xC8: //INY
- PollInterrupts();
- Y++;
- Fetch(addressBus); // dummy read
- flag_Zero = Y == 0;
- flag_Negative = Y >= 0x80;
- CompleteOperation();
- break;
-
- case 0xC9: //CMP Imm
- PollInterrupts();
- GetImmediate();
- Op_CMP(dl);
- CompleteOperation();
- break;
-
- case 0xCA: //DEX
- PollInterrupts();
- X--;
- Fetch(addressBus); // dummy read
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
-
- break;
-
- case 0xCB: // AXS ***
- PollInterrupts();
- GetImmediate();
- X = (byte)(X & A);
- flag_Carry = X >= dl;
- X -= dl;
- flag_Zero = X == 0;
- flag_Negative = (X >= 0x80);
-
- CompleteOperation();
- break;
-
-
- case 0xCC: //CPY Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_CPY(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xCD: //CMP Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xCE: //DEC Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3:
- // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4:
- // dummy write
- Store(dl, addressBus);
- break;
- case 5: // write
- PollInterrupts();
- Op_DEC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xCF: //DCP Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3:
- // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4:
- // dummy write
- Store(dl, addressBus);
- break;
- case 5: // write
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD0: //BNE
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (flag_Zero)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD1: //(CMP), Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD2: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0xD3: //(DCP) Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD4: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD5: //CMP zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD6: //DEC zp, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3:
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4:
- Store(dl, addressBus); //dummy write
- break;
- case 5: // read from address
- PollInterrupts();
- Op_DEC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD7: //DCP Zp X ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3:
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4:
- Store(dl, addressBus); //dummy write
- break;
- case 5: // read from address
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xD8: //CLD
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Decimal = false;
- CompleteOperation();
-
- break;
- case 0xD9: //CMP abs, Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xDA: //NOP ***
- PollInterrupts();
- Fetch(addressBus); // dummy read
- CompleteOperation();
- break;
-
- case 0xDB: //DCP Abs Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xDC: //NOP Absolute, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xDD: //CMP abs, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_CMP(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xDE: //DEC Abs X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_DEC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xDF: //DCP Abs X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_DEC(addressBus);
- Op_CMP(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE0: //CPX Imm
- PollInterrupts();
- GetImmediate();
- Op_CPX(dl);
- CompleteOperation();
- break;
-
- case 0xE1: //(SBC X)
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE2: //NOP Immediate ***
- PollInterrupts();
- GetImmediate();
- CompleteOperation();
- break;
-
- case 0xE3: //(ISC, X) ***
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffX();
- break;
- case 5: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // write back to the address
- Store(dl, addressBus);
- break; // perform the operation
- case 7:
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE4: //CPX zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_CPX(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE5: //SBC Zp
-
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE6: //INC zp
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_INC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE7: //ISC zp ***
- switch (operationCycle)
- {
- case 1:
- GetAddressZeroPage();
- break;
- case 2: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 3: //dummy write
- Store(dl, addressBus);
- break;
- case 4: // perform operation
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xE8: //INX
- PollInterrupts();
- Fetch(addressBus); // dummy read
- X++;
- flag_Zero = X == 0;
- flag_Negative = X >= 0x80;
- CompleteOperation();
- break;
-
- case 0xE9: //SBC Imm
- PollInterrupts();
- GetImmediate();
- Op_SBC(dl);
- CompleteOperation();
- break;
-
- case 0xEA: //NOP
- PollInterrupts();
- Fetch(addressBus); // dummy read
- CompleteOperation();
- break;
-
- case 0xEB: //SBC Imm ***
- PollInterrupts();
- GetImmediate();
- Op_SBC(dl);
- CompleteOperation();
- break;
-
- case 0xEC: //CPX Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_CPX(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xED: //SBC Abs
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xEE: //INC Abs
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- if (addressBus == 0x4014)
- {
-
- }
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_INC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xEF: //ISC Abs ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressAbsolute();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF0: //BEQ
- switch (operationCycle)
- {
- case 1:
- PollInterrupts();
- GetImmediate();
- if (!flag_Zero)
- {
- CompleteOperation();
- }
- break;
- case 2:
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- temporaryAddress = (ushort)(programCounter + ((dl >= 0x80) ? -(256 - dl) : dl));
- programCounter = (ushort)((programCounter & 0xFF00) | (byte)((programCounter & 0xFF) + dl));
- if ((temporaryAddress & 0xFF00) == (programCounter & 0xFF00))
- {
- CompleteOperation();
- }
- break;
- case 3: // read from address
- PollInterrupts_CantDisableIRQ(); // If the first poll detected an IRQ, this second poll should not be allowed to un-set the IRQ.
- addressBus = programCounter;
- Fetch(addressBus); // dummy read
- programCounter = (ushort)((programCounter & 0xFF) | (temporaryAddress & 0xFF00));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF1: //(SBC) Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(true);
- break;
- case 5: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF2: ///HLT ***
- switch (operationCycle)
- {
- case 1:
- dl = Fetch(addressBus);
- break;
- case 2:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 3:
- case 4:
- addressBus = 0xFFFE;
- Fetch(addressBus);
- break;
- case 5:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- break;
- case 6:
- addressBus = 0xFFFF;
- Fetch(addressBus);
- operationCycle = 5; //makes this loop infinitely.
- break;
- }
- break;
-
- case 0xF3: //(ISC) Y
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressIndOffY(false);
- break;
- case 5: // dummy read
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 6: // dummy write
- Store(dl, addressBus);
- break;
- case 7: // read from address
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF4: //NOP zp, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF5: //SBC Zp, X
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF6: //INC Zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_INC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF7: //ISC zp, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- GetAddressZPOffX();
- break;
- case 3: // read from address
- dl = Fetch(addressBus);
- CPU_Read = false;
- break;
- case 4: //dummy write
- Store(dl, addressBus);
- break;
- case 5:
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xF8: //SED
- PollInterrupts();
- Fetch(addressBus); // dummy read
- flag_Decimal = true;
- CompleteOperation();
- break;
-
- case 0xF9: //SBC Abs Y
-
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffY(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xFA: //NOP ***
- PollInterrupts();
- Fetch(addressBus); // dummy read
- CompleteOperation();
- break;
-
- case 0xFB: //ISC Abs Y ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffY(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xFC: //NOP Absolute ,X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Fetch(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xFD: //SBC Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- GetAddressAbsOffX(true);
- break;
- case 4: // read from address
- PollInterrupts();
- Op_SBC(Fetch(addressBus));
- CompleteOperation();
- break;
- }
- break;
-
- case 0xFE: //INC Abs, X
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_INC(addressBus);
- CompleteOperation();
- break;
- }
- break;
-
- case 0xFF: //ISC Abs, X ***
- switch (operationCycle)
- {
- case 1:
- case 2:
- case 3:
- case 4:
- GetAddressAbsOffX(false);
- if (operationCycle == 4) { CPU_Read = false; }
- break;
- case 5:// dummy write
- Store(dl, addressBus);
- break;
- case 6:// read from address
- PollInterrupts();
- Op_INC(addressBus);
- Op_SBC(dl);
- CompleteOperation();
- break;
- }
- break;
- // And that's all 256 instructions!
- }
- operationCycle++; // increment this for next CPU cycle.
- }
- if (DoDMCDMA && APU_ImplicitAbortDMC4015)
- {
- APU_ImplicitAbortDMC4015 = false; // If this was delayed by a write cycle, it won't run at all.
- }
- }
-
-
- public void ResetReadPush()
- {
- // the RESET instruction has unique behavior where it reads from the stack, and decrements the stack pointer.
- Fetch((ushort)(0x100 + stackPointer));
- stackPointer--;
- }
-
- public void Push(byte A)
- {
- // Store to the stack, and decrement the stack pointer.
- Store(A, (ushort)(0x100 + stackPointer));
- stackPointer--;
- }
-
- // I don't have a void for pop... All instructions that pull form the stack just perform the logic.
-
-
- ushort PPU_VRAM_MysteryAddress; // used during consecutive write cycles to VRAM. The PPU makes 2 extra writes to VRAM, and one of them I call "the mystery write".
-
- public ushort PPU_AddressBus; // the Address Bus of the PPU
- public bool PPU_ALE; // Address Latch Enable
- public byte PPU_OctalLatch; // This is the address latch.
-
- public ushort PPU_v = 0;// PPU Internal Register 'v'
- public ushort PPU_t = 0; // PPU Internal Register 't'. "can also be thought of as the address of the top left onscreen tile: https://www.nesdev.org/wiki/PPU_scrolling"
- /*
- The v and t registers are 15 bits:
- yyy NN YYYYY XXXXX
- ||| || ||||| +++++-- coarse X scroll
- ||| || +++++-------- coarse Y scroll
- ||| ++-------------- nametable select
- +++----------------- fine Y scroll
- */
-
- byte PPU_Update2006Delay; // The number of PPU cycles to wait between writing to $2006 and the ppu from updating
- byte PPU_Update2005Delay; // The number of PPU cycles to wait between writing to $2004 and the ppu from updating
- byte PPU_Update2005Value; // The value written to $2005, for use when the delay has ended.
- byte PPU_Update2001Value; // The value written to $2001, for use when the delay has ended.
- ushort PPU_Update2006Value; // The value written to $2006, for use when the delay has ended.
- ushort PPU_Update2006Value_Temp;
-
- byte PPU_Update2001Delay; // The number of PPU cycles to wait between writing to $2001 and the ppu from updating
- byte PPU_Update2001OAMCorruptionDelay; // I plan to refactor 2001 writes and remove the hard-coded delays. This will be removed eventually.
- byte PPU_Update2001EmphasisBitsDelay; // I plan to refactor 2001 writes and remove the hard-coded delays. This will be removed eventually.
-
- bool PPU_WasRenderingBefore2001Write; // Were we rendering before writing to $2001? (used for OAM corruption)
-
- byte PPU_ReadBuffer = 0; // when reading from $2007, this buffer holds the value from VRAM that gets read. Updated after reading from $2007.
-
- bool PPUAddrLatch = false; // Certain ppu registers take two writes to fully set things up. It's flipped when writing to $2005 and $2006. Reset when reading from $2002
-
- bool PPUControlIncrementMode32; // Set by writing to $2000. If set, the VRAM address is incremented by 32 instead of 1 after reads/writes to $2007.
- bool PPUControl_NMIEnabled; // Set by writing to $2000. If set, the NMI can occur.
-
- public bool PPU_PatternSelect_Sprites; //which pattern table is used for sprites / background
- public bool PPU_PatternSelect_Background; //which pattern table is used for sprites / background
- public bool PPU_EXT_Enable; // I can toggle this boolean, but it is otherwise unimplemented.
-
- //for logging purposes. doesn't update databus.
- public byte Observe(ushort Address)
- {
- // Reading from anywhere goes through this function.
- if ((Address >= 0x8000))
- {
- // Reading from ROM.
- // Different mappers could rearrange the data from the ROM into different locations on the system bus.
- return MapperObserve(Address);
- }
- else if (Address < 0x2000)
- {
- // Reading from RAM.
- // Ram mirroring! Only addresses $0000 through $07FF exist in RAM, so ignore bits 11 and 12
- return RAM[Address & 0x7FF];
- }
- else if (Address >= 0x2000 && Address < 0x4000)
- {
- // PPU registers. most of these aren't meant to be read.
- Address = (ushort)(Address & 0x2007);
- switch (Address)
- {
- case 0x2000:
- // Write only. Return the PPU databus.
- return PPUBus;
- case 0x2001:
- // Write only. Return the PPU databus.
- return PPUBus;
- case 0x2002:
- // PPU Flags.
- return (byte)((((PPUStatus_VBlank ? 0x80 : 0) | (PPUStatus_SpriteZeroHit ? 0x40 : 0) | (PPUStatus_SpriteOverflow ? 0x20 : 0)) & 0xE0) + (PPUBus & 0x1F));
- case 0x2003:
- // write only. Return the PPU databus.
- return PPUBus;
- case 0x2004:
- // Read from OAM
- return (byte)(ReadOAM());
- case 0x2005:
- // write only. Return the PPU databus.
- return PPUBus;
- case 0x2006:
- // write only. Return the PPU databus.
- return PPUBus;
- case 0x2007:
- // Reading from VRAM.
- return ObservePPU(PPU_v);
- }
-
- }
- else if (Address >= 0x4000 && Address <= 0x401F) // observe the APU registers
- {
- //addressBus
- byte Reg = (byte)(Address & 0x1F);
- if (Reg == 0x15)
- {
-
- byte InternalBus = dataBus;
-
- InternalBus &= 0x20;
- InternalBus |= (byte)(APU_Status_DMCInterrupt ? 0x80 : 0);
- InternalBus |= (byte)(APU_Status_FrameInterrupt ? 0x40 : 0);
- InternalBus |= (byte)((APU_DMC_BytesRemaining != 0 && APU_Status_DelayedDMC) ? 0x10 : 0); // see footnote.
- InternalBus |= (byte)((APU_LengthCounter_Noise != 0) ? 0x08 : 0);
- InternalBus |= (byte)((APU_LengthCounter_Triangle != 0) ? 0x04 : 0);
- InternalBus |= (byte)((APU_LengthCounter_Pulse2 != 0) ? 0x02 : 0);
- InternalBus |= (byte)((APU_LengthCounter_Pulse1 != 0) ? 0x01 : 0);
- return InternalBus; // reading from $4015 can not affect the databus
- }
- else if (Reg == 0x16 || Reg == 0x17)
- {
- return (byte)((((Reg == 0x16) ? (ControllerShiftRegister1 & 0x80) : (ControllerShiftRegister2 & 0x80)) == 0 ? 0 : 1) | (dataBus & 0xE0));
- }
- }
- else
- {
- //mapper chip stuff, but also open bus!
- return MapperObserve(Address);
- }
-
- return dataBus;
- }
- public byte Fetch(ushort Address)
- {
- dataPinsAreNotFloating = false; // assume the data pins are floating by default.
- // Reading from anywhere goes through this function.
- if ((Address >= 0x8000))
- {
- // Reading from ROM.
- // Different mappers could rearrange the data from the ROM into different locations on the system bus.
- MapperFetchPRG(Address);
- dataPinsAreNotFloating = true;
- }
- else if (Address < 0x2000)
- {
- // Reading from RAM.
- // Ram mirroring! Only addresses $0000 through $07FF exist in RAM, so ignore bits 11 and 12
- dataBus = RAM[Address & 0x7FF];
- dataPinsAreNotFloating = true;
- }
- else if (Address >= 0x2000 && Address < 0x4000)
- {
- // PPU registers. most of these aren't meant to be read.
- Address = (ushort)(Address & 0x2007);
- switch (Address)
- {
- case 0x2000:
- // Write only. Return the PPU databus.
- dataBus = PPUBus;
-
- break;
- case 0x2001:
- // Write only. Return the PPU databus.
- dataBus = PPUBus;
-
- break;
- case 0x2002:
- // PPU Flags.
-
- dataBus = (byte)((((PPUStatus_VBlank ? 0x80 : 0)))); // The vblank flag is read at the start of the read...
- PPU_Read2002 = true;
- EmulateUntilEndOfRead();
- dataBus |= (byte)((((PPUStatus_SpriteZeroHit_Delayed ? 0x40 : 0) | (PPUStatus_SpriteOverflow_Delayed ? 0x20 : 0)) & 0xE0) + (PPUBus & 0x1F)); // ...while the sprite flags are read at the end.
-
- PPUAddrLatch = false;
- PPUBus = dataBus;
- for (int i = 5; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
-
- break;
- case 0x2003:
- // write only. Return the PPU databus.
- dataBus = PPUBus; break;
- case 0x2004:
- // Read from OAM
- EmulateUntilEndOfRead();
- dataBus = ReadOAM();
-
- PPUBus = dataBus;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
-
- break;
- case 0x2005:
- // write only. Return the PPU databus.
- dataBus = PPUBus; break;
- case 0x2006:
- // write only. Return the PPU databus.
- dataBus = PPUBus; break;
- case 0x2007:
- // Reading from VRAM.
-
- if ((PPU_AddressBus & 0x3FFF) >= 0x3F00)
- {
- // read from palette RAM.
- // Palette RAM only returns bits 0-5, so bits 6 and 7 are PPU open bus.
- ThisDotReadFromPaletteRAM = true;
- ushort PalRAMAddr = (ushort)(PPU_v & 0x3F1F);
- if ((PalRAMAddr & 3) == 0)
- {
- PalRAMAddr &= 0x3F0F;
- }
-
- dataBus = (byte)(((PaletteRAM[PalRAMAddr & 0x1F] & (PPU_Mask_Greyscale ? 0x30 : 0x3F)) | (PPUBus & 0xC0)));
- }
- else
- {
- dataBus = PPU_ReadBuffer;
- }
- PPUBus = dataBus;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
-
- EmulateUntilEndOfRead();
- PPU_2007_Read_SR = true; // set the SR latch at the end of the CPU read. Here's where the clock alignment differences begin. :)
- PPU_2007_Read = true; // Start the $2007 Read state machine.
-
- break;
- }
- dataPinsAreNotFloating = true;
-
- }
- else
- {
- //mapper chip stuff, but also open bus!
- MapperFetchPRG(Address);
- }
-
- if (addressBus >= 0x4000 && addressBus <= 0x401F) // If APU registers are active, bus conflicts can occur. Or perhaps you are intentionally reading from the APU registers...
- {
- //addressBus
- byte Reg = (byte)(Address & 0x1F);
- if (Reg == 0x15)
- {
-
- internalBus &= 0x20;
- internalBus |= (byte)(APU_Status_DMCInterrupt ? 0x80 : 0);
- internalBus |= (byte)(APU_Status_FrameInterrupt ? 0x40 : 0);
- internalBus |= (byte)((APU_DMC_BytesRemaining != 0 && APU_Status_DelayedDMC) ? 0x10 : 0); // see footnote.
- internalBus |= (byte)((APU_LengthCounter_Noise != 0) ? 0x08 : 0);
- internalBus |= (byte)((APU_LengthCounter_Triangle != 0) ? 0x04 : 0);
- internalBus |= (byte)((APU_LengthCounter_Pulse2 != 0) ? 0x02 : 0);
- internalBus |= (byte)((APU_LengthCounter_Pulse1 != 0) ? 0x01 : 0);
-
- Clearing_APU_FrameInterrupt = true;
-
-
- // footnote:
- // Consider the following. LDA #0, STA $4015, LDA $4015.
- // The APU_DMC_BytesRemaining byte isn't cleared until 3 or 4 cycles after writing 0 to $4015.
- // However, reading from $4015 after the needs to immediately have bit 4 cleared.
-
- return internalBus; // reading from $4015 can not affect the databus
- }
- else if (Reg == 0x16 || Reg == 0x17)
- {
- byte ControllerRead = (byte)((((Reg == 0x16) ? (ControllerShiftRegister1 & 0x80) : (ControllerShiftRegister2 & 0x80)) == 0 ? 0 : 1) | (dataBus & 0xE0));
-
- // controller ports
- // grab 1 bit from the controller's shift register.
- // also add the upper 3 bits of the databus.
-
- if (Reg == 0x16)
- {
- // if there are 2 CPU cycles in a row that read from this address, the registers don't get shifted
- Controller1ShiftCounter = 2; // The shift register isn't shifted until this is 0, decremented in every APU PUT cycle
- }
- else
- {
- // if there are 2 CPU cycles in a row that read from this address, the registers don't get shifted
- Controller2ShiftCounter = 2; // The shift register isn't shifted until this is 0, decremented in every APU PUT cycle
- }
-
- APU_ControllerPortsStrobed = false; // This allows data to rapidly be streamed in through the A button if the controllers are read while strobed.
- if (DoOAMDMA && dataPinsAreNotFloating) // If all the databus pins are floating, then the controller bits are visible. Otherwise... not so much.
- {
- return dataBus;
- }
- dataBus = ControllerRead;
-
- }
- }
-
- internalBus = dataBus;
- return dataBus;
- }
- public byte ObservePPU(ushort Address)
- {
- if(Address >= 0x3F00)
- {
- if((Address & 0x3) == 0)
- {
- return PaletteRAM[Address & 0x0F];
- }
- return PaletteRAM[Address & 0x1F];
- }
- return Cart.MapperChip.SnoopPPU(Address);
- }
-
- byte MapperObserve(ushort Address)
- {
- return Cart.MapperChip.SnoopCPU(Address);
- }
-
- void MapperFetchPRG(ushort Address)
- {
- Cart.MapperChip.Connector_SetUpCPUAddressPins(Address);
- Cart.MapperChip.Connector_SetUpCPUDataPins(dataBus); // So when we read from the data pins, if nothing changed, we get back this value.
- Cart.MapperChip.FetchCPU();
- dataBus = Cart.MapperChip.Connector_ReadCPUDataPins();
- return;
- }
-
- byte ReadOAM()
- {
- if ((PPU_Mask_ShowBackground || PPU_Mask_ShowSprites) && PPU_Scanline < 240)
- {
- return PPU_OAMLatch; // Remember, PPU_OAMLatch is updated on the first half of a PPU cycle. OAMBuffer is set up on the second half.
- }
- return OAM[PPUOAMAddress];
- }
-
- bool PPU_PendingVBlank;
-
- bool dataPinsAreNotFloating = false; // used in controller reading + OAM DMA.
- public bool TAS_ReadingTAS; // if we're reading inputs from a TAS, this will be set.
- public int TAS_InputSequenceIndex; // which index from the TAS input log will be used for this current controller strobe?
- public ushort[] TAS_InputLog; // controller [22222222 11111111]
- public bool[] TAS_ResetLog; // just a list of booleans determining if we should soft-reset on this frame or not.
- public bool ClockFiltering = false; // If set, TAS_InputSequenceIndex increments every time the controllers are strobed (or clocked, if the controller is held strobing). Otherwise, "latch filtering" is used, incrementing TAS_InputSequenceIndex once a frame.
- public bool SyncFM2; // This is set if we're running an FM2 TAS, which (due to FCEUX's very incorrect timing of the first frame after power on) I need to start execution on scanline 240, and prevent the vblank flag from being set.
- public void Store(byte Input, ushort Address)
- {
- Cart.MapperChip.Connector_SetUpCPUAddressPins(Address);
- Cart.MapperChip.Connector_SetUpCPUDataPins(Input);
- // This is used whenever writing anywhere with the CPU
- if (Address < 0x2000)
- {
- //guaranteed to be RAM
- // Even still, the address pins on the 72 pin connector get updated.
- RAM[Address & 0x7FF] = Input;
-
- }
- else if (Address < 0x4000)
- {
- // $2000 through $3FFF writes to the PPU registers
- StorePPURegisters(Address, Input);
- }
- else if (Address >= 0x4000 && Address <= 0x4015)
- {
- // Writing to $4000 through $4015 are APU registers
- switch (Address)
- {
- default:
- APU_Register[Address & 0xFF] = Input; break;
- case 0x4003:
- if (APU_Status_Pulse1)
- {
- APU_LengthCounter_ReloadValuePulse1 = APU_LengthCounterLUT[Input >> 3];
- APU_LengthCounter_ReloadPulse1 = true;
- }
- APU_ChannelTimer_Pulse1 |= (ushort)((Input &= 0x7) << 8);
- break;
- case 0x4007:
- if (APU_Status_Pulse2)
- {
- APU_LengthCounter_ReloadValuePulse2 = APU_LengthCounterLUT[Input >> 3];
- APU_LengthCounter_ReloadPulse2 = true;
- }
- APU_ChannelTimer_Pulse2 |= (ushort)((Input &= 0x7) << 8);
- break;
- case 0x400B:
- if (APU_Status_Triangle)
- {
- APU_LengthCounter_ReloadValueTriangle = APU_LengthCounterLUT[Input >> 3];
- APU_LengthCounter_ReloadTriangle = true;
-
- }
- APU_ChannelTimer_Triangle |= (ushort)((Input &= 0x7) << 8);
- break;
- case 0x400F:
- if (APU_Status_Noise)
- {
- APU_LengthCounter_ReloadValueNoise = APU_LengthCounterLUT[Input >> 3];
- APU_LengthCounter_ReloadNoise = true;
- }
- break;
-
- case 0x4010:
- APU_DMC_EnableIRQ = (Input & 0x80) != 0;
- APU_DMC_Loop = (Input & 0x40) != 0;
- APU_DMC_Rate = APU_DMCRateLUT[Input & 0xF];
- if (!APU_DMC_EnableIRQ)
- {
- APU_Status_DMCInterrupt = false;
- IRQ_LevelDetector = false;
- }
- break;
-
- case 0x4011:
- APU_DMC_Output = (byte)(Input & 0x7F);
-
- break;
-
- case 0x4012:
- APU_DMC_SampleAddress = (ushort)(0xC000 | (Input << 6));
- break;
-
- case 0x4013:
- APU_DMC_SampleLength = (ushort)((Input << 4) | 1);
- break;
-
- case 0x4014: //OAM DMA
- DoOAMDMA = true;
- FirstCycleOfOAMDMA = true;
- DMAAddress = 0; // the starting address for the OAM DMC is always page aligned.
- DMAPage = Input;
- break;
- case 0x4015: //DMC DMA (and other audio channels)
-
- APU_Status_DelayedDMC = (Input & 0x10) != 0;
- APU_Status_Noise = (Input & 0x08) != 0;
- APU_Status_Triangle = (Input & 0x04) != 0;
- APU_Status_Pulse2 = (Input & 0x02) != 0;
- APU_Status_Pulse1 = (Input & 0x01) != 0;
-
- APU_DelayedDMC4015 = (byte)(APU_PutCycle ? 3 : 4); // Enable in 1 APU cycles, or 1.5 APU cycles. (it will be decremented later this cycle, so it's really like 2 : 3.
-
- if (APU_Status_DelayedDMC && APU_DMC_BytesRemaining == 0)
- {
- // sets up the sample bytes_remaining and sample address.
- StartDMCSample();
- // However, the sample will only begin playing if the DMC is currently silent
- if (APU_Silent)
- {
- DMCDMADelay = 2; // 2 APU cycles
- }
- }
-
- if (!APU_Status_Noise) { APU_LengthCounter_Noise = 0; }
- if (!APU_Status_Triangle) { APU_LengthCounter_Triangle = 0; }
- if (!APU_Status_Pulse2) { APU_LengthCounter_Pulse2 = 0; }
- if (!APU_Status_Pulse1) { APU_LengthCounter_Pulse1 = 0; }
- APU_Status_DMCInterrupt = false;
- IRQ_LevelDetector = false;
-
- // Explicit abort stuff.
- if (!APU_Status_DelayedDMC && ((APU_ChannelTimer_DMC == 2 && !APU_PutCycle) || (APU_ChannelTimer_DMC == APU_DMC_Rate && APU_PutCycle))) // this will be the APU cycle that fires a DMC DMA
- {
- APU_DelayedDMC4015 = (byte)(APU_PutCycle ? 5 : 6); // Disable in 2.5 APU cycles, or 3 APU cycles.
- // basically, if the DMA has already begun, don't abort it for *this* edge case.
- }
-
- // Implicit abort stuff.
- if (APU_Status_DelayedDMC && ((APU_ChannelTimer_DMC == 10 && !APU_PutCycle) || (APU_ChannelTimer_DMC == 8 && APU_PutCycle)))
- {
- // okay, so the series of events is as follows:
- // the Load DMA will occur
- // regardless of the buffer being empty, there will be a 1-cycle DMA that gets aborted 2 cycles after the load DMA ends.
- APU_SetImplicitAbortDMC4015 = true; // This will occur in 8 (or 9) cpu cycles
- }
-
- break;
- }
-
- }
- else if (Address == 0x4016)
- {
- if (TAS_ReadingTAS)
- {
- APU_ControllerPortsStrobing = ((Input & 1) != 0);
- }
- APU_ControllerPortsStrobing = ((Input & 1) != 0);
- if (!APU_ControllerPortsStrobing)
- {
- APU_ControllerPortsStrobed = false;
- }
- }
- else if (Address == 0x4017)
- {
- APU_FrameCounterMode = (Input & 0x80) != 0;
- APU_FrameCounterInhibitIRQ = (Input & 0x40) != 0;
- if (APU_FrameCounterMode)
- {
- APU_HalfFrameClock = true;
- APU_QuarterFrameClock = true;
- }
- if (APU_FrameCounterInhibitIRQ)
- {
- APU_Status_FrameInterrupt = false;
- IRQ_LevelDetector = false;
- }
- APU_FrameCounterReset = (byte)((APU_PutCycle ? 3 : 4));
- }
- else if (Address >= 0x4020)
- {
- // mapper chip specific stuff- but also open bus!
- Cart.MapperChip.StoreCPU(Address, Input);
-
- //MapperStore(Input, Address, Cart.MemoryMapper);
-
- }
- else
- {
- // open bus!
- // this doesn't write anywhere, but it still updates the databus!
- }
-
- dataBus = Input;
- internalBus = dataBus;
- }
-
- public void StorePPURegisters(ushort Addr, byte In)
- {
- //EmulateNMasterClockCycles(1); // wait for PPUSEL to go high
- // Okay, I KNOW this shouldn't be commented out. TODO: figure out why the timing on this is off by one.
-
- ushort AddrT = (ushort)((Addr & 0x2007));
- switch (AddrT)
- {
- case 0x2000:
- // writing here updates a large amount of PPU flags
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- if (PPU_RESET)
- {
- return;
- }
-
-
- // now that PPUSEL is high, the value of the databus is written to the PPU register.
- PPU_t = (ushort)((PPU_t & 0b0111001111111111) | ((dataBus & 0x3) << 10)); // This early write to the t register is the cause of the scanline bug in SMB1.
- PPU_EXT_Enable = (dataBus & 0x40) == 0x40;
- // technically this changes PPUControl_NMIEnabled here too, but it's invisible as the NMI polling has already happened and it will be re-enabled before then.
-
- EmulateNMasterClockCycles(2); // wait for the CPU databus to change. (that's right, it doesn't happen at the start of the write cycle!)
- PPUControl_NMIEnabled = (In & 0x80) != 0;
- PPUControlIncrementMode32 = (In & 0x4) != 0;
- PPU_Spritex16 = (In & 0x20) != 0;
- PPU_PatternSelect_Sprites = (In & 0x8) != 0;
- PPU_PatternSelect_Background = (In & 0x10) != 0;
- PPU_t = (ushort)((PPU_t & 0b0111001111111111) | ((In & 0x3) << 10)); // change which nametable to render.
- PPU_EXT_Enable = (In & 0x40) == 0x40;
-
- break;
-
- case 0x2001:
- // writing here updates a large amount of PPU flags
- // Is the background being drawn? Are sprites being drawn? Greyscale / color emphasis?
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- if (PPU_RESET)
- {
- return;
- }
- // Okay look, I *know* this hard-coded solution is jank and sloppy.
- // It is temporary.
- // I want to re-do the picture processing unit from the ground up, honestly.
- // In the mean time, let's go back to the hard-coded delays. I got the correct results from the tests while doing this.
- // And we can fix it later.
- /*
- EmulateNMasterClockCycles(1); // wait for PPUSEL to go high
-
- PPU_Mask_EmphasizeBlue = (dataBus & 0x80) != 0;
- PPU_Mask_Greyscale = (dataBus & 0x1) != 0;
-
- EmulateNMasterClockCycles(2); // wait for the CPU databus to change. (that's right, it doesn't happen at the start of the write cycle!)
-
- PPU_Mask_EmphasizeBlue = (In & 0x80) != 0;
- PPU_Mask_EmphasizeGreen = (In & 0x40) != 0;
- PPU_Mask_EmphasizeRed = (In & 0x20) != 0;
- PPU_Mask_Greyscale = (In & 0x1) != 0;
-
- EmulateNMasterClockCycles(4); // wait for PPUSEL to go low.
-
- PPU_WasRenderingBefore2001Write = PPU_Mask_ShowBackground || PPU_Mask_ShowSprites;
-
- PPU_Mask_8PxShowBackground = (In & 0x02) != 0;
- PPU_Mask_8PxShowSprites = (In & 0x04) != 0;
- PPU_Mask_ShowBackground = (In & 0x08) != 0;
- PPU_Mask_ShowSprites = (In & 0x10) != 0;
-
- PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change
- PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites;
- */
-
-
- switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different.
- {
- case 0:
- PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 2; PPU_Update2001OAMCorruptionDelay = 2; break;
- case 1:
- PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 1; PPU_Update2001OAMCorruptionDelay = 3; break; // PPU_Update2001EmphasisBitsDelay is actually 2, but different behavior than case 0 and 3.
- case 2:
- PPU_Update2001Delay = 3; PPU_Update2001EmphasisBitsDelay = 1; PPU_Update2001OAMCorruptionDelay = 3; break; // PPU_Update2001EmphasisBitsDelay is actually 2, but different behavior than case 0 and 3.
- case 3:
- PPU_Update2001Delay = 2; PPU_Update2001EmphasisBitsDelay = 2; PPU_Update2001OAMCorruptionDelay = 2; break;
- }
- PPU_WasRenderingBefore2001Write = PPU_Mask_ShowBackground || PPU_Mask_ShowSprites;
- PPU_Mask_ShowBackground_Instant = PPU_Mask_ShowBackground; // now that the PPU has updated, OAM evaluation will also recognize the change
- PPU_Mask_ShowSprites_Instant = PPU_Mask_ShowSprites;
- // TODO: Remove this hard-coded junk:
- bool temp_rendering = PPU_WasRenderingBefore2001Write;
- bool temp_renderingFromInput = ((In & 0x08) != 0) || ((In & 0x10) != 0);
- // disabling rendering can cause OAM corruption.
- if (temp_rendering && !temp_renderingFromInput)
- {
- // we are disabling rendering inside vblank
- if (PPU_Scanline < 241 || PPU_Scanline == 261)
- {
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = true; // used in the next cycle of sprite evaluation
- if ((PPU_Dot & 7) < 2 && PPU_Dot <= 250)
- {
- // Palette corruption only occurs if rendering was disabled during the first 2 dots of a nametable fetch
- // TODO: Fiskbit has enlightened me a bit on how this is actually working:
- // The VRAM address muxer selects between the PAR, NT address, AT address, and v.
- // v isn't an explicit input; it's actually the NT input when rendering is disabled.
- // The AT input actually sources a lot of its bits from the NT input, and this leads to an unfortunate bug where turning rendering off during an AT fetch actually results in a brief period where you have an AT input that is sourcing from v instead of an NT address.
- // And the address muxer ends up using this AT input briefly right after rendering is disabled.
- // This is why you can get palette RAM corruption when turning rendering off during an AT fetch if v was pointing into $3C00-$3EFF, despite this clearly not being palette RAM. The AT input that is being used actually points into palette RAM because those 2 bits are forced to 1.
- if ((PPU_v & 0x3FFF) >= 0x3C00) // palette corruption only appears to occur when disabling rendering if the VRAM address is currently greater than 3C00
- {
- PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = true; // used in the color calculation for the next dot being drawn
- }
- }
- }
- }
- else if (!temp_rendering && temp_renderingFromInput)
- {
- if (PPU_Scanline < 241 || PPU_Scanline == 261)
- {
- // if re-enabling rendering outside vblank
- if (PPU_PendingOAMCorruption)
- {
- // If OAM corruption is going to occur
- if (PPUClock == 1 || PPUClock == 2)
- {
- // if on clock alignment 1 or 2, it doesn't happen!
- PPU_OAMCorruptionRenderingEnabledOutOfVBlank = true;
- }
- }
- }
- }
-
- // This is temp. I know it's wrong (we're not even waiting for PPUSEL here.) but I'll fix it after redoing the entire ppu or something.
- if (PPU_Update2001EmphasisBitsDelay == 2)
- {
- PPU_Mask_Greyscale = (dataBus & 0x01) != 0;
- PPU_Mask_EmphasizeBlue = (dataBus & 0x80) != 0;
- }
- else
- {
- PPU_Update2001EmphasisBitsDelay++; // it's always 2.
- }
- PPU_Mask_EmphasizeRed = (In & 0x20) != 0;
- PPU_Mask_EmphasizeGreen = (In & 0x40) != 0;
-
- PPU_Update2001Value = In;
-
- break;
-
- case 0x2002: // this value is Read only.
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- break;
-
- case 0x2003:
- // writing here updates the OAM address
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- PPUOAMAddress = PPUBus;
- break;
-
- case 0x2004:
- // writing here updates the OAM byte at the current OAM address
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- if (((PPU_Scanline >= 240 && PPU_Scanline < 261) && (PPU_Mask_ShowBackground || PPU_Mask_ShowSprites)) || (!PPU_Mask_ShowBackground && !PPU_Mask_ShowSprites))
- {
- if ((PPUOAMAddress & 3) == 2)
- {
- In &= 0xE3;
- }
- OAM[PPUOAMAddress] = In;
- PPUOAMAddress++;
- }
- else
- {
- PPUOAMAddress += 4;
- PPUOAMAddress &= 0xFC;
-
- }
- break;
-
- case 0x2005:
- // writing here updates the X and Y scroll
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- if (PPU_RESET)
- {
- return;
- }
- switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different.
- {
- case 0: PPU_Update2005Delay = 1; break;
- case 1: PPU_Update2005Delay = 1; break;
- case 2: PPU_Update2005Delay = 2; break;
- case 3: PPU_Update2005Delay = 1; break;
- }
- PPU_Update2005Value = In;
- // There's a slight delay before the PPU updates the scroll with the correct values.
- // In the meantime, it uses the value from the databus.
- if (!PPUAddrLatch)
- {
- PPU_FineXScroll = (byte)(dataBus & 7);
- PPU_t = (ushort)((PPU_t & 0b0111111111100000) | (dataBus >> 3));
- }
- else
- {
- PPU_t = (ushort)((PPU_t & 0b0000110000011111) | (((dataBus & 0xF8) << 2) | ((dataBus & 7) << 12)));
- }
- break;
-
- case 0x2006:
- // writing here updates the PPU's read/write address.
- PPUBus = In;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- if (PPU_RESET)
- {
- return;
- }
-
- if (!PPUAddrLatch)
- {
- PPU_t = (ushort)((PPU_t & 0b000000011111111) | ((In & 0x3F) << 8));
-
- }
- else
- {
- PPU_t = (ushort)((PPU_t & 0b0111111100000000) | (In));
- PPU_Update2006Value = PPU_t;
- PPU_Update2006Value_Temp = PPU_v;
- switch (PPUClock & 3) //depending on CPU/PPU alignment, the delay could be different.
- {
- case 0: PPU_Update2006Delay = 4; break;
- case 1: PPU_Update2006Delay = 4; break;
- case 2: PPU_Update2006Delay = 5; break;
- case 3: PPU_Update2006Delay = 4; break;
- }
- }
- PPUAddrLatch = !PPUAddrLatch;
-
- break;
-
- case 0x2007:
- // writing here updates the byte at the current read/write address
- PPUBus = In;
- PPU_2007_WriteData = PPUBus;
- for (int i = 0; i < 8; i++) { PPUBusDecay[i] = PPUBusDecayConstant; }
- EmulateNMasterClockCycles(7); // wait for PPUSEL to go low
- PPU_2007_Write = true;
- PPU_2007_Write_SR = true; // set the SR latch at the end of the CPU write. Here's where the clock alignment differences begin. :)
- break;
- // and that's it for the ppu registers!
-
- default: break; //should never happen
- }
-
-
- }
-
- void StartDMCSample()
- {
- // This runs when writing to $4015, or if a DPCM sample is looping and needs to restart.
- APU_DMC_AddressCounter = APU_DMC_SampleAddress;
- APU_DMC_BytesRemaining = APU_DMC_SampleLength;
- }
-
-
- #region GetAddressFunctions
-
- // these functions are used inside the giant opcode switch statement.
-
- void GetImmediate()
- {
- // Fetch the value at the program counter, store it in the DataLatch, and increment the Program Counter.
- dl = Fetch(programCounter);
- programCounter++;
- addressBus = programCounter;
- }
-
- void GetAddressAbsolute()
- {
- // Fetch the value at the PC, and write to either the High byte or Low byte of the 16 bit address bus. Also increment the Program Counter.
- if (operationCycle == 1)
- {
- // fetch address low
- dl = Fetch(programCounter);
- }
- else
- {
- // fetch address high
- addressBus = (ushort)(dl | (Fetch(programCounter) << 8));
- }
- programCounter++;
- }
-
- void GetAddressZeroPage()
- {
- // Fetch the value at the PC, and this 8 bit value replaces the contents of the 16 bit address bus.
- addressBus = Fetch(programCounter);
- programCounter++;
- }
-
- void GetAddressIndOffX()
- {
- // Fetch the value from the PC, then using that value as an 8-bit address on the zero page, add the X register, then set the High byte and Low byte of the Address Bus from there.
- switch (operationCycle)
- {
- case 1: // fetch pointer address
- addressBus = Fetch(programCounter);
- programCounter++;
- break;
- case 2: // Add X
- // dummy read
- Fetch(addressBus);
- addressBus = (byte)(addressBus + X);
- break;
- case 3: // fetch address low
- dl = Fetch((byte)(addressBus));
- break;
- case 4: // fetch address high
- addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8));
- break;
- }
- }
-
- void GetAddressIndOffY(bool TakeExtraCycleOnlyIfPageBoundaryCrossed)
- {
- // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed.
-
- // either way, the general gist of this function is:
- // Fetch the value from the PC. use that 8 bit location on the zero page to fetch the High and Low byte of the new Address Bus location, then add Y to that.
- if (TakeExtraCycleOnlyIfPageBoundaryCrossed)
- {
- switch (operationCycle)
- {
- case 1: // fetch pointer address
- addressBus = Fetch(programCounter);
- programCounter++;
- break;
- case 2: // fetch address low
- dl = Fetch((byte)(addressBus));
- break;
- case 3: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8));
- temporaryAddress = addressBus;
- H = (byte)(addressBus >> 8);
- if (((temporaryAddress + Y) & 0xFF00) == (temporaryAddress & 0xFF00))
- {
- operationCycle++; //skip next cycle
- }
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF));
- break;
- case 4: // increment high byte
- dl = Fetch(addressBus); // dummy read
- H = (byte)(addressBus >> 8);
- H++; // This is incremented.
- addressBus += 0x100;
- break;
- }
- }
- else
- {
- switch (operationCycle)
- {
- case 1: // fetch pointer address
- addressBus = Fetch(programCounter);
- programCounter++;
- break;
- case 2: // fetch address low
- dl = Fetch((byte)(addressBus));
- break;
- case 3: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | (Fetch((byte)(addressBus + 1)) << 8));
- temporaryAddress = addressBus;
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF));
- break;
- case 4: // increment high byte
- dl = Fetch(addressBus); // dummy read
- H = (byte)(addressBus >> 8);
- H++; // This is incremented.
- if (((temporaryAddress + Y) & 0xFF00) != (temporaryAddress & 0xFF00))
- {
- addressBus += 0x100; // really, this would just replace the high byte with H, but this is less computationally expensive
- }
- break;
- }
- }
-
- }
-
- void GetAddressZPOffX()
- {
- // Fetch the value from the PC, then add X to that.
- if (operationCycle == 1)
- {
- // fetch address
- addressBus = Fetch(programCounter);
- programCounter++;
- }
- else
- {
- // dummy read, and add X
- dl = Fetch(addressBus);
- addressBus = (byte)(addressBus + X);
- }
- }
-
- void GetAddressZPOffY()
- {
- // Fetch the value from the PC, then add Y to that.
- if (operationCycle == 1)
- {
- // fetch address
- addressBus = Fetch(programCounter);
- programCounter++;
- }
- else
- {
- // dummy read, and add Y
- dl = Fetch(addressBus);
- addressBus = (byte)(addressBus + Y);
- }
- }
-
- void GetAddressAbsOffX(bool TakeExtraCycleIfPageBoundaryCrossed)
- {
- // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed.
-
- // Fetch the High and Low byte values from the byte at the PC, then add X.
- if (TakeExtraCycleIfPageBoundaryCrossed)
- {
- switch (operationCycle)
- {
- case 1: // fetch address low
- dl = Fetch(programCounter);
- programCounter++;
-
- break;
- case 2: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | Fetch(programCounter) << 8);
- temporaryAddress = addressBus;
- H = (byte)(addressBus >> 8);
-
- if (((temporaryAddress + X) & 0xFF00) == (temporaryAddress & 0xFF00))
- {
- operationCycle++; //skip next cycle
- FixHighByte = false;
- }
- else
- {
- FixHighByte = true;
- }
-
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + X) & 0xFF));
- programCounter++;
-
- break;
- case 3: // increment high byte
- dl = Fetch(addressBus);
- H = (byte)(addressBus >> 8);
- H++;
- if (FixHighByte)
- {
- addressBus += 0x100;
- }
- break;
- case 4: // dummy read
- dl = Fetch(addressBus); // read into pd
- break;
- }
- }
- else
- {
- switch (operationCycle)
- {
- case 1: // fetch address low
- dl = Fetch(programCounter);
- programCounter++;
-
- break;
- case 2: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | Fetch(programCounter) << 8);
- temporaryAddress = addressBus;
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + X) & 0xFF));
- programCounter++;
-
- break;
- case 3: // fix high byte if applicable
- dl = Fetch(addressBus); // read into pd
- H = (byte)(addressBus >> 8);
- H++;
- if (((temporaryAddress + X) & 0xFF00) != (temporaryAddress & 0xFF00))
- {
- addressBus += 0x100;
- }
- break;
- case 4: // dummy read
- dl = Fetch(addressBus); // read into pd
- break;
- }
- }
- }
- bool FixHighByte = false;
- void GetAddressAbsOffY(bool TakeExtraCycleIfPageBoundaryCrossed)
- {
- // Some instructions will always take 4 cycles to determine the address, and others will normally take 3, but take the extra cycle if a page boundary was crossed.
-
- // Fetch the High and Low byte values from the byte at the PC, then add Y.
- if (TakeExtraCycleIfPageBoundaryCrossed)
- {
- switch (operationCycle)
- {
- case 1: // fetch address low
- dl = Fetch(programCounter);
- programCounter++;
-
- break;
- case 2: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | Fetch(programCounter) << 8);
- temporaryAddress = addressBus;
- H = (byte)(addressBus >> 8);
-
- if (((temporaryAddress + Y) & 0xFF00) == (temporaryAddress & 0xFF00))
- {
- operationCycle++; //skip next cycle
- FixHighByte = false;
- }
- else
- {
- FixHighByte = true;
- }
-
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF));
- programCounter++;
-
- break;
- case 3: // increment high byte
- dl = Fetch(addressBus);
- H = (byte)(addressBus >> 8);
- H++;
- if (FixHighByte)
- {
- addressBus += 0x100;
- }
- break;
- case 4: // dummy read
- dl = Fetch(addressBus); // read into databus
- break;
- }
- }
- else
- {
- switch (operationCycle)
- {
- case 1: // fetch address low
- dl = Fetch(programCounter);
- programCounter++;
-
- break;
- case 2: // fetch address high, add Y to low byte
- addressBus = (ushort)(dl | Fetch(programCounter) << 8);
- temporaryAddress = addressBus;
- addressBus = (ushort)((addressBus & 0xFF00) | ((addressBus + Y) & 0xFF));
- programCounter++;
-
- break;
- case 3: // fix high byte if applicable
- dl = Fetch(addressBus); // read into pd
- H = (byte)(addressBus >> 8);
- H++;
- if (((temporaryAddress + Y) & 0xFF00) != (temporaryAddress & 0xFF00))
- {
- addressBus += 0x100;
- }
- break;
- case 4: // dummy read
- dl = Fetch(addressBus); // read into pd
- break;
- }
- }
- }
- #endregion
-
- #region OpFunctions
-
- // This is not every instruction!!!
- // These are just the ones that have frequently repeated logic.
- // Instructions like STA just simply `Store(A, Address);`, which doesn't need a jump somewhere to do that.
- // Many undocumented opcodes have unique behavior that is also just handled in the switch statement, instead of jumping to a unique function.
-
- void Op_ORA(byte Input)
- {
- // Bitwise OR A with some value
- A |= Input;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_ASL(byte Input, ushort Address)
- {
- // Arithmetic shift left.
- flag_Carry = Input >= 0x80; // If bit 7 was set before the shift
- Input <<= 1;
- Store(Input, Address); // store the result at the target address
- flag_Negative = Input >= 0x80; // if bit 7 of the result is set
- flag_Zero = Input == 0x00; // if all bits are cleared
- }
-
- void Op_ASL_A()
- {
- // Arithmetic shift left the Accumulator
- flag_Carry = A >= 0x80; // If bit 7 was set before the shift
- A <<= 1;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_SLO(byte Input, ushort Address)
- {
- // Undocumented Opcode: equivalent to ASL + ORA
- Op_ASL(Input, Address);
- Op_ORA(dataBus);
- }
-
- void Op_AND(byte Input)
- {
- // Bitwise AND with A
- A &= Input;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_ROL(byte Input, ushort Address)
- {
- // Rotate Left
- bool Futureflag_Carry = Input >= 0x80;
- Input <<= 1;
- if (flag_Carry)
- {
- Input |= 1; // Put the old carry flag value into bit 0
- }
- Store(Input, Address); // store the result at the target address
- flag_Carry = Futureflag_Carry; // if bit 7 of the initial value was set
- flag_Negative = Input >= 0x80; // if bit 7 of the result is set
- flag_Zero = Input == 0x00; // if all bits are cleared
- }
-
- void Op_ROL_A()
- {
- // Rotate Left the Accumulator
- bool Futureflag_Carry = A >= 0x80;
- A <<= 1;
- if (flag_Carry)
- {
- A |= 1; // Put the old carry flag value into bit 0
- }
- flag_Carry = Futureflag_Carry; // if bit 7 of the initial value was set
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_RLA(byte Input, ushort Address)
- {
- // Undocumented Opcode: equivalent to ROL + AND
- Op_ROL(Input, Address);
- Op_AND(dataBus);
- }
-
- void Op_EOR(byte Input)
- {
- // Bitwise Exclusive OR A
- A ^= Input;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_LSR(byte Input, ushort Address)
- {
- // Logical Shift Right
- flag_Carry = (Input & 1) == 1; // If bit 0 of the initial value is set
- Input >>= 1;
- Store(Input, Address); // store the result at the target address
- flag_Negative = Input >= 0x80; // if bit 7 of the result is set
- flag_Zero = Input == 0x00; // if all bits are cleared
- }
-
- void Op_LSR_A()
- {
- // Logical Shift Right the Accumulator
- flag_Carry = (A & 1) == 1; // If bit 0 of the initial value is set
- A >>= 1;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_SRE(byte Input, ushort Address)
- {
- // Undocumented Opcode: equivalent to LSR + EOR
- Op_LSR(Input, Address);
- Op_EOR(dataBus);
- }
-
- void Op_ADC(byte Input)
- {
- // Add with Carry
- int Intput = Input + A + (flag_Carry ? 1 : 0);
- flag_Overflow = (~(A ^ Input) & (A ^ Intput) & 0x80) != 0;
- flag_Carry = Intput > 0xFF;
- A = (byte)Intput;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_ROR(byte Input, ushort Address)
- {
- // Rotate Right
- bool FutureFlag_Carry = (Input & 1) == 1; // if bit 0 was set before the shift
- Input >>= 1;
- if (flag_Carry)
- {
- Input |= 0x80; // put the old carry flag into bit 7
- }
- Store(Input, Address);
- flag_Carry = FutureFlag_Carry; // if bit 0 was set before the shift
- flag_Negative = Input >= 0x80; // if bit 7 of the result is set
- flag_Zero = Input == 0x00; // if all bits are cleared
- }
-
- void Op_ROR_A()
- {
- bool FutureFlag_Carry = (A & 1) == 1;
- A >>= 1;
- if (flag_Carry)
- {
- A |= 0x80; // put the old carry flag into bit 7
- }
- flag_Carry = FutureFlag_Carry; // if bit 0 was set before the shift
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_RRA(byte Input, ushort Address)
- {
- // Undocumented Opcode: equivalent to ROR + ADC
- Op_ROR(Input, Address);
- Op_ADC(dataBus);
- }
-
- void Op_CMP(byte Input)
- {
- // Compare A
- flag_Zero = A == Input; // if A is equal to the value being compared
- flag_Carry = A >= Input;// if A is greater than the value being compared
- flag_Negative = ((byte)(A - Input) >= 0x80); // if A - the value being compared would leave bit 7 set
- }
-
- void Op_CPY(byte Input)
- {
- // Compare Y
- flag_Zero = Y == Input; // if Y is equal to the value being compared
- flag_Carry = Y >= Input;// if Y is greater than the value being compared
- flag_Negative = ((byte)(Y - Input) >= 0x80); // if Y - the value being compared would leave bit 7 set
- }
-
- void Op_CPX(byte Input)
- {
- // Compare X
- flag_Zero = X == Input; // if X is equal to the value being compared
- flag_Carry = X >= Input;// if X is greater than the value being compared
- flag_Negative = ((byte)(X - Input) >= 0x80); // if X - the value being compared would leave bit 7 set
- }
-
- void Op_SBC(byte Input)
- {
- // Subtract with Carry
- int Intput = A - Input;
- if (!flag_Carry)
- {
- Intput -= 1;
- }
- flag_Overflow = ((A ^ Input) & (A ^ Intput) & 0x80) != 0;
- flag_Carry = Intput >= 0;
- A = (byte)Intput;
- flag_Negative = A >= 0x80; // if bit 7 of the result is set
- flag_Zero = A == 0x00; // if all bits are cleared
- }
-
- void Op_INC(ushort Address)
- {
- // Increment
- dl++; // The value read is currently stored in the PreDecode register
- flag_Zero = dl == 0; // if all bits are cleared
- flag_Negative = dl >= 0x80; // if bit 7 of the result is set
- Store(dl, Address);
-
- }
-
- void Op_DEC(ushort Address)
- {
- // Decrement
- dl--; // The value read is currently stored in the PreDecode register
- flag_Zero = dl == 0; // if all bits are cleared
- flag_Negative = dl >= 0x80; // if bit 7 of the result is set
- Store(dl, Address);
-
- }
-
-
- #endregion
-
- // this is the tracelogger.
- // I call this function during the first cycle of every instruction.
-
- public ushort DebugRange_Low = 0x0000;
- public ushort DebugRange_High = 0xFFFF;
- public bool OnlyDebugInRange = false;
- void Debug()
- {
- if (OnlyDebugInRange)
- {
- if (programCounter < DebugRange_Low || programCounter > DebugRange_High)
- {
- return;
- }
- }
-
- string addr = programCounter.ToString("X4");
- string bytes = "";
- int b = 0;
- while (b < Documentation.OpDocs[opCode].length)
- {
- string t = Observe((ushort)(programCounter + b)).ToString("X");
- if (t.Length == 1) { t = "0" + t; }
- t += " ";
- bytes = bytes + t;
- b++;
- }
-
- if (bytes.Length < 7)
- {
- bytes += "\t";
- }
-
- string sA = A.ToString("X2");
- string sX = X.ToString("X2");
- string sY = Y.ToString("X2");
- string sS = stackPointer.ToString("X2");
-
- string Flags = "";
- Flags += flag_Negative ? "N" : "n";
- Flags += flag_Overflow ? "V" : "v";
- Flags += "--";
- Flags += flag_Decimal ? "D" : "d";
- Flags += flag_Interrupt ? "I" : "i";
- Flags += flag_Zero ? "Z" : "z";
- Flags += flag_Carry ? "C" : "c";
-
-
- if (DebugLog == null)
- {
- DebugLog = new StringBuilder();
- }
-
- string instruction = Documentation.OpDocs[opCode].mnemonic + " ";
-
- if (opCode == 0)
- {
- if (DoReset)
- {
- instruction = "RESET";
- bytes = "--\t";
- }
- else if (DoNMI)
- {
- instruction = "NMI";
- bytes = "--\t";
-
- }
- else if (DoIRQ)
- {
- instruction = "IRQ";
- bytes = "--\t";
-
- }
- }
-
- ushort Target = 0;
-
- switch (Documentation.OpDocs[opCode].mode)
- {
- case "i": //implied
- break;
- case "d": //zp
- instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = Observe((ushort)(programCounter + 1)); break;
- case "a": //abs
- instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = (ushort)((Observe((ushort)(programCounter + 2)) << 8) | Observe((ushort)(programCounter + 1))); break;
- case "r": //relative
- instruction += "$" + ((ushort)(programCounter + (sbyte)Observe((ushort)(programCounter + 1))) + 2).ToString("X4"); Target = (ushort)((ushort)(programCounter + (sbyte)Observe((ushort)(programCounter + 1))) + 2); break;
- case "#v": //imm
- instruction += "#" + Observe((ushort)(programCounter + 1)).ToString("X2"); Target = Observe((ushort)(programCounter + 1)); break;
- case "A": //A
- instruction += "A"; break;
- case "(a)": //(ind)
- instruction += "($" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ") -> $" + (Observe((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100)) + Observe((ushort)((Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100) + 1)) * 0x100).ToString("X4"); Target = (ushort)(Observe((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100)) + Observe((ushort)((Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100) + 1)) * 0x100); break;
- case "d,x": //zp, x
- instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X -> $" + (Observe((ushort)(programCounter + 1)) + X).ToString("X2"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + X); break;
- case "d,y": //zp, y
- instruction += "<$" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", Y -> $" + (Observe((ushort)(programCounter + 1)) + Y).ToString("X2"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Y); break;
- case "a,x": //abs, x
- instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X -> $" + ((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + X)).ToString("X4"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + X); break;
- case "a,y": //abs, Y
- instruction += "$" + Observe((ushort)(programCounter + 2)).ToString("X2") + Observe((ushort)(programCounter + 1)).ToString("X2") + ", Y -> $" + ((ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + Y)).ToString("X4"); Target = (ushort)(Observe((ushort)(programCounter + 1)) + Observe((ushort)(programCounter + 2)) * 0x100 + Y); break;
- case "(d),y": //(zp), Y
- instruction += "($00" + Observe((ushort)(programCounter + 1)).ToString("X2") + "), Y -> $" + ((ushort)(Observe(Observe((ushort)(programCounter + 1))) + Observe((ushort)((byte)(Observe((ushort)(programCounter + 1)) + 1) + (ushort)((Observe((ushort)(programCounter + 1)) & 0xFF00)))) * 0x100) + Y).ToString("X4"); Target = (ushort)((ushort)(Observe(Observe((ushort)(programCounter + 1))) + Observe((ushort)((byte)(Observe((ushort)(programCounter + 1)) + 1) + (ushort)((Observe((ushort)(programCounter + 1)) & 0xFF00)))) * 0x100) + Y); break;
- case "(d,x)": //(zp, X)
- instruction += "($00" + Observe((ushort)(programCounter + 1)).ToString("X2") + ", X) -> $" + (Observe((byte)(Observe((ushort)(programCounter + 1)) + X)) + Observe((ushort)((byte)((byte)(Observe((ushort)(programCounter + 1)) + X) + 1) + (ushort)(((byte)(Observe((ushort)(programCounter + 1)) + X) & 0xFF00)))) * 0x100).ToString("X4"); Target = (ushort)(Observe((byte)(Observe((ushort)(programCounter + 1)) + X)) + Observe((ushort)((byte)((byte)(Observe((ushort)(programCounter + 1)) + X) + 1) + (ushort)(((byte)(Observe((ushort)(programCounter + 1)) + X) & 0xFF00)))) * 0x100); break;
-
- }
-
- if (Target == 0x2007)
- {
- instruction += " | PPU[$" + PPU_v.ToString("X4") + "]";
- }
-
-
-
- if (instruction.Length < 8)
- {
- instruction += "\t";
- }
- if (instruction.Length < 17)
- {
- instruction += "\t";
- }
-
- int PPUCycle = 0;
- String PPUPos = "(" + PPU_Scanline + ", " + PPU_Dot + ")";
-
-
-
- if (totalCycles < 27395)
- {
- PPUCycle = PPU_Scanline * 341 + PPU_Dot;
- }
- else
- {
- if (PPU_Scanline >= 241)
- {
- PPUCycle = (PPU_Scanline - 241) * 341 + PPU_Dot;
- }
- else
- {
- PPUCycle = (PPU_Scanline + 21) * 341 + PPU_Dot;
- }
- }
-
- if ((PPUPos.Length + PPUCycle.ToString().Length + 1) < 13)
- {
- PPUPos += "\t";
- }
-
- string LogLine = "$" + addr + "\t" + bytes + "\t" + instruction + "\tA:" + sA + "\tX:" + sX + "\tY:" + sY + "\tSP:" + sS + "\t" + Flags + "\tCycle: " + totalCycles;
-
- bool LogExtra = true;
- if (LogExtra)
- {
- //string TempLine_APU_Full = LogLine + "\t" + "DMC :: S_Addr: $" + APU_DMC_SampleAddress.ToString("X4") + "\t S_Length:" + APU_DMC_SampleLength.ToString() + "\t AddrCounter: $" + APU_DMC_AddressCounter.ToString("X4") + "\t BytesLeft:" + APU_DMC_BytesRemaining.ToString() + "\t Shifter:" + APU_DMC_Shifter.ToString() + ":" + APU_DMC_ShifterBitsRemaining.ToString() + "\tDMC_Timer:" + (APU_PutCycle ? APU_ChannelTimer_DMC : (APU_ChannelTimer_DMC - 1)).ToString();
-
- //string TempLine_APUFrameCounter_IRQs = LogLine + " \t$4015: " + Observe(0x4015).ToString("X2") + "\t APU_FrameCounter: " + APU_Framecounter.ToString() + " \tEvenCycle = : " + APU_PutCycle + " \tDoIRQ = " + DoIRQ;
-
- //string TempLine_PPU = LogLine + "\t$2000:" + Observe(0x2000).ToString("X2") + "\t$2001:" + Observe(0x2001).ToString("X2") + "\t$2002:" + Observe(0x2002).ToString("X2") + "\tR/W Addr:" + PPU_v.ToString("X4") + "\tPPUAddrLatch:" + PPUAddrLatch + "\tPPU AddressBus: " + PPU_AddressBus.ToString("X4");
- //string TempLine_PPU2 = LogLine + "\tVRAMAddress:" + PPU_v.ToString("X4") + "\tPPUReadBuffer:" + PPU_ReadBuffer.ToString("X2");
- string TempLine_PPU3 = LogLine + "\tPPU_Coords (" + PPU_Scanline + ", " + PPU_Dot + ")\todd:" + PPU_OddFrame.ToString() + "\tv: " + PPU_v.ToString("X4")
- /*
- + "\tOAM2: [";
-
- for(int i = 0; i < OAM2.Length; i++)
- {
- TempLine_PPU3 += OAM2[i].ToString("X2");
- if(i < OAM2.Length - 1)
- {
- TempLine_PPU3 += ", ";
- }
- else
- {
- TempLine_PPU3 += "]\t OAMBuffer:" + PPU_OAMBuffer.ToString("X2");
- }
- }*/
- + "\tG/P:" + (APU_PutCycle ? "Put" : "Get") + " FC:" + APU_Framecounter.ToString();
-
- //String TempLine_Mapper = LogLine + Cart.MapperChip.AppendToDebugLog();
-
- DebugLog.AppendLine(TempLine_PPU3);
- }
- else
- {
- DebugLog.AppendLine(LogLine);
- }
-
-
- }
-
- void Debug_PPU()
- {
- string dotColor = "";
- if (PPU_ShowScreenBorders || (PPU_Scanline < 240 && PPU_Dot <= 256 && PPU_Dot > 0))
- {
- dotColor = "COLOR: " + DotColor.ToString("X2") + "\t";
- }
- else
- {
- dotColor = "COLOR: BL\t";
- }
- string MMC3 = Cart.MapperChip.AppendToDebugLog();
- string v = "v: " + PPU_v.ToString("X4") + "\t";
- string Addr = "Address: " + PPU_AddressBus.ToString("X4") + "\t";
- string Octal = "OctalLatch: " + PPU_OctalLatch.ToString("X2") + "\t";
- string enabled = "[" + (PPU_Mask_ShowSprites ? "S" : "-") + (PPU_Mask_ShowBackground ? "B" : "-") + "]\t";
- string ALE = "ALE: " + (PPU_ALE?"1":"0") + "\t";
- string RD = "RD: " + (PPU_READ ? "1" : "0") + "\t";
- string BSR_Lo = "BSRL: " + Convert.ToString(PPU_BackgroundPatternShiftRegisterL, 2).PadLeft(16, '0') + "\t";
- string BSR_Hi = "BSRH: " + Convert.ToString(PPU_BackgroundPatternShiftRegisterH, 2).PadLeft(16, '0') + "\t";
- string OamBuf = "OAM Buffer: " + PPU_OAMBuffer.ToString("X2") + "\t";
-
- string EightCycleRead = "";
- if ((PPU_Dot <= 256) || (PPU_Dot >= 321)) // if this is a visible pixel, or preparing the start of next scanline
- {
- if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground) && (PPU_Scanline < 240))
- {
- if (PPU_Dot >= 1)
- {
- EightCycleRead = "8CycleReadTick (bg): " + ((byte)((PPU_Dot + 7) & 7)).ToString() + "\t";
- }
- else
- {
- EightCycleRead = "8CycleReadTick (bg): 4" + "\t";
- }
- }
- else
- {
- EightCycleRead = "8CycleReadTick (bg): -\t";
- }
- }
- else
- { // HBlank, or dot 0.
- if ((PPU_Mask_ShowSprites || PPU_Mask_ShowBackground) && (PPU_Scanline < 240))
- {
- EightCycleRead = "8CycleReadTick (sp): " + ((byte)((PPU_Dot + 7) & 7)).ToString() + "\t";
- }
- else
- {
- EightCycleRead = "8CycleReadTick (sp): -\t";
- }
- }
-
- string LogLine = "(" + PPU_Scanline.ToString() + ", " + PPU_Dot.ToString() + ") \t" + v + Addr + Octal + EightCycleRead + dotColor + enabled + MMC3 + ALE + RD + BSR_Lo + BSR_Hi + OamBuf;
- DebugLog.AppendLine(LogLine);
- }
-
- public List SaveState()
- {
- List State = new List();
-
- State.Add((byte)programCounter);
- State.Add((byte)(programCounter >> 8));
- State.Add((byte)addressBus);
- State.Add((byte)(addressBus >> 8));
- State.Add((byte)temporaryAddress);
- State.Add((byte)(temporaryAddress >> 8));
- State.Add((byte)OAMAddressBus);
- State.Add((byte)(OAMAddressBus >> 8));
- State.Add((byte)PPU_v);
- State.Add((byte)(PPU_v >> 8));
- State.Add((byte)PPU_t);
- State.Add((byte)(PPU_t >> 8));
-
- State.Add((byte)totalCycles);
- State.Add((byte)(totalCycles >> 8));
- State.Add((byte)(totalCycles >> 16));
- State.Add((byte)(totalCycles >> 24));
-
- State.Add(PPUClock);
- State.Add(CPUClock);
-
- State.Add(operationCycle);
- State.Add(opCode);
-
- State.Add(dl);
- State.Add(dataBus);
- State.Add(A);
- State.Add(X);
- State.Add(Y);
- State.Add(stackPointer);
- status = flag_Carry ? (byte)0x01 : (byte)0;
- status += flag_Zero ? (byte)0x02 : (byte)0;
- status += flag_Interrupt ? (byte)0x04 : (byte)0;
- status += flag_Decimal ? (byte)0x08 : (byte)0;
- status += flag_Overflow ? (byte)0x40 : (byte)0;
- status += flag_Negative ? (byte)0x80 : (byte)0;
- State.Add(status);
-
- State.Add(specialBus);
- State.Add(H);
- State.Add((byte)(IgnoreH ? 1 : 0));
-
- State.Add((byte)(CPU_SYNC ? 1 : 0));
- State.Add((byte)(CPU_Read ? 1 : 0));
- State.Add((byte)(DoBRK ? 1 : 0));
- State.Add((byte)(DoNMI ? 1 : 0));
- State.Add((byte)(DoIRQ ? 1 : 0));
- State.Add((byte)(DoReset ? 1 : 0));
- State.Add((byte)(DoOAMDMA ? 1 : 0));
- State.Add((byte)(FirstCycleOfOAMDMA ? 1 : 0));
- State.Add((byte)(DoDMCDMA ? 1 : 0));
- State.Add(DMCDMADelay);
- State.Add(CannotRunDMCDMARightNow);
- State.Add(DMAPage);
- State.Add(DMAAddress);
- State.Add((byte)(APU_ControllerPortsStrobing ? 1 : 0));
- State.Add((byte)(APU_ControllerPortsStrobed ? 1 : 0));
- State.Add(ControllerPort1);
- State.Add(ControllerPort2);
- State.Add(ControllerShiftRegister1);
- State.Add(ControllerShiftRegister2);
- State.Add(Controller1ShiftCounter);
- State.Add(Controller2ShiftCounter);
- State.Add((byte)(dataPinsAreNotFloating ? 1 : 0));
-
- State.Add((byte)(APU_PutCycle ? 1 : 0));
- State.Add((byte)(APU_Status_DMCInterrupt ? 1 : 0));
- State.Add((byte)(APU_Status_FrameInterrupt ? 1 : 0));
- State.Add((byte)(APU_Status_DMC ? 1 : 0));
- State.Add((byte)(APU_Status_DelayedDMC ? 1 : 0));
- State.Add((byte)(APU_Status_Noise ? 1 : 0));
- State.Add((byte)(APU_Status_Triangle ? 1 : 0));
- State.Add((byte)(APU_Status_Pulse2 ? 1 : 0));
- State.Add((byte)(APU_Status_Pulse1 ? 1 : 0));
- State.Add((byte)(Clearing_APU_FrameInterrupt ? 1 : 0));
- State.Add(APU_DelayedDMC4015);
- State.Add((byte)(APU_ImplicitAbortDMC4015 ? 1 : 0));
- State.Add((byte)(APU_SetImplicitAbortDMC4015 ? 1 : 0));
- foreach (Byte b in APU_Register) { State.Add(b); }
- State.Add((byte)(APU_FrameCounterMode ? 1 : 0));
- State.Add((byte)(APU_FrameCounterInhibitIRQ ? 1 : 0));
- State.Add(APU_FrameCounterReset);
- State.Add((byte)APU_Framecounter);
- State.Add((byte)(APU_Framecounter >> 8));
- State.Add((byte)(APU_QuarterFrameClock ? 1 : 0));
- State.Add((byte)(APU_HalfFrameClock ? 1 : 0));
- State.Add((byte)(APU_Envelope_StartFlag ? 1 : 0));
- State.Add((byte)(APU_Envelope_DividerClock ? 1 : 0));
- State.Add(APU_Envelope_DecayLevel);
- State.Add(APU_LengthCounter_Pulse1);
- State.Add(APU_LengthCounter_Pulse2);
- State.Add(APU_LengthCounter_Triangle);
- State.Add(APU_LengthCounter_Noise);
- State.Add((byte)(APU_LengthCounter_HaltPulse1 ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_HaltPulse2 ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_HaltTriangle ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_HaltNoise ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_ReloadPulse1 ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_ReloadPulse2 ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_ReloadTriangle ? 1 : 0));
- State.Add((byte)(APU_LengthCounter_ReloadNoise ? 1 : 0));
- State.Add(APU_LengthCounter_ReloadValuePulse1);
- State.Add(APU_LengthCounter_ReloadValuePulse2);
- State.Add(APU_LengthCounter_ReloadValueTriangle);
- State.Add(APU_LengthCounter_ReloadValueNoise);
- State.Add((byte)APU_ChannelTimer_Pulse1);
- State.Add((byte)(APU_ChannelTimer_Pulse1 >> 8));
- State.Add((byte)APU_ChannelTimer_Pulse2);
- State.Add((byte)(APU_ChannelTimer_Pulse2 >> 8));
- State.Add((byte)APU_ChannelTimer_Triangle);
- State.Add((byte)(APU_ChannelTimer_Triangle >> 8));
- State.Add((byte)APU_ChannelTimer_Noise);
- State.Add((byte)(APU_ChannelTimer_Noise >> 8));
- State.Add((byte)APU_ChannelTimer_DMC);
- State.Add((byte)(APU_ChannelTimer_DMC >> 8));
- State.Add((byte)(APU_DMC_EnableIRQ ? 1 : 0));
- State.Add((byte)(APU_DMC_Loop ? 1 : 0));
- State.Add((byte)APU_DMC_Rate);
- State.Add((byte)(APU_DMC_Rate >> 8));
- State.Add(APU_DMC_Output);
- State.Add((byte)APU_DMC_SampleAddress);
- State.Add((byte)(APU_DMC_SampleAddress >> 8));
- State.Add((byte)APU_DMC_SampleLength);
- State.Add((byte)(APU_DMC_SampleLength >> 8));
- State.Add((byte)APU_DMC_BytesRemaining);
- State.Add((byte)(APU_DMC_BytesRemaining >> 8));
- State.Add(APU_DMC_Buffer);
- State.Add((byte)APU_DMC_AddressCounter);
- State.Add((byte)(APU_DMC_AddressCounter >> 8));
- State.Add(APU_DMC_Shifter);
- State.Add(APU_DMC_ShifterBitsRemaining);
- State.Add((byte)(APU_Silent ? 1 : 0));
-
- State.Add(SecondaryOAMSize);
- State.Add(OAM2Address);
- State.Add((byte)(OAM2Overflowed ? 1 : 0));
- State.Add(SpriteEvaluationTick);
- State.Add((byte)(OAMAddressOverflowedDuringSpriteEvaluation ? 1 : 0));
- State.Add(OAM2Address);
- State.Add(PPUBus);
- for (int i = 0; i < 8; i++)
- {
- State.Add((byte)PPUBusDecay[i]);
- State.Add((byte)(PPUBusDecay[i] >> 8));
- State.Add((byte)(PPUBusDecay[i] >> 16));
- State.Add((byte)(PPUBusDecay[i] >> 24));
- }
- State.Add(PPUOAMAddress);
- State.Add((byte)(PPUStatus_VBlank ? 1 : 0));
- State.Add((byte)(PPUStatus_SpriteZeroHit ? 1 : 0));
- State.Add((byte)(PPUStatus_SpriteOverflow ? 1 : 0));
- State.Add((byte)(PPUStatus_PendingSpriteZeroHit ? 1 : 0));
- State.Add((byte)(PPUStatus_PendingSpriteZeroHit2 ? 1 : 0));
- State.Add((byte)(PPUStatus_SpriteZeroHit_Delayed ? 1 : 0));
- State.Add((byte)(PPUStatus_SpriteOverflow_Delayed ? 1 : 0));
-
- State.Add((byte)(PPU_Spritex16 ? 1 : 0));
- State.Add((byte)PPU_Scanline);
- State.Add((byte)(PPU_Scanline >> 8));
- State.Add((byte)PPU_Dot);
- State.Add((byte)(PPU_Dot >> 8));
- State.Add((byte)(PPU_VRegisterChangedOutOfVBlank ? 1 : 0));
- State.Add((byte)(PPU_OAMCorruptionRenderingDisabledOutOfVBlank ? 1 : 0));
- State.Add((byte)(PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant ? 1 : 0));
- State.Add((byte)(PPU_PendingOAMCorruption ? 1 : 0));
- State.Add(PPU_OAMCorruptionIndex);
- State.Add((byte)(PPU_OAMCorruptionRenderingEnabledOutOfVBlank ? 1 : 0));
- State.Add((byte)(PPU_OAMEvaluationCorruptionOddCycle ? 1 : 0));
- State.Add((byte)(PPU_OAMEvaluationObjectInRange ? 1 : 0));
- State.Add((byte)(PPU_OAMEvaluationObjectInXRange ? 1 : 0));
- State.Add((byte)(PPU_PaletteCorruptionRenderingDisabledOutOfVBlank ? 1 : 0));
- State.Add(PPU_AttributeLatchRegister);
- State.Add((byte)PPU_BackgroundAttributeShiftRegisterL);
- State.Add((byte)(PPU_BackgroundAttributeShiftRegisterL >> 8));
- State.Add((byte)PPU_BackgroundAttributeShiftRegisterH);
- State.Add((byte)(PPU_BackgroundAttributeShiftRegisterH >> 8));
- State.Add((byte)PPU_BackgroundPatternShiftRegisterL);
- State.Add((byte)(PPU_BackgroundPatternShiftRegisterL >> 8));
- State.Add((byte)PPU_BackgroundPatternShiftRegisterH);
- State.Add((byte)(PPU_BackgroundPatternShiftRegisterH >> 8));
- State.Add(PPU_FineXScroll);
- for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShiftRegisterL[i]); }
- for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShiftRegisterH[i]); }
- for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteAttribute[i]); }
- for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteXposition[i]); }
- for (int i = 0; i < 8; i++) { State.Add(PPU_SpriteShifterCounter[i]); }
- State.Add((byte)PPU_SpritePattern);
- State.Add((byte)PPU_OAM_VerticalOffset);
- State.Add((byte)(PPU_OAM_VerticalOffset >> 8));
- State.Add((byte)(PPU_NextScanlineContainsSpriteZero ? 1 : 0));
- State.Add((byte)(PPU_CurrentScanlineContainsSpriteZero ? 1 : 0));
- State.Add(PPU_SpritePatternL);
- State.Add(PPU_SpritePatternH);
- State.Add((byte)(PPU_Mask_Greyscale ? 1 : 0));
- State.Add((byte)(PPU_Mask_8PxShowBackground ? 1 : 0));
- State.Add((byte)(PPU_Mask_8PxShowSprites ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowBackground ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowSprites ? 1 : 0));
- State.Add((byte)(PPU_Mask_EmphasizeRed ? 1 : 0));
- State.Add((byte)(PPU_Mask_EmphasizeGreen ? 1 : 0));
- State.Add((byte)(PPU_Mask_EmphasizeBlue ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowBackground_Delayed ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowSprites_Delayed ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowBackground_Instant ? 1 : 0));
- State.Add((byte)(PPU_Mask_ShowSprites_Instant ? 1 : 0));
- State.Add(PPU_LowBitPlane);
- State.Add(PPU_HighBitPlane);
- State.Add(PPU_Attribute);
- State.Add((byte)(PPU_CanDetectSpriteZeroHit ? 1 : 0));
- State.Add((byte)(PPU_OddFrame ? 1 : 0));
- State.Add(PaletteRAMAddress);
- State.Add((byte)(ThisDotReadFromPaletteRAM ? 1 : 0));
- State.Add((byte)(NMI_PinsSignal ? 1 : 0));
- State.Add((byte)(NMI_PreviousPinsSignal ? 1 : 0));
- State.Add((byte)(IRQ_LevelDetector ? 1 : 0));
- State.Add((byte)(NMILine ? 1 : 0));
- State.Add((byte)(IRQLine ? 1 : 0));
- State.Add((byte)(CopyV ? 1 : 0));
- State.Add((byte)(SkippedPreRenderDot341 ? 1 : 0));
- State.Add((byte)(OamCorruptedOnOddCycle ? 1 : 0));
- State.Add(PPU_OAMBuffer);
- State.Add(PPU_RenderTemp);
- State.Add((byte)(PPU_Commit_NametableFetch ? 1 : 0));
- State.Add((byte)(PPU_Commit_AttributeFetch ? 1 : 0));
- State.Add((byte)(PPU_Commit_PatternLowFetch ? 1 : 0));
- State.Add((byte)(PPU_Commit_PatternHighFetch ? 1 : 0));
-
- State.Add((byte)PPU_VRAM_MysteryAddress);
- State.Add((byte)(PPU_VRAM_MysteryAddress >> 8));
- State.Add((byte)PPU_AddressBus);
- State.Add((byte)(PPU_AddressBus >> 8));
- State.Add(PPU_Update2006Delay);
- State.Add(PPU_Update2005Delay);
- State.Add(PPU_Update2005Value);
- State.Add(PPU_Update2001Value);
- State.Add((byte)PPU_Update2006Value);
- State.Add((byte)(PPU_Update2006Value >> 8));
- State.Add((byte)PPU_Update2006Value_Temp);
- State.Add((byte)(PPU_Update2006Value_Temp >> 8));
- State.Add((byte)(PPU_WasRenderingBefore2001Write ? 1 : 0));
- State.Add(PPU_ReadBuffer);
- State.Add((byte)(PPUAddrLatch ? 1 : 0));
- State.Add((byte)(PPUControlIncrementMode32 ? 1 : 0));
- State.Add((byte)(PPUControl_NMIEnabled ? 1 : 0));
- State.Add((byte)(PPU_PatternSelect_Sprites ? 1 : 0));
- State.Add((byte)(PPU_PatternSelect_Background ? 1 : 0));
- State.Add((byte)(PPU_PendingVBlank ? 1 : 0));
-
- State.Add((byte)(PPU_VSET ? 1 : 0));
- State.Add((byte)(PPU_VSET_Latch1 ? 1 : 0));
- State.Add((byte)(PPU_VSET_Latch2 ? 1 : 0));
- State.Add((byte)(PPU_Read2002 ? 1 : 0));
-
- State.Add((byte)(OAMDMA_Aligned ? 1 : 0));
- State.Add((byte)(OAMDMA_Halt ? 1 : 0));
- State.Add((byte)(DMCDMA_Halt ? 1 : 0));
- State.Add(OAM_InternalBus);
-
- State.Add((byte)PPU_PatternAddressRegister_CHR);
- State.Add((byte)(PPU_PatternAddressRegister_CHR >> 8));
- State.Add((byte)(PPU_ALE ? 1 : 0));
- State.Add(PPU_OctalLatch);
-
- State.Add((byte)(PPU_2007_Read ? 1 : 0));
- State.Add((byte)(PPU_2007_Read_SR ? 1 : 0));
- for (int i = 0; i < PPU_2007_Read_Latches.Length; i++) { State.Add((byte)(PPU_2007_Read_Latches[i] ? 1 : 0)); }
- State.Add((byte)(PPU_2007_PD_RB ? 1 : 0));
- State.Add((byte)(PPU_2007_ReadALE ? 1 : 0));
- State.Add((byte)(PPU_2007_Read_H0_Latch ? 1 : 0));
- State.Add((byte)(PPU_2007_Read_XRB ? 1 : 0));
- State.Add((byte)(PPU_READ ? 1 : 0));
- State.Add((byte)(PPU_2007_Write ? 1 : 0));
- State.Add((byte)(PPU_2007_Write_SR ? 1 : 0));
- for (int i = 0; i < PPU_2007_Write_Latches.Length; i++) { State.Add((byte)(PPU_2007_Write_Latches[i] ? 1 : 0)); }
- State.Add((byte)(PPU_2007_DB_PAR ? 1 : 0));
- State.Add((byte)(PPU_2007_WriteALE ? 1 : 0));
- State.Add((byte)(PPU_2007_TStep_Latch ? 1 : 0));
- State.Add((byte)(PPU_2007_TStep ? 1 : 0));
- State.Add((byte)(PPU_2007_BLNK_Latch ? 1 : 0));
- State.Add((byte)(PPU_2007_PaletteRAMEnable ? 1 : 0));
- State.Add(PPU_2007_WriteData);
- State.Add((byte)(PPU_WRITE ? 1 : 0));
-
- State.Add(PPU_RenderingCounter);
-
- State.Add(PPU_Update2001Delay); // TEMPORARY
- State.Add(PPU_Update2001OAMCorruptionDelay); // TEMPORARY
- State.Add(PPU_Update2001EmphasisBitsDelay); // TEMPORARY
-
- foreach (Byte b in RAM) { State.Add(b); }
- foreach (Byte b in VRAM) { State.Add(b); }
- foreach (Byte b in OAM) { State.Add(b); }
- foreach (Byte b in OAM2) { State.Add(b); }
- foreach (Byte b in PaletteRAM) { State.Add(b); }
-
- List MapperBytes = Cart.MapperChip.SaveMapperRegisters();
- for (int i = 0; i < MapperBytes.Count; i++)
- {
- State.Add(MapperBytes[i]);
- }
-
- return State;
- }
-
- public void LoadState(List State)
- {
- int p = 0;
- programCounter = State[p++];
- programCounter |= (ushort)(State[p++] << 8);
- addressBus = State[p++];
- addressBus |= (ushort)(State[p++] << 8);
- temporaryAddress = State[p++];
- temporaryAddress |= (ushort)(State[p++] << 8);
- OAMAddressBus = State[p++];
- OAMAddressBus |= (ushort)(State[p++] << 8);
- PPU_v = State[p++];
- PPU_v |= (ushort)(State[p++] << 8);
- PPU_t = State[p++];
- PPU_t |= (ushort)(State[p++] << 8);
-
- totalCycles = State[p++];
- totalCycles |= (State[p++] << 8);
- totalCycles |= (State[p++] << 16);
- totalCycles |= (State[p++] << 24);
-
- PPUClock = State[p++];
- CPUClock = State[p++];
-
- operationCycle = State[p++];
- opCode = State[p++];
-
- dl = State[p++];
- dataBus = State[p++];
- A = State[p++];
- X = State[p++];
- Y = State[p++];
- stackPointer = State[p++];
-
- status = State[p++];
- flag_Carry = (status & 1) == 1;
- flag_Zero = ((status & 0x02) >> 1) == 1;
- flag_Interrupt = ((status & 0x04) >> 2) == 1;
- flag_Decimal = ((status & 0x08) >> 3) == 1;
- flag_Overflow = ((status & 0x40) >> 6) == 1;
- flag_Negative = ((status & 0x80) >> 7) == 1;
-
- specialBus = State[p++];
- H = State[p++];
- IgnoreH = (State[p++] & 1) == 1;
-
- CPU_SYNC = (State[p++] & 1) == 1;
- CPU_Read = (State[p++] & 1) == 1;
- DoBRK = (State[p++] & 1) == 1;
- DoNMI = (State[p++] & 1) == 1;
- DoIRQ = (State[p++] & 1) == 1;
- DoReset = (State[p++] & 1) == 1;
- DoOAMDMA = (State[p++] & 1) == 1;
- FirstCycleOfOAMDMA = (State[p++] & 1) == 1;
- DoDMCDMA = (State[p++] & 1) == 1;
- DMCDMADelay = State[p++];
- CannotRunDMCDMARightNow = State[p++];
- DMAPage = State[p++];
- DMAAddress = State[p++];
- APU_ControllerPortsStrobing = (State[p++] & 1) == 1;
- APU_ControllerPortsStrobed = (State[p++] & 1) == 1;
- ControllerPort1 = State[p++];
- ControllerPort2 = State[p++];
- ControllerShiftRegister1 = State[p++];
- ControllerShiftRegister2 = State[p++];
- Controller1ShiftCounter = State[p++];
- Controller2ShiftCounter = State[p++];
- dataPinsAreNotFloating = (State[p++] & 1) == 1;
-
- APU_PutCycle = (State[p++] & 1) == 1;
- APU_Status_DMCInterrupt = (State[p++] & 1) == 1;
- APU_Status_FrameInterrupt = (State[p++] & 1) == 1;
- APU_Status_DMC = (State[p++] & 1) == 1;
- APU_Status_DelayedDMC = (State[p++] & 1) == 1;
- APU_Status_Noise = (State[p++] & 1) == 1;
- APU_Status_Triangle = (State[p++] & 1) == 1;
- APU_Status_Pulse2 = (State[p++] & 1) == 1;
- APU_Status_Pulse1 = (State[p++] & 1) == 1;
- Clearing_APU_FrameInterrupt = (State[p++] & 1) == 1;
- APU_DelayedDMC4015 = State[p++];
- APU_ImplicitAbortDMC4015 = (State[p++] & 1) == 1;
- APU_SetImplicitAbortDMC4015 = (State[p++] & 1) == 1;
- for (int i = 0; i < APU_Register.Length; i++) { APU_Register[i] = State[p++]; }
- APU_FrameCounterMode = (State[p++] & 1) == 1;
- APU_FrameCounterInhibitIRQ = (State[p++] & 1) == 1;
- APU_FrameCounterReset = State[p++];
- APU_Framecounter = State[p++];
- APU_Framecounter |= (ushort)(State[p++] << 8);
- APU_QuarterFrameClock = (State[p++] & 1) == 1;
- APU_HalfFrameClock = (State[p++] & 1) == 1;
- APU_Envelope_StartFlag = (State[p++] & 1) == 1;
- APU_Envelope_DividerClock = (State[p++] & 1) == 1;
- APU_Envelope_DecayLevel = State[p++];
- APU_LengthCounter_Pulse1 = State[p++];
- APU_LengthCounter_Pulse2 = State[p++];
- APU_LengthCounter_Triangle = State[p++];
- APU_LengthCounter_Noise = State[p++];
- APU_LengthCounter_HaltPulse1 = (State[p++] & 1) == 1;
- APU_LengthCounter_HaltPulse2 = (State[p++] & 1) == 1;
- APU_LengthCounter_HaltTriangle = (State[p++] & 1) == 1;
- APU_LengthCounter_HaltNoise = (State[p++] & 1) == 1;
- APU_LengthCounter_ReloadPulse1 = (State[p++] & 1) == 1;
- APU_LengthCounter_ReloadPulse2 = (State[p++] & 1) == 1;
- APU_LengthCounter_ReloadTriangle = (State[p++] & 1) == 1;
- APU_LengthCounter_ReloadNoise = (State[p++] & 1) == 1;
- APU_LengthCounter_ReloadValuePulse1 = State[p++];
- APU_LengthCounter_ReloadValuePulse2 = State[p++];
- APU_LengthCounter_ReloadValueTriangle = State[p++];
- APU_LengthCounter_ReloadValueNoise = State[p++];
- APU_ChannelTimer_Pulse1 = State[p++];
- APU_ChannelTimer_Pulse1 |= (ushort)(State[p++] << 8);
- APU_ChannelTimer_Pulse2 = State[p++];
- APU_ChannelTimer_Pulse2 |= (ushort)(State[p++] << 8);
- APU_ChannelTimer_Triangle = State[p++];
- APU_ChannelTimer_Triangle |= (ushort)(State[p++] << 8);
- APU_ChannelTimer_Noise = State[p++];
- APU_ChannelTimer_Noise |= (ushort)(State[p++] << 8);
- APU_ChannelTimer_DMC = State[p++];
- APU_ChannelTimer_DMC |= (ushort)(State[p++] << 8);
- APU_DMC_EnableIRQ = (State[p++] & 1) == 1;
- APU_DMC_Loop = (State[p++] & 1) == 1;
- APU_DMC_Rate = State[p++];
- APU_DMC_Rate |= (ushort)(State[p++] << 8);
- APU_DMC_Output = State[p++];
- APU_DMC_SampleAddress = State[p++];
- APU_DMC_SampleAddress |= (ushort)(State[p++] << 8);
- APU_DMC_SampleLength = State[p++];
- APU_DMC_SampleLength |= (ushort)(State[p++] << 8);
- APU_DMC_BytesRemaining = State[p++];
- APU_DMC_BytesRemaining |= (ushort)(State[p++] << 8);
- APU_DMC_Buffer = State[p++];
- APU_DMC_AddressCounter = State[p++];
- APU_DMC_AddressCounter |= (ushort)(State[p++] << 8);
- APU_DMC_Shifter = State[p++];
- APU_DMC_ShifterBitsRemaining = State[p++];
- APU_Silent = (State[p++] & 1) == 1;
-
- SecondaryOAMSize = State[p++];
- OAM2Address = State[p++];
- OAM2Overflowed = (State[p++] & 1) == 1;
- SpriteEvaluationTick = State[p++];
- OAMAddressOverflowedDuringSpriteEvaluation = (State[p++] & 1) == 1;
- OAM2Address = State[p++];
- PPUBus = State[p++];
- for (int i = 0; i < 8; i++)
- {
- PPUBusDecay[i] = State[p++];
- PPUBusDecay[i] |= (State[p++] << 8);
- PPUBusDecay[i] |= (State[p++] << 16);
- PPUBusDecay[i] |= (State[p++] << 24);
- }
- PPUOAMAddress = State[p++];
- PPUStatus_VBlank = (State[p++] & 1) == 1;
- PPUStatus_SpriteZeroHit = (State[p++] & 1) == 1;
- PPUStatus_SpriteOverflow = (State[p++] & 1) == 1;
- PPUStatus_PendingSpriteZeroHit = (State[p++] & 1) == 1;
- PPUStatus_PendingSpriteZeroHit2 = (State[p++] & 1) == 1;
- PPUStatus_SpriteZeroHit_Delayed = (State[p++] & 1) == 1;
- PPUStatus_SpriteOverflow_Delayed = (State[p++] & 1) == 1;
-
- PPU_Spritex16 = (State[p++] & 1) == 1;
- PPU_Scanline = State[p++];
- PPU_Scanline |= (ushort)(State[p++] << 8);
- PPU_Dot = State[p++];
- PPU_Dot |= (ushort)(State[p++] << 8);
- PPU_VRegisterChangedOutOfVBlank = (State[p++] & 1) == 1;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank = (State[p++] & 1) == 1;
- PPU_OAMCorruptionRenderingDisabledOutOfVBlank_Instant = (State[p++] & 1) == 1;
- PPU_PendingOAMCorruption = (State[p++] & 1) == 1;
- PPU_OAMCorruptionIndex = State[p++];
- PPU_OAMCorruptionRenderingEnabledOutOfVBlank = (State[p++] & 1) == 1;
- PPU_OAMEvaluationCorruptionOddCycle = (State[p++] & 1) == 1;
- PPU_OAMEvaluationObjectInRange = (State[p++] & 1) == 1;
- PPU_OAMEvaluationObjectInXRange = (State[p++] & 1) == 1;
- PPU_PaletteCorruptionRenderingDisabledOutOfVBlank = (State[p++] & 1) == 1;
- PPU_AttributeLatchRegister = State[p++];
- PPU_BackgroundAttributeShiftRegisterL = State[p++];
- PPU_BackgroundAttributeShiftRegisterL |= (ushort)(State[p++] << 8);
- PPU_BackgroundAttributeShiftRegisterH = State[p++];
- PPU_BackgroundAttributeShiftRegisterH |= (ushort)(State[p++] << 8);
- PPU_BackgroundPatternShiftRegisterL = State[p++];
- PPU_BackgroundPatternShiftRegisterL |= (ushort)(State[p++] << 8);
- PPU_BackgroundPatternShiftRegisterH = State[p++];
- PPU_BackgroundPatternShiftRegisterH |= (ushort)(State[p++] << 8);
- PPU_FineXScroll = State[p++];
- for (int i = 0; i < 8; i++) { PPU_SpriteShiftRegisterL[i] = State[p++]; }
- for (int i = 0; i < 8; i++) { PPU_SpriteShiftRegisterH[i] = State[p++]; }
- for (int i = 0; i < 8; i++) { PPU_SpriteAttribute[i] = State[p++]; }
- for (int i = 0; i < 8; i++) { PPU_SpriteXposition[i] = State[p++]; }
- for (int i = 0; i < 8; i++) { PPU_SpriteShifterCounter[i] = State[p++]; }
- PPU_SpritePattern = State[p++];
- PPU_OAM_VerticalOffset = State[p++];
- PPU_OAM_VerticalOffset |= (ushort)(State[p++] << 8);
- PPU_NextScanlineContainsSpriteZero = (State[p++] & 1) == 1;
- PPU_CurrentScanlineContainsSpriteZero = (State[p++] & 1) == 1;
- PPU_SpritePatternL = State[p++];
- PPU_SpritePatternH = State[p++];
- PPU_Mask_Greyscale = (State[p++] & 1) == 1;
- PPU_Mask_8PxShowBackground = (State[p++] & 1) == 1;
- PPU_Mask_8PxShowSprites = (State[p++] & 1) == 1;
- PPU_Mask_ShowBackground = (State[p++] & 1) == 1;
- PPU_Mask_ShowSprites = (State[p++] & 1) == 1;
- PPU_Mask_EmphasizeRed = (State[p++] & 1) == 1;
- PPU_Mask_EmphasizeGreen = (State[p++] & 1) == 1;
- PPU_Mask_EmphasizeBlue = (State[p++] & 1) == 1;
- PPU_Mask_ShowBackground_Delayed = (State[p++] & 1) == 1;
- PPU_Mask_ShowSprites_Delayed = (State[p++] & 1) == 1;
- PPU_Mask_ShowBackground_Instant = (State[p++] & 1) == 1;
- PPU_Mask_ShowSprites_Instant = (State[p++] & 1) == 1;
- PPU_LowBitPlane = State[p++];
- PPU_HighBitPlane = State[p++];
- PPU_Attribute = State[p++];
- PPU_CanDetectSpriteZeroHit = (State[p++] & 1) == 1;
- PPU_OddFrame = (State[p++] & 1) == 1;
- PaletteRAMAddress = State[p++];
- ThisDotReadFromPaletteRAM = (State[p++] & 1) == 1;
- NMI_PinsSignal = (State[p++] & 1) == 1;
- NMI_PreviousPinsSignal = (State[p++] & 1) == 1;
- IRQ_LevelDetector = (State[p++] & 1) == 1;
- NMILine = (State[p++] & 1) == 1;
- IRQLine = (State[p++] & 1) == 1;
- CopyV = (State[p++] & 1) == 1;
- SkippedPreRenderDot341 = (State[p++] & 1) == 1;
- OamCorruptedOnOddCycle = (State[p++] & 1) == 1;
- PPU_OAMBuffer = State[p++];
- PPU_RenderTemp = State[p++];
- PPU_Commit_NametableFetch = (State[p++] & 1) == 1;
- PPU_Commit_AttributeFetch = (State[p++] & 1) == 1;
- PPU_Commit_PatternLowFetch = (State[p++] & 1) == 1;
- PPU_Commit_PatternHighFetch = (State[p++] & 1) == 1;
-
- PPU_VRAM_MysteryAddress = State[p++];
- PPU_VRAM_MysteryAddress |= (ushort)(State[p++] << 8);
- PPU_AddressBus = State[p++];
- PPU_AddressBus |= (ushort)(State[p++] << 8);
- PPU_Update2006Delay = State[p++];
- PPU_Update2005Delay = State[p++];
- PPU_Update2005Value = State[p++];
- PPU_Update2001Value = State[p++];
- PPU_Update2006Value = State[p++];
- PPU_Update2006Value |= (ushort)(State[p++] << 8);
- PPU_Update2006Value_Temp = State[p++];
- PPU_Update2006Value_Temp |= (ushort)(State[p++] << 8);
- PPU_WasRenderingBefore2001Write = (State[p++] & 1) == 1;
- PPU_ReadBuffer = State[p++];
- PPUAddrLatch = (State[p++] & 1) == 1;
- PPUControlIncrementMode32 = (State[p++] & 1) == 1;
- PPUControl_NMIEnabled = (State[p++] & 1) == 1;
- PPU_PatternSelect_Sprites = (State[p++] & 1) == 1;
- PPU_PatternSelect_Background = (State[p++] & 1) == 1;
- PPU_PendingVBlank = (State[p++] & 1) == 1;
-
- PPU_VSET = (State[p++] & 1) == 1;
- PPU_VSET_Latch1 = (State[p++] & 1) == 1;
- PPU_VSET_Latch2 = (State[p++] & 1) == 1;
- PPU_Read2002 = (State[p++] & 1) == 1;
-
- OAMDMA_Aligned = (State[p++] & 1) == 1;
- OAMDMA_Halt = (State[p++] & 1) == 1;
- DMCDMA_Halt = (State[p++] & 1) == 1;
- OAM_InternalBus = State[p++];
-
- PPU_PatternAddressRegister_CHR = State[p++];
- PPU_PatternAddressRegister_CHR |= (ushort)(State[p++] << 8);
- PPU_ALE = (State[p++] & 1) == 1;
- PPU_OctalLatch = State[p++];
-
- PPU_2007_Read = (State[p++] & 1) == 1;
- PPU_2007_Read_SR = (State[p++] & 1) == 1;
- for (int i = 0; i < PPU_2007_Read_Latches.Length; i++) { PPU_2007_Read_Latches[i] = (State[p++] & 1) == 1; }
- PPU_2007_PD_RB = (State[p++] & 1) == 1;
- PPU_2007_ReadALE = (State[p++] & 1) == 1;
- PPU_2007_Read_H0_Latch = (State[p++] & 1) == 1;
- PPU_2007_Read_XRB = (State[p++] & 1) == 1;
- PPU_READ = (State[p++] & 1) == 1;
- PPU_2007_Write = (State[p++] & 1) == 1;
- PPU_2007_Write_SR = (State[p++] & 1) == 1;
- for (int i = 0; i < PPU_2007_Write_Latches.Length; i++) { PPU_2007_Write_Latches[i] = (State[p++] & 1) == 1; }
- PPU_2007_DB_PAR = (State[p++] & 1) == 1;
- PPU_2007_WriteALE = (State[p++] & 1) == 1;
- PPU_2007_TStep_Latch = (State[p++] & 1) == 1;
- PPU_2007_TStep = (State[p++] & 1) == 1;
- PPU_2007_BLNK_Latch = (State[p++] & 1) == 1;
- PPU_2007_PaletteRAMEnable = (State[p++] & 1) == 1;
- PPU_2007_WriteData = State[p++];
- PPU_WRITE = (State[p++] & 1) == 1;
-
- PPU_RenderingCounter = State[p++];
-
- PPU_Update2001Delay = State[p++]; //TEMPORARY
- PPU_Update2001OAMCorruptionDelay = State[p++]; //TEMPORARY
- PPU_Update2001EmphasisBitsDelay = State[p++]; //TEMPORARY
-
- for (int i = 0; i < RAM.Length; i++) { RAM[i] = State[p++]; }
- for (int i = 0; i < VRAM.Length; i++) { VRAM[i] = State[p++]; }
- for (int i = 0; i < OAM.Length; i++) { OAM[i] = State[p++]; }
- for (int i = 0; i < OAM2.Length; i++) { OAM2[i] = State[p++]; }
- for (int i = 0; i < PaletteRAM.Length; i++) { PaletteRAM[i] = State[p++]; }
-
- Cart.MapperChip.LoadMapperRegisters(State, p, out p);
- }
-
- public void Dispose()
- {
- Cart = null;
- Screen.Dispose();
- BorderedScreen.Dispose();
- NTSCScreen.Dispose();
- BorderedNTSCScreen.Dispose();
- }
-
- }
-
- public class DirectBitmap : IDisposable
- {
- // This class was copied from Stack Overflow
- // Writing to the standard Bitmap class is slow, so this class exists as a faster alternative.
- public Bitmap Bitmap { get; private set; }
- public Int32[] Bits { get; private set; }
- public bool Disposed { get; private set; }
- public int Height { get; private set; }
- public int Width { get; private set; }
-
- protected GCHandle BitsHandle { get; private set; }
-
- public DirectBitmap(int width, int height)
- {
- Width = width;
- Height = height;
- Bits = new Int32[width * height];
- BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
- Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
- }
-
- public void SetPixel(int x, int y, Color color)
- {
- int index = x + (y * Width);
- int col = color.ToArgb();
-
- Bits[index] = col;
- }
-
- public void SetPixel(int x, int y, int colorRGBA)
- {
- int index = x + (y * Width);
- Bits[index] = colorRGBA;
- }
-
- public Color GetPixel(int x, int y)
- {
- int index = x + (y * Width);
- int col = Bits[index];
- Color result = Color.FromArgb(col);
-
- return result;
- }
-
- public void Dispose()
- {
- if (Disposed) return;
- Disposed = true;
- Bitmap.Dispose();
- BitsHandle.Free();
- }
- }
-
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.Designer.cs
deleted file mode 100644
index 46f61fcb..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.Designer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace TriCNES
-{
- partial class TriC72PinConnector
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.SuspendLayout();
- //
- // TriC72PinConnector
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(292, 687);
- this.Name = "TriC72PinConnector";
- this.Text = "TriC72PinConnector";
- this.ResumeLayout(false);
-
- }
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.cs
deleted file mode 100644
index 76e669f7..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- public partial class TriC72PinConnector : Form
- {
- public TriCNESGUI MainGUI;
- CheckBox[] cb_PinsFloating;
- public void TriC72PinConnector_Closing(object sender, FormClosingEventArgs e)
- {
- if (MainGUI != null)
- {
- MainGUI.CartConnector = null;
- }
- Dispose();
- }
- public TriC72PinConnector()
- {
- InitializeComponent();
- cb_PinsFloating = new CheckBox[72];
-
- for(int i = 0; i < 72; i++)
- {
- cb_PinsFloating[i] = new CheckBox();
- cb_PinsFloating[i].Location = new Point(32 + ((i >= 36) ? 128 : 0), (36 - (i % 36)) * 18);
- cb_PinsFloating[i].Text = PinNames[i];
- cb_PinsFloating[i].RightToLeft = (i >= 36) ? RightToLeft.Yes : RightToLeft.No;
- Controls.Add(cb_PinsFloating[i]);
- }
-
- FormClosing += TriC72PinConnector_Closing;
- }
-
- String[] PinNames = new String[] {
- "GND",
- "CPU A11",
- "CPU A10",
- "CPU A9",
- "CPU A8",
- "CPU A7",
- "CPU A6",
- "CPU A5",
- "CPU A4",
- "CPU A3",
- "CPU A2",
- "CPU A1",
- "CPU A0",
- "CPU R/W",
- "/IRQ",
- "EXP 0",
- "EXP 1",
- "EXP 2",
- "EXP 3",
- "EXP 4",
- "PPU /RD",
- "CIRAM A10",
- "PPU A6",
- "PPU A5",
- "PPU A4",
- "PPU A3",
- "PPU A2",
- "PPU A1",
- "PPU A0",
- "PPU D0",
- "PPU D1",
- "PPU D2",
- "PPU D3",
- "CIC toPak",
- "CIC toMB",
- "+5V",
- "SYSTEM CLK",
- "M2",
- "CPU A12",
- "CPU A13",
- "CPU A14",
- "CPU D7",
- "CPU D6",
- "CPU D5",
- "CPU D4",
- "CPU D3",
- "CPU D2",
- "CPU D1",
- "CPU D0",
- "CPU /A15",
- "EXP 9",
- "EXP 8",
- "EXP 7",
- "EXP 6",
- "EXP 5",
- "PPU /WR",
- "CIRAM /CE",
- "PPU /A13",
- "PPU A7",
- "PPU A8",
- "PPU A9",
- "PPU A11",
- "PPU A10",
- "PPU A12",
- "PPU A13",
- "PPU D7",
- "PPU D6",
- "PPU D5",
- "PPU D4",
- "CIC +RST",
- "CIC CLK",
- "GND",
- };
-
- public void Update72PinConnector()
- {
- if(MainGUI.EMU != null)
- {
- if (MainGUI.EMU.ConnectorPinFloating != null)
- {
- for(int i = 0; i < 72; i++)
- {
- MainGUI.EMU.ConnectorPinFloating[i] = cb_PinsFloating[i].Checked;
- }
- }
- }
- }
-
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.resx
deleted file mode 100644
index 1af7de15..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Forms/TriC72PinConnector.resx
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/LICENSE b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/LICENSE
deleted file mode 100644
index 0fae2cdf..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 Chris Siebert
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Program.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Program.cs
deleted file mode 100644
index 9fe72b82..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Program.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- internal static class Program
- {
- ///
- /// The main entry point for the application.
- ///
- [STAThread]
- static void Main()
- {
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.Run(new TriCNESGUI());
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/AssemblyInfo.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/AssemblyInfo.cs
deleted file mode 100644
index 0d5e9ce4..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("TriCNES")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("TriCNES")]
-[assembly: AssemblyCopyright("Copyright © 2024")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("b53c8a3b-8f4f-4837-99a0-47d7d9fe757f")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.Designer.cs
deleted file mode 100644
index fe7bd6be..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace TriCNES.Properties
-{
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources
- {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources()
- {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager
- {
- get
- {
- if ((resourceMan == null))
- {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TriCNES.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture
- {
- get
- {
- return resourceCulture;
- }
- set
- {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.resx
deleted file mode 100644
index af7dbebb..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Settings.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Settings.Designer.cs
deleted file mode 100644
index da10a54a..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace TriCNES.Properties
-{
-
-
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
- {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default
- {
- get
- {
- return defaultInstance;
- }
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/README.md b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/README.md
deleted file mode 100644
index 1b22fa51..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/README.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# TriCNES
-
-TriCNES, or "Coin's Contrabulous Cartswapulator", is a Nintendo Entertainment System emulator written by Chris "100th_Coin" Siebert with a focus on test-driven accuracy. This emulator was originally made in order to experiment with a theorhetical arbitary code exploit I created titled "Intercycle Cartridge Swapping" where the NES cartridge is replaced every CPU cycle in order to run custom code. Intercycle Cartridge Swapping was later verified to work on real hardware by Youtube user Decrazyo.
-
-This NES emulator was built from the ground up starting from a blank .net winforms project
-
-# Limitations
-
-This emulator does not produce audio.
-This emulator only accepts inputs in the form of a TAS file.
-This emulator can only run NTSC cartridges properly.
-This emulator only supports the following mapper chips:
-* 0: NROM
-* 1: MMC1
-* 2: UxROM
-* 3: CNROM
-* 4: MMC3 (MMC6 support in the dev build)
-* 7: AOROM
-* 9: MMC2 (dev build)
-* 69: Sunsoft FME-7
-
-# Supported TAS file types
-
-Due to varying emulator accuracy, this emulator is not guaranteed to sync all TAS files. Despite this, it supports loading inputs from the following formats:
-* .3c2 (TriCNES) (dev build)
-* .3c3 (TriCNES TAS Timeline) (dev build)
-* .bk2 (Bizhawk)
-* .tasproj (Bizhawk's TAStudio)
-* .fm2 (FCEUX)
-* .fm3 (FCEUX's TAS Editor)
-* .fmv (Famtasia)
-* .r08 (Replay Device)
-
-In addition to those TAS file types, my emulator can also load my very own intercycle-cart-swapping TAS format, .3ct.
-
-# The .3ct TAS file format
-
-TAS files that were made with the intention of swapping cartridges between cycles are stored in the following format.
-
-The first line of the file is an integer, indicating how many cartridges are being used for this TAS. This integer will be called 'n'.
-
-The following 'n' lines are local file paths to the ROMs you wish to use. This will set up an array of cartridges.
-
-The remaining lines until the end of the file are in the following format: "x y" where an integer 'x' is seperated from an integer 'y' with a space.
-
-When the TAS is being played back, before CPU cycle 'x', swap to the cartridge at index 'y' into the cartridge array.
-
-Here's an example:
-
-5
-Super Mario Bros. [!].nes
-Dash Galaxy in the Alien Asylum (U) [!].nes
-Kung Fu (JU) [!].nes
-Pipe Dream (U) [!].nes
-Super Mario Bros. 3 (U) (V1.1) [!].nes
-6 0
-7 1
-8 2
-9 3
-10 4
-
-
-In this example, there are 5 cartridges. Before cycle 6, the emulator will swap to `Super Mario Bros. [!].nes`, as that is index 0 into the cartridge array. Before cycle 7, the emulator will swap to index 1, `Dash Galaxy in the Alien Asylum (U) [!].nes`, and so on.
-
-# Screenshots
-
-
-
-
-
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/TriCNES.csproj b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/TriCNES.csproj
deleted file mode 100644
index 1c2dc97b..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/TriCNES.csproj
+++ /dev/null
@@ -1,182 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {B53C8A3B-8F4F-4837-99A0-47D7D9FE757F}
- WinExe
- TriCNES
- TriCNES
- v4.8
- 512
- true
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- icon.ico
-
-
- 8
-
-
-
-
- packages\ppy.SDL2-CS.1.0.82\lib\netstandard2.0\SDL2-CS.dll
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Form
-
-
- TASProperties.cs
-
-
- Form
-
-
- TASProperties3ct.cs
-
-
- Form
-
-
- TriC72PinConnector.cs
-
-
- Form
-
-
- TriCHexEditor.cs
-
-
- Form
-
-
- TriCNESGUI.cs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Form
-
-
- TriCNTViewer.cs
-
-
- Form
-
-
- TriCTASTimeline.cs
-
-
- Form
-
-
- TriCTraceLogger.cs
-
-
- TASProperties.cs
-
-
- TASProperties3ct.cs
-
-
- TriC72PinConnector.cs
-
-
- TriCHexEditor.cs
-
-
- TriCNESGUI.cs
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
- Designer
-
-
- True
- Resources.resx
-
-
- TriCNTViewer.cs
-
-
- TriCTASTimeline.cs
-
-
- TriCTraceLogger.cs
-
-
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
- True
- Settings.settings
- True
-
-
-
-
-
-
-
-
- Always
-
-
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.Designer.cs
deleted file mode 100644
index 0376fd10..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.Designer.cs
+++ /dev/null
@@ -1,263 +0,0 @@
-namespace TriCNES
-{
- partial class TASProperties
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.components = new System.ComponentModel.Container();
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TASProperties));
- this.b_RunTAS = new System.Windows.Forms.Button();
- this.tb_FilePath = new System.Windows.Forms.TextBox();
- this.l_FilePath = new System.Windows.Forms.Label();
- this.rb_LatchFiltering = new System.Windows.Forms.RadioButton();
- this.rb_ClockFiltering = new System.Windows.Forms.RadioButton();
- this.panel1 = new System.Windows.Forms.Panel();
- this.TASPropTooltips = new System.Windows.Forms.ToolTip(this.components);
- this.cb_ClockAlignment = new System.Windows.Forms.ComboBox();
- this.cb_CpuClock = new System.Windows.Forms.ComboBox();
- this.b_BrowseFile = new System.Windows.Forms.Button();
- this.l_InputCount = new System.Windows.Forms.Label();
- this.l_FamtasiaWarning = new System.Windows.Forms.Label();
- this.label3 = new System.Windows.Forms.Label();
- this.label1 = new System.Windows.Forms.Label();
- this.cb_fceuxFrame0 = new System.Windows.Forms.CheckBox();
- this.panel1.SuspendLayout();
- this.SuspendLayout();
- //
- // b_RunTAS
- //
- this.b_RunTAS.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
- this.b_RunTAS.Location = new System.Drawing.Point(24, 255);
- this.b_RunTAS.Name = "b_RunTAS";
- this.b_RunTAS.Size = new System.Drawing.Size(300, 40);
- this.b_RunTAS.TabIndex = 0;
- this.b_RunTAS.Text = "Run TAS";
- this.b_RunTAS.UseVisualStyleBackColor = true;
- this.b_RunTAS.Click += new System.EventHandler(this.b_RunTAS_Click);
- //
- // tb_FilePath
- //
- this.tb_FilePath.Anchor = System.Windows.Forms.AnchorStyles.Top;
- this.tb_FilePath.Location = new System.Drawing.Point(53, 6);
- this.tb_FilePath.Name = "tb_FilePath";
- this.tb_FilePath.ReadOnly = true;
- this.tb_FilePath.Size = new System.Drawing.Size(206, 20);
- this.tb_FilePath.TabIndex = 1;
- //
- // l_FilePath
- //
- this.l_FilePath.AutoSize = true;
- this.l_FilePath.Location = new System.Drawing.Point(26, 9);
- this.l_FilePath.Name = "l_FilePath";
- this.l_FilePath.Size = new System.Drawing.Size(23, 13);
- this.l_FilePath.TabIndex = 2;
- this.l_FilePath.Text = "File";
- //
- // rb_LatchFiltering
- //
- this.rb_LatchFiltering.AutoSize = true;
- this.rb_LatchFiltering.Location = new System.Drawing.Point(5, 3);
- this.rb_LatchFiltering.Name = "rb_LatchFiltering";
- this.rb_LatchFiltering.Size = new System.Drawing.Size(91, 17);
- this.rb_LatchFiltering.TabIndex = 3;
- this.rb_LatchFiltering.TabStop = true;
- this.rb_LatchFiltering.Text = "Latch Filtering";
- this.TASPropTooltips.SetToolTip(this.rb_LatchFiltering, "Latch filtering will provide a single input per frame.");
- this.rb_LatchFiltering.UseVisualStyleBackColor = true;
- //
- // rb_ClockFiltering
- //
- this.rb_ClockFiltering.AutoSize = true;
- this.rb_ClockFiltering.Location = new System.Drawing.Point(5, 26);
- this.rb_ClockFiltering.Name = "rb_ClockFiltering";
- this.rb_ClockFiltering.Size = new System.Drawing.Size(91, 17);
- this.rb_ClockFiltering.TabIndex = 4;
- this.rb_ClockFiltering.TabStop = true;
- this.rb_ClockFiltering.Text = "Clock Filtering";
- this.TASPropTooltips.SetToolTip(this.rb_ClockFiltering, "Clock filtering will provide multiple inputs per frame. This is used in \"subframe" +
- "\" TASes.");
- this.rb_ClockFiltering.UseVisualStyleBackColor = true;
- //
- // panel1
- //
- this.panel1.Controls.Add(this.rb_LatchFiltering);
- this.panel1.Controls.Add(this.rb_ClockFiltering);
- this.panel1.Location = new System.Drawing.Point(24, 200);
- this.panel1.Name = "panel1";
- this.panel1.Size = new System.Drawing.Size(100, 49);
- this.panel1.TabIndex = 6;
- //
- // cb_ClockAlignment
- //
- this.cb_ClockAlignment.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- this.cb_ClockAlignment.FormattingEnabled = true;
- this.cb_ClockAlignment.Items.AddRange(new object[] {
- "Phase 0",
- "Phase 1",
- "Phase 2",
- "Phase 3"});
- this.cb_ClockAlignment.Location = new System.Drawing.Point(176, 174);
- this.cb_ClockAlignment.Name = "cb_ClockAlignment";
- this.cb_ClockAlignment.Size = new System.Drawing.Size(65, 21);
- this.cb_ClockAlignment.TabIndex = 10;
- this.TASPropTooltips.SetToolTip(this.cb_ClockAlignment, "Some runs may desync depending on alignment. Different emulators use different al" +
- "ignments. Only change this if you know what you are doing.");
- //
- // cb_CpuClock
- //
- this.cb_CpuClock.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- this.cb_CpuClock.FormattingEnabled = true;
- this.cb_CpuClock.Items.AddRange(new object[] {
- "Phase 0",
- "Phase 1",
- "Phase 2",
- "Phase 3",
- "Phase 4",
- "Phase 5",
- "Phase 6",
- "Phase 7",
- "Phase 8",
- "Phase 9",
- "Phase 10",
- "Phase 11"});
- this.cb_CpuClock.Location = new System.Drawing.Point(176, 151);
- this.cb_CpuClock.Name = "cb_CpuClock";
- this.cb_CpuClock.Size = new System.Drawing.Size(65, 21);
- this.cb_CpuClock.TabIndex = 13;
- this.TASPropTooltips.SetToolTip(this.cb_CpuClock, "Some runs may desync depending on alignment. Different emulators use different al" +
- "ignments. Only change this if you know what you are doing.");
- //
- // b_BrowseFile
- //
- this.b_BrowseFile.Location = new System.Drawing.Point(265, 6);
- this.b_BrowseFile.Name = "b_BrowseFile";
- this.b_BrowseFile.Size = new System.Drawing.Size(59, 21);
- this.b_BrowseFile.TabIndex = 7;
- this.b_BrowseFile.Text = "Browse...";
- this.b_BrowseFile.UseVisualStyleBackColor = true;
- //
- // l_InputCount
- //
- this.l_InputCount.AutoSize = true;
- this.l_InputCount.Location = new System.Drawing.Point(26, 29);
- this.l_InputCount.Name = "l_InputCount";
- this.l_InputCount.Size = new System.Drawing.Size(44, 13);
- this.l_InputCount.TabIndex = 8;
- this.l_InputCount.Text = "0 inputs";
- //
- // l_FamtasiaWarning
- //
- this.l_FamtasiaWarning.AutoSize = true;
- this.l_FamtasiaWarning.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.l_FamtasiaWarning.Location = new System.Drawing.Point(12, 73);
- this.l_FamtasiaWarning.Name = "l_FamtasiaWarning";
- this.l_FamtasiaWarning.Size = new System.Drawing.Size(325, 26);
- this.l_FamtasiaWarning.TabIndex = 9;
- this.l_FamtasiaWarning.Text = "Warning!\r\nFamtasia is an old emulator, and the TAS is not guaranteed to sync!";
- this.l_FamtasiaWarning.TextAlign = System.Drawing.ContentAlignment.TopCenter;
- //
- // label3
- //
- this.label3.AutoSize = true;
- this.label3.Location = new System.Drawing.Point(21, 177);
- this.label3.Name = "label3";
- this.label3.Size = new System.Drawing.Size(149, 13);
- this.label3.TabIndex = 11;
- this.label3.Text = "PPU / Master clock alignment";
- //
- // label1
- //
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(21, 154);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(149, 13);
- this.label1.TabIndex = 12;
- this.label1.Text = "CPU / Master clock alignment";
- //
- // cb_fceuxFrame0
- //
- this.cb_fceuxFrame0.AutoSize = true;
- this.cb_fceuxFrame0.Checked = true;
- this.cb_fceuxFrame0.CheckState = System.Windows.Forms.CheckState.Checked;
- this.cb_fceuxFrame0.Location = new System.Drawing.Point(24, 124);
- this.cb_fceuxFrame0.Name = "cb_fceuxFrame0";
- this.cb_fceuxFrame0.Size = new System.Drawing.Size(175, 17);
- this.cb_fceuxFrame0.TabIndex = 14;
- this.cb_fceuxFrame0.Text = "Use FCEUX\'s Frame 0 behavior";
- this.TASPropTooltips.SetToolTip(this.cb_fceuxFrame0, "FCEUX inaccurately emulates the first frame from the beginning of VBlank rather t" +
- "han the end.\r\nUnchecking this box can potentially lead to a desync, though it wi" +
- "ll be more accurate.");
- this.cb_fceuxFrame0.UseVisualStyleBackColor = true;
- //
- // TASProperties
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(350, 307);
- this.Controls.Add(this.cb_fceuxFrame0);
- this.Controls.Add(this.cb_CpuClock);
- this.Controls.Add(this.label1);
- this.Controls.Add(this.label3);
- this.Controls.Add(this.cb_ClockAlignment);
- this.Controls.Add(this.l_FamtasiaWarning);
- this.Controls.Add(this.l_InputCount);
- this.Controls.Add(this.b_BrowseFile);
- this.Controls.Add(this.panel1);
- this.Controls.Add(this.l_FilePath);
- this.Controls.Add(this.tb_FilePath);
- this.Controls.Add(this.b_RunTAS);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.Name = "TASProperties";
- this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
- this.Text = "TAS Properties";
- this.panel1.ResumeLayout(false);
- this.panel1.PerformLayout();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.Button b_RunTAS;
- private System.Windows.Forms.TextBox tb_FilePath;
- private System.Windows.Forms.Label l_FilePath;
- private System.Windows.Forms.RadioButton rb_LatchFiltering;
- private System.Windows.Forms.RadioButton rb_ClockFiltering;
- private System.Windows.Forms.ToolTip TASPropTooltips;
- private System.Windows.Forms.Panel panel1;
- private System.Windows.Forms.Button b_BrowseFile;
- private System.Windows.Forms.Label l_InputCount;
- private System.Windows.Forms.Label l_FamtasiaWarning;
- private System.Windows.Forms.ComboBox cb_ClockAlignment;
- private System.Windows.Forms.Label label3;
- private System.Windows.Forms.Label label1;
- private System.Windows.Forms.ComboBox cb_CpuClock;
- private System.Windows.Forms.CheckBox cb_fceuxFrame0;
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.cs
deleted file mode 100644
index a6e5ad5b..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- public partial class TASProperties : Form
- {
- public TASProperties()
- {
- InitializeComponent();
- FormClosing += TASProperties_Closing;
- }
-
- private void TASProperties_Closing(Object sender, FormClosingEventArgs e)
- {
- if (MainGUI != null)
- {
- MainGUI.TASPropertiesForm = null;
- }
- Dispose();
- }
-
- public string TasFilePath;
- public ushort[] TasInputLog;
- public bool[] TasResetLog;
- public TriCNESGUI MainGUI;
-
- public bool SubframeInputs()
- {
- return rb_ClockFiltering.Checked;
- }
-
- public bool UseFCEUXFrame0Timing() // this only applies to TASes using the .fm2 or .fm3 file format.
- {
- return cb_fceuxFrame0.Checked;
- }
-
- public byte GetPPUClockPhase()
- {
- return (byte)cb_ClockAlignment.SelectedIndex;
- }
-
- public byte GetCPUClockPhase()
- {
- return (byte)cb_CpuClock.SelectedIndex;
- }
-
- public string extension;
-
- public void Init()
- {
- tb_FilePath.Text = TasFilePath;
- // determine file type
- extension = Path.GetExtension(TasFilePath);
- // create list of inputs from the tas file, and make any settings changes if needed.
- byte[] ByteArray = File.ReadAllBytes(TasFilePath);
- List TASInputs = new List(); // Low byte is player 1, High byte is player 2.
-
- rb_ClockFiltering.Checked = false;
- rb_LatchFiltering.Checked = true;
- l_FamtasiaWarning.Visible = false;
- cb_ClockAlignment.SelectedIndex = 0;
- cb_ClockAlignment.Update();
- cb_CpuClock.SelectedIndex = 0;
- cb_CpuClock.Update();
- cb_fceuxFrame0.Enabled = false;
- switch (extension)
- {
- case ".bk2":
- case ".tasproj":
- {
- cb_CpuClock.SelectedIndex = 8;
- cb_CpuClock.Update();
- }
- break;
- case ".fm2":
- {
- cb_fceuxFrame0.Enabled = true;
- // change the alignment to use FCEUX's
- cb_CpuClock.SelectedIndex = 0;
- cb_CpuClock.Update();
- }
- break;
- case ".fm3":
- {
-
- }
- break;
- case ".fmv":
- {
- l_FamtasiaWarning.Visible = true;
- }
- break;
- case ".r08":
- {
-
- }
- break;
- case ".3c2":
- {
-
- }
- break;
- case ".3c3":
- {
-
- }
- break;
-
- // TODO: ask if the .tasd file format is a thing yet
- }
-
- List Resets = new List();
- TASInputs = MainGUI.ParseTasFile(TasFilePath, out Resets);
- // okay cool, now we have the entire input log.
- TasInputLog = TASInputs.ToArray();
- TasResetLog = Resets.ToArray();
- l_InputCount.Text = TasInputLog.Length + " Inputs";
- }
-
- private void b_RunTAS_Click(object sender, EventArgs e)
- {
- MainGUI.StartTAS();
-
-
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.resx
deleted file mode 100644
index 60bd14d9..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties.resx
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 17, 17
-
-
-
-
- AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA
- AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD/////////////
- //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA
- AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//
- /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA
- AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//
- ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA
- AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA
- AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/////////
- //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA
- AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA
- AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD/////////////
- ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/////////
- //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////
- //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////
- /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA
- zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP////////
- //////////////////////////////////8=
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.Designer.cs
deleted file mode 100644
index 37330488..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.Designer.cs
+++ /dev/null
@@ -1,235 +0,0 @@
-namespace TriCNES
-{
- partial class TASProperties3ct
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TASProperties3ct));
- this.cb_CpuClock = new System.Windows.Forms.ComboBox();
- this.label1 = new System.Windows.Forms.Label();
- this.label3 = new System.Windows.Forms.Label();
- this.cb_ClockAlignment = new System.Windows.Forms.ComboBox();
- this.l_InputCount = new System.Windows.Forms.Label();
- this.b_BrowseFile = new System.Windows.Forms.Button();
- this.l_FilePath = new System.Windows.Forms.Label();
- this.tb_FilePath = new System.Windows.Forms.TextBox();
- this.b_RunTAS = new System.Windows.Forms.Button();
- this.b_LoadCartridges = new System.Windows.Forms.Button();
- this.panel1 = new System.Windows.Forms.Panel();
- this.rb_FromPOW = new System.Windows.Forms.RadioButton();
- this.rb_FromRES = new System.Windows.Forms.RadioButton();
- this.panel1.SuspendLayout();
- this.SuspendLayout();
- //
- // cb_CpuClock
- //
- this.cb_CpuClock.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- this.cb_CpuClock.FormattingEnabled = true;
- this.cb_CpuClock.Items.AddRange(new object[] {
- "Phase 0",
- "Phase 1",
- "Phase 2",
- "Phase 3",
- "Phase 4",
- "Phase 5",
- "Phase 6",
- "Phase 7",
- "Phase 8",
- "Phase 9",
- "Phase 10",
- "Phase 11"});
- this.cb_CpuClock.Location = new System.Drawing.Point(177, 56);
- this.cb_CpuClock.Name = "cb_CpuClock";
- this.cb_CpuClock.Size = new System.Drawing.Size(65, 21);
- this.cb_CpuClock.TabIndex = 24;
- //
- // label1
- //
- this.label1.AutoSize = true;
- this.label1.Location = new System.Drawing.Point(22, 59);
- this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(149, 13);
- this.label1.TabIndex = 23;
- this.label1.Text = "CPU / Master clock alignment";
- //
- // label3
- //
- this.label3.AutoSize = true;
- this.label3.Location = new System.Drawing.Point(22, 82);
- this.label3.Name = "label3";
- this.label3.Size = new System.Drawing.Size(149, 13);
- this.label3.TabIndex = 22;
- this.label3.Text = "PPU / Master clock alignment";
- //
- // cb_ClockAlignment
- //
- this.cb_ClockAlignment.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- this.cb_ClockAlignment.FormattingEnabled = true;
- this.cb_ClockAlignment.Items.AddRange(new object[] {
- "Phase 0",
- "Phase 1",
- "Phase 2",
- "Phase 3"});
- this.cb_ClockAlignment.Location = new System.Drawing.Point(177, 79);
- this.cb_ClockAlignment.Name = "cb_ClockAlignment";
- this.cb_ClockAlignment.Size = new System.Drawing.Size(65, 21);
- this.cb_ClockAlignment.TabIndex = 21;
- //
- // l_InputCount
- //
- this.l_InputCount.AutoSize = true;
- this.l_InputCount.Location = new System.Drawing.Point(27, 32);
- this.l_InputCount.Name = "l_InputCount";
- this.l_InputCount.Size = new System.Drawing.Size(44, 13);
- this.l_InputCount.TabIndex = 19;
- this.l_InputCount.Text = "0 inputs";
- //
- // b_BrowseFile
- //
- this.b_BrowseFile.Location = new System.Drawing.Point(266, 9);
- this.b_BrowseFile.Name = "b_BrowseFile";
- this.b_BrowseFile.Size = new System.Drawing.Size(59, 21);
- this.b_BrowseFile.TabIndex = 18;
- this.b_BrowseFile.Text = "Browse...";
- this.b_BrowseFile.UseVisualStyleBackColor = true;
- //
- // l_FilePath
- //
- this.l_FilePath.AutoSize = true;
- this.l_FilePath.Location = new System.Drawing.Point(27, 12);
- this.l_FilePath.Name = "l_FilePath";
- this.l_FilePath.Size = new System.Drawing.Size(23, 13);
- this.l_FilePath.TabIndex = 16;
- this.l_FilePath.Text = "File";
- //
- // tb_FilePath
- //
- this.tb_FilePath.Anchor = System.Windows.Forms.AnchorStyles.Top;
- this.tb_FilePath.Location = new System.Drawing.Point(54, 9);
- this.tb_FilePath.Name = "tb_FilePath";
- this.tb_FilePath.ReadOnly = true;
- this.tb_FilePath.Size = new System.Drawing.Size(206, 20);
- this.tb_FilePath.TabIndex = 15;
- //
- // b_RunTAS
- //
- this.b_RunTAS.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
- this.b_RunTAS.Enabled = false;
- this.b_RunTAS.Location = new System.Drawing.Point(25, 212);
- this.b_RunTAS.Name = "b_RunTAS";
- this.b_RunTAS.Size = new System.Drawing.Size(300, 40);
- this.b_RunTAS.TabIndex = 14;
- this.b_RunTAS.Text = "Run TAS";
- this.b_RunTAS.UseVisualStyleBackColor = true;
- this.b_RunTAS.Click += new System.EventHandler(this.b_RunTAS_Click);
- //
- // b_LoadCartridges
- //
- this.b_LoadCartridges.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
- this.b_LoadCartridges.Location = new System.Drawing.Point(25, 154);
- this.b_LoadCartridges.Name = "b_LoadCartridges";
- this.b_LoadCartridges.Size = new System.Drawing.Size(300, 40);
- this.b_LoadCartridges.TabIndex = 25;
- this.b_LoadCartridges.Text = "Load Cartridges";
- this.b_LoadCartridges.UseVisualStyleBackColor = true;
- this.b_LoadCartridges.Click += new System.EventHandler(this.b_LoadCartridges_Click);
- //
- // panel1
- //
- this.panel1.Controls.Add(this.rb_FromPOW);
- this.panel1.Controls.Add(this.rb_FromRES);
- this.panel1.Location = new System.Drawing.Point(25, 98);
- this.panel1.Name = "panel1";
- this.panel1.Size = new System.Drawing.Size(100, 49);
- this.panel1.TabIndex = 26;
- //
- // rb_FromPOW
- //
- this.rb_FromPOW.AutoSize = true;
- this.rb_FromPOW.Location = new System.Drawing.Point(5, 3);
- this.rb_FromPOW.Name = "rb_FromPOW";
- this.rb_FromPOW.Size = new System.Drawing.Size(92, 17);
- this.rb_FromPOW.TabIndex = 3;
- this.rb_FromPOW.TabStop = true;
- this.rb_FromPOW.Text = "From POWER";
- this.rb_FromPOW.UseVisualStyleBackColor = true;
- //
- // rb_FromRES
- //
- this.rb_FromRES.AutoSize = true;
- this.rb_FromRES.Location = new System.Drawing.Point(5, 26);
- this.rb_FromRES.Name = "rb_FromRES";
- this.rb_FromRES.Size = new System.Drawing.Size(87, 17);
- this.rb_FromRES.TabIndex = 4;
- this.rb_FromRES.TabStop = true;
- this.rb_FromRES.Text = "From RESET";
- this.rb_FromRES.UseVisualStyleBackColor = true;
- //
- // TASProperties3ct
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(350, 264);
- this.Controls.Add(this.panel1);
- this.Controls.Add(this.b_LoadCartridges);
- this.Controls.Add(this.cb_CpuClock);
- this.Controls.Add(this.label1);
- this.Controls.Add(this.label3);
- this.Controls.Add(this.cb_ClockAlignment);
- this.Controls.Add(this.l_InputCount);
- this.Controls.Add(this.b_BrowseFile);
- this.Controls.Add(this.l_FilePath);
- this.Controls.Add(this.tb_FilePath);
- this.Controls.Add(this.b_RunTAS);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.Name = "TASProperties3ct";
- this.Text = "3CT TAS Properties";
- this.panel1.ResumeLayout(false);
- this.panel1.PerformLayout();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.ComboBox cb_CpuClock;
- private System.Windows.Forms.Label label1;
- private System.Windows.Forms.Label label3;
- private System.Windows.Forms.ComboBox cb_ClockAlignment;
- private System.Windows.Forms.Label l_InputCount;
- private System.Windows.Forms.Button b_BrowseFile;
- private System.Windows.Forms.Label l_FilePath;
- private System.Windows.Forms.TextBox tb_FilePath;
- private System.Windows.Forms.Button b_RunTAS;
- private System.Windows.Forms.Button b_LoadCartridges;
- private System.Windows.Forms.Panel panel1;
- private System.Windows.Forms.RadioButton rb_FromPOW;
- private System.Windows.Forms.RadioButton rb_FromRES;
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.cs
deleted file mode 100644
index a543bb71..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Windows.Forms;
-using TriCNES.mappers;
-
-namespace TriCNES
-{
- public partial class TASProperties3ct : Form
- {
- public TASProperties3ct()
- {
- InitializeComponent();
- FormClosing += TASProperties_Closing;
- }
-
- private void TASProperties_Closing(Object sender, FormClosingEventArgs e)
- {
- if (MainGUI != null)
- {
- MainGUI.TASPropertiesForm3ct = null;
- }
- Dispose();
- }
-
- public string TasFilePath;
- public ushort[] TasInputLog;
- public TriCNESGUI MainGUI;
-
- public byte GetPPUClockPhase()
- {
- return (byte)cb_ClockAlignment.SelectedIndex;
- }
-
- public byte GetCPUClockPhase()
- {
- return (byte)cb_CpuClock.SelectedIndex;
- }
-
- public bool FromRESET()
- {
- return rb_FromRES.Checked;
- }
-
- public Cartridge[] CartridgeArray;
-
- public void Init()
- {
- tb_FilePath.Text = TasFilePath;
- cb_ClockAlignment.SelectedIndex = 0;
- cb_ClockAlignment.Update();
- cb_CpuClock.SelectedIndex = 0;
- cb_CpuClock.Update();
- rb_FromPOW.Checked = true;
- rb_FromPOW.Update();
- }
-
- Cartridge BackupCart;
-
- private void b_RunTAS_Click(object sender, EventArgs e)
- {
- if (rb_FromPOW.Checked)
- {
- int i = 0;
- while (i < CartridgeArray.Length)
- {
- CartridgeArray[i].PRGRAM = new byte[0x2000];
- if (CartridgeArray[i].UsingCHRRAM)
- {
- if ((64 << CartridgeArray[i].ROM[11]) == 0)
- {
- CartridgeArray[i].CHRROM = new byte[0x2000]; // Default to 0x2000 bytes of CHR RAM.
- }
- else
- {
- CartridgeArray[i].CHRROM = new byte[64 << CartridgeArray[i].ROM[11]]; // 0x2000 bytes of CHR ROM, multiplied by byte 5 of the iNES header.
- }
- }
- Mapper MapperChip = Cartridge.SetMapper(CartridgeArray[i].MemoryMapper);
-
- MapperChip.Cart = CartridgeArray[i];
- CartridgeArray[i].MapperChip = MapperChip;
- i++;
- }
- }
- MainGUI.Start3CTTAS();
- }
-
- public List CyclesToSwapOn;
- public List CartsToSwapIn;
- private void b_LoadCartridges_Click(object sender, EventArgs e)
- {
- bool error = false;
- // check if rom folder is empty
- string Dir = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\"))
- {
- Dir += @"roms\";
- if(Directory.GetFiles(Dir).Length == 0)
- {
- MessageBox.Show("Loading a .3ct TAS requires your roms to be located in the TriCNES roms folder.");
- return;
- }
- }
- // rom folder isn't empty!
-
- StringReader SR = new StringReader(File.ReadAllText(tb_FilePath.Text));
- string l = SR.ReadLine();
- int count = int.Parse(l);
- CartridgeArray = new Cartridge[count];
- int i = 0;
- while(i < count)
- {
- l = SR.ReadLine();
- if(File.Exists(Dir+l))
- {
- if(i ==0)
- {
- BackupCart = new Cartridge(Dir + l);
- }
- if (MainGUI.EMU != null && MainGUI.EMU.Cart.Name == (Dir + l))
- {
- CartridgeArray[i] = MainGUI.EMU.Cart; // If running a TAS from RESET, we want to use the currently loaded cartridge
- }
- else
- {
- CartridgeArray[i] = new Cartridge(Dir + l);
- }
- }
- else
- {
- MessageBox.Show("TriCNES roms folder is missing a required ROM for this TAS!\n\nMissing ROM: \"" + l + "\"");
- return;
- }
- i++;
- }
- // if all carts are now loaded.
- // let's also prepare the cycles to swap on, and the carts to swap in
- CyclesToSwapOn = new List();
- CartsToSwapIn = new List();
-
- l = SR.ReadLine();
- while (l != null)
- {
- // the format here is:
- //x y
- //x and y could be any length, but there's a space between them.
-
- string s = l.Substring(0, l.IndexOf(" "));
- CyclesToSwapOn.Add(int.Parse(s));
- s = l.Remove(0,s.Length+1);
- CartsToSwapIn.Add(int.Parse(s));
- l = SR.ReadLine();
- }
-
-
- b_RunTAS.Enabled = true;
- }
-
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.resx
deleted file mode 100644
index a380ada5..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TASProperties3ct.resx
+++ /dev/null
@@ -1,197 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
-
-
- AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA
- AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD/////////////
- //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA
- AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//
- /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA
- AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//
- ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA
- AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA
- AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/////////
- //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA
- AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA
- AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD/////////////
- ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/////////
- //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////
- //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////
- /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA
- zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP////////
- //////////////////////////////////8=
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.Designer.cs
deleted file mode 100644
index 246e1f0c..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.Designer.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-namespace TriCNES
-{
- partial class TriCHexEditor
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCHexEditor));
- this.pb_hexView = new System.Windows.Forms.PictureBox();
- this.vScrollBar1 = new System.Windows.Forms.VScrollBar();
- this.menuStrip1 = new System.Windows.Forms.MenuStrip();
- this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.scopeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.rAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.cPUAddressSpaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.vRAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.pPUAddressSpaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.oAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.paletteRAMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.copyToClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- ((System.ComponentModel.ISupportInitialize)(this.pb_hexView)).BeginInit();
- this.menuStrip1.SuspendLayout();
- this.SuspendLayout();
- //
- // pb_hexView
- //
- this.pb_hexView.Location = new System.Drawing.Point(12, 37);
- this.pb_hexView.Name = "pb_hexView";
- this.pb_hexView.Size = new System.Drawing.Size(312, 512);
- this.pb_hexView.TabIndex = 0;
- this.pb_hexView.TabStop = false;
- //
- // vScrollBar1
- //
- this.vScrollBar1.LargeChange = 32;
- this.vScrollBar1.Location = new System.Drawing.Point(327, 57);
- this.vScrollBar1.Maximum = 128;
- this.vScrollBar1.Name = "vScrollBar1";
- this.vScrollBar1.Size = new System.Drawing.Size(17, 492);
- this.vScrollBar1.TabIndex = 1;
- //
- // menuStrip1
- //
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.fileToolStripMenuItem,
- this.settingsToolStripMenuItem});
- this.menuStrip1.Location = new System.Drawing.Point(0, 0);
- this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(356, 24);
- this.menuStrip1.TabIndex = 2;
- this.menuStrip1.Text = "menuStrip1";
- //
- // settingsToolStripMenuItem
- //
- this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.scopeToolStripMenuItem});
- this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
- this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
- this.settingsToolStripMenuItem.Text = "Settings";
- //
- // scopeToolStripMenuItem
- //
- this.scopeToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.rAMToolStripMenuItem,
- this.cPUAddressSpaceToolStripMenuItem,
- this.vRAMToolStripMenuItem,
- this.pPUAddressSpaceToolStripMenuItem,
- this.oAMToolStripMenuItem,
- this.paletteRAMToolStripMenuItem});
- this.scopeToolStripMenuItem.Name = "scopeToolStripMenuItem";
- this.scopeToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.scopeToolStripMenuItem.Text = "Scope";
- //
- // rAMToolStripMenuItem
- //
- this.rAMToolStripMenuItem.Checked = true;
- this.rAMToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
- this.rAMToolStripMenuItem.Name = "rAMToolStripMenuItem";
- this.rAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.rAMToolStripMenuItem.Text = "RAM";
- this.rAMToolStripMenuItem.Click += new System.EventHandler(this.rAMToolStripMenuItem_Click);
- //
- // cPUAddressSpaceToolStripMenuItem
- //
- this.cPUAddressSpaceToolStripMenuItem.Name = "cPUAddressSpaceToolStripMenuItem";
- this.cPUAddressSpaceToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.cPUAddressSpaceToolStripMenuItem.Text = "CPU Address Space";
- this.cPUAddressSpaceToolStripMenuItem.Click += new System.EventHandler(this.cPUAddressSpaceToolStripMenuItem_Click);
- //
- // vRAMToolStripMenuItem
- //
- this.vRAMToolStripMenuItem.Name = "vRAMToolStripMenuItem";
- this.vRAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.vRAMToolStripMenuItem.Text = "VRAM";
- this.vRAMToolStripMenuItem.Click += new System.EventHandler(this.vRAMToolStripMenuItem_Click);
- //
- // pPUAddressSpaceToolStripMenuItem
- //
- this.pPUAddressSpaceToolStripMenuItem.Name = "pPUAddressSpaceToolStripMenuItem";
- this.pPUAddressSpaceToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.pPUAddressSpaceToolStripMenuItem.Text = "PPU Address Space";
- this.pPUAddressSpaceToolStripMenuItem.Click += new System.EventHandler(this.pPUAddressSpaceToolStripMenuItem_Click);
- //
- // oAMToolStripMenuItem
- //
- this.oAMToolStripMenuItem.Name = "oAMToolStripMenuItem";
- this.oAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.oAMToolStripMenuItem.Text = "OAM";
- this.oAMToolStripMenuItem.Click += new System.EventHandler(this.oAMToolStripMenuItem_Click);
- //
- // paletteRAMToolStripMenuItem
- //
- this.paletteRAMToolStripMenuItem.Name = "paletteRAMToolStripMenuItem";
- this.paletteRAMToolStripMenuItem.Size = new System.Drawing.Size(176, 22);
- this.paletteRAMToolStripMenuItem.Text = "Palette RAM";
- this.paletteRAMToolStripMenuItem.Click += new System.EventHandler(this.paletteRAMToolStripMenuItem_Click);
- //
- // fileToolStripMenuItem
- //
- this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.copyToClipboardToolStripMenuItem});
- this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
- this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
- this.fileToolStripMenuItem.Text = "File";
- //
- // copyToClipboardToolStripMenuItem
- //
- this.copyToClipboardToolStripMenuItem.Name = "copyToClipboardToolStripMenuItem";
- this.copyToClipboardToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.copyToClipboardToolStripMenuItem.Text = "Copy to Clipboard";
- this.copyToClipboardToolStripMenuItem.Click += new System.EventHandler(this.copyToClipboardToolStripMenuItem_Click);
- //
- // TriCHexEditor
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(356, 561);
- this.Controls.Add(this.vScrollBar1);
- this.Controls.Add(this.pb_hexView);
- this.Controls.Add(this.menuStrip1);
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.MainMenuStrip = this.menuStrip1;
- this.MinimumSize = new System.Drawing.Size(16, 135);
- this.Name = "TriCHexEditor";
- this.Text = "Hex Editor";
- ((System.ComponentModel.ISupportInitialize)(this.pb_hexView)).EndInit();
- this.menuStrip1.ResumeLayout(false);
- this.menuStrip1.PerformLayout();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.PictureBox pb_hexView;
- private System.Windows.Forms.VScrollBar vScrollBar1;
- private System.Windows.Forms.MenuStrip menuStrip1;
- private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem scopeToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem rAMToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem cPUAddressSpaceToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem vRAMToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem pPUAddressSpaceToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem oAMToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem paletteRAMToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem copyToClipboardToolStripMenuItem;
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.cs
deleted file mode 100644
index 972c0d20..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.cs
+++ /dev/null
@@ -1,295 +0,0 @@
-using System;
-using System.Drawing;
-using System.Text;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- public partial class TriCHexEditor : Form
- {
- public TriCHexEditor()
- {
- InitializeComponent();
- hexBitmap = new Bitmap(312,512);
- G = Graphics.FromImage(hexBitmap);
- Font_Consolas = new Font("Consolas", 8);
- Scope = "RAM";
- Resize += TriCHexEditor_Resize;
- vScrollBar1.ValueChanged += Scrollbar_ValueChanged;
- FormClosing += TriCHexEditor_Closing;
- }
- public void TriCHexEditor_Closing(object sender, FormClosingEventArgs e)
- {
- if(MainGUI != null)
- {
- MainGUI.HexEditor = null;
- }
- Dispose();
- }
-
- public TriCNESGUI MainGUI;
- public Graphics G;
- public Bitmap hexBitmap;
- public Font Font_Consolas;
- public int Scroll;
- public string Scope;
- ScopeType scopeType = ScopeType.RAM;
-
- public int MaxRows = 32;
-
- private void TriCHexEditor_Resize(object sender, EventArgs e)
- {
- MaxRows = (Size.Height - 115) / 15;
- hexBitmap = new Bitmap(312, Size.Height - 88);
- pb_hexView.Size = new Size(312, Size.Height - 88);
- vScrollBar1.Size = new Size(vScrollBar1.Width,Size.Height-108);
- vScrollBar1.LargeChange = MaxRows;
- G = Graphics.FromImage(hexBitmap);
- RefreshEntireHexView();
- }
-
- private void Scrollbar_ValueChanged(object sender, EventArgs e)
- {
- Scroll = vScrollBar1.Value;
- RefreshEntireHexView();
- }
-
- enum ScopeType
- {
- RAM,
- CPU_Address_Space,
- VRAM,
- PPU_Address_Space,
- OAM,
- Palette_RAM
- };
-
- public void Update()
- {
- MethodInvoker upd = delegate
- {
- RefreshEntireHexView();
- };
- try
- {
- this.Invoke(upd);
- }
- catch(Exception e)
- {
-
- }
- }
-
- public void RefreshEntireHexView()
- {
- if(MainGUI.EMU == null)
- {
- return;
- }
-
- vScrollBar1.Enabled = MaxRows < vScrollBar1.Maximum;
- vScrollBar1.Update();
-
- G.FillRectangle(Brushes.WhiteSmoke,new Rectangle(0,0, 312, Size.Height - 88));
- G.DrawString(Scope + ":", Font_Consolas, Brushes.Black, new Point(0, 0));
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(" " + x.ToString("X"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 16));
- }
- switch (scopeType)
- {
- case ScopeType.RAM:
- {
- int i = Scroll*0x10;
- int y = 0;
- while(i < 0x800 && y < MaxRows)
- {
- // print $xy0:
- G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32+y*15));
- for(int x=0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.RAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x*15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- case ScopeType.CPU_Address_Space:
- {
- int i = Scroll * 0x10;
- int y = 0;
- while (i < 0x10000 && y < MaxRows)
- {
- // print $xyz0:
- G.DrawString("$" + (i).ToString("X4") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15));
-
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.Observe((ushort)i).ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- case ScopeType.VRAM:
- {
- int i = Scroll * 0x10;
- int y = 0;
- while (i < 0x800 && y < MaxRows)
- {
- // print $xy0:
- G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15));
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.VRAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- case ScopeType.PPU_Address_Space:
- {
- int i = Scroll * 0x10;
- int y = 0;
- while (i < 0x4000 && y < MaxRows)
- {
- // print $xyz0:
- G.DrawString("$" + (i).ToString("X4") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15));
-
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.ObservePPU((ushort)i).ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- case ScopeType.OAM:
- {
- int i = Scroll * 0x10;
- int y = 0;
- while (i < 0x100 && y < MaxRows)
- {
- // print $xy0:
- G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15));
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.OAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- case ScopeType.Palette_RAM:
- {
- int i = Scroll * 0x10;
- int y = 0;
- while (i < 0x20 && y < MaxRows)
- {
- // print $xy0:
- G.DrawString("$" + (i).ToString("X3") + ":", Font_Consolas, Brushes.Black, new Point(0, 32 + y * 15));
- for (int x = 0; x < 0x10; x++)
- {
- G.DrawString(MainGUI.EMU.PaletteRAM[i].ToString("X2"), Font_Consolas, Brushes.Black, new Point(42 + x * 15, 32 + y * 15));
- i++;
- }
- y++;
- }
- }
- break;
- }
-
- pb_hexView.Image = hexBitmap;
- pb_hexView.Update();
- }
-
- void ChangeScope(ScopeType st)
- {
- Scroll = 0;
- vScrollBar1.Value = 0;
- vScrollBar1.Update();
- rAMToolStripMenuItem.Checked = st == ScopeType.RAM;
- cPUAddressSpaceToolStripMenuItem.Checked = st == ScopeType.CPU_Address_Space;
- vRAMToolStripMenuItem.Checked = st == ScopeType.VRAM;
- pPUAddressSpaceToolStripMenuItem.Checked = st == ScopeType.PPU_Address_Space;
- oAMToolStripMenuItem.Checked = st == ScopeType.OAM;
- paletteRAMToolStripMenuItem.Checked = st == ScopeType.Palette_RAM;
- scopeType = st;
- switch(st)
- {
- case ScopeType.RAM:
- vScrollBar1.Maximum = 0x80; Scope = "RAM"; break;
- case ScopeType.CPU_Address_Space:
- vScrollBar1.Maximum = 0x1000; Scope = "CPU Address Space"; break;
- case ScopeType.VRAM:
- vScrollBar1.Maximum = 0x80; Scope = "VRAM"; break;
- case ScopeType.PPU_Address_Space:
- vScrollBar1.Maximum = 0x400; Scope = "PPU Address Space"; break;
- case ScopeType.OAM:
- vScrollBar1.Maximum = 0x10; Scope = "OAM"; break;
- case ScopeType.Palette_RAM:
- vScrollBar1.Maximum = 0x2; Scope = "Palette RAM"; break;
- }
- RefreshEntireHexView();
- }
-
- private void rAMToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.RAM);
- }
-
- private void cPUAddressSpaceToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.CPU_Address_Space);
- }
-
- private void vRAMToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.VRAM);
- }
-
- private void pPUAddressSpaceToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.PPU_Address_Space);
- }
-
- private void oAMToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.OAM);
- }
-
- private void paletteRAMToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ChangeScope(ScopeType.Palette_RAM);
- }
-
- private void copyToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
- {
- StringBuilder sb = new StringBuilder();
- for(int i = 0; i < vScrollBar1.Maximum*0x10;i++)
- {
- switch (scopeType)
- {
- case ScopeType.RAM:
- sb.Append(MainGUI.EMU.RAM[i].ToString("X2") + " "); break;
- case ScopeType.CPU_Address_Space:
- sb.Append(MainGUI.EMU.Observe((ushort)i).ToString("X2") + " "); break;
- case ScopeType.VRAM:
- sb.Append(MainGUI.EMU.VRAM[i].ToString("X2") + " "); break;
- case ScopeType.PPU_Address_Space:
- sb.Append(MainGUI.EMU.ObservePPU((ushort)i).ToString("X2") + " "); break;
- case ScopeType.OAM:
- sb.Append(MainGUI.EMU.OAM[i].ToString("X2") + " "); break;
- case ScopeType.Palette_RAM:
- sb.Append(MainGUI.EMU.PaletteRAM[i].ToString("X2") + " "); break;
- }
- }
- Clipboard.SetText(sb.ToString());
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.resx
deleted file mode 100644
index 9c754a53..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCHexEditor.resx
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 17, 17
-
-
-
-
- AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA
- AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD/////////////
- //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA
- AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//
- /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA
- AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//
- ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA
- AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA
- AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/////////
- //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA
- AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA
- AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD/////////////
- ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/////////
- //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////
- //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////
- /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA
- zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP////////
- //////////////////////////////////8=
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.Designer.cs
deleted file mode 100644
index 20b65eb6..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.Designer.cs
+++ /dev/null
@@ -1,487 +0,0 @@
-using System.Threading;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- partial class TriCNESGUI
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCNESGUI));
- this.menuStrip1 = new System.Windows.Forms.MenuStrip();
- this.consoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.loadROMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.resetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.powerCycleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.screenshotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.saveStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.loadStateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.tASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.loadTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.load3ctToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.pPUClockToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.phase0ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.phase1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.phase2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.phase3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.decodeNTSCSignalsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.trueToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.showRawSignalsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.falseToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.viewBoarderToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.toolstrip_ViewBorders_True = new System.Windows.Forms.ToolStripMenuItem();
- this.toolstrip_ViewBorders_False = new System.Windows.Forms.ToolStripMenuItem();
- this.viewScaleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem3 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem5 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem6 = new System.Windows.Forms.ToolStripMenuItem();
- this.xToolStripMenuItem7 = new System.Windows.Forms.ToolStripMenuItem();
- this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.traceLoggerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.nametableViewerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.tASTimelineToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.hexEditorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.pb_Screen = new TriCNES.PictureBoxWithInterpolationMode();
- this.cartridgeConnectorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.menuStrip1.SuspendLayout();
- ((System.ComponentModel.ISupportInitialize)(this.pb_Screen)).BeginInit();
- this.SuspendLayout();
- //
- // menuStrip1
- //
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.consoleToolStripMenuItem,
- this.tASToolStripMenuItem,
- this.settingsToolStripMenuItem,
- this.toolsToolStripMenuItem});
- this.menuStrip1.Location = new System.Drawing.Point(0, 0);
- this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(256, 24);
- this.menuStrip1.TabIndex = 0;
- this.menuStrip1.Text = "menuStrip1";
- //
- // consoleToolStripMenuItem
- //
- this.consoleToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.loadROMToolStripMenuItem,
- this.resetToolStripMenuItem,
- this.powerCycleToolStripMenuItem,
- this.screenshotToolStripMenuItem,
- this.saveStateToolStripMenuItem,
- this.loadStateToolStripMenuItem});
- this.consoleToolStripMenuItem.Name = "consoleToolStripMenuItem";
- this.consoleToolStripMenuItem.Size = new System.Drawing.Size(62, 20);
- this.consoleToolStripMenuItem.Text = "Console";
- //
- // loadROMToolStripMenuItem
- //
- this.loadROMToolStripMenuItem.Name = "loadROMToolStripMenuItem";
- this.loadROMToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.loadROMToolStripMenuItem.Text = "Load ROM";
- this.loadROMToolStripMenuItem.Click += new System.EventHandler(this.loadROMToolStripMenuItem_Click);
- //
- // resetToolStripMenuItem
- //
- this.resetToolStripMenuItem.Name = "resetToolStripMenuItem";
- this.resetToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.resetToolStripMenuItem.Text = "Reset";
- this.resetToolStripMenuItem.Click += new System.EventHandler(this.resetToolStripMenuItem_Click);
- //
- // powerCycleToolStripMenuItem
- //
- this.powerCycleToolStripMenuItem.Name = "powerCycleToolStripMenuItem";
- this.powerCycleToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.powerCycleToolStripMenuItem.Text = "Power Cycle";
- this.powerCycleToolStripMenuItem.Click += new System.EventHandler(this.powerCycleToolStripMenuItem_Click);
- //
- // screenshotToolStripMenuItem
- //
- this.screenshotToolStripMenuItem.Name = "screenshotToolStripMenuItem";
- this.screenshotToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.screenshotToolStripMenuItem.Text = "Screenshot";
- this.screenshotToolStripMenuItem.Click += new System.EventHandler(this.screenshotToolStripMenuItem_Click);
- //
- // saveStateToolStripMenuItem
- //
- this.saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem";
- this.saveStateToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.saveStateToolStripMenuItem.Text = "Save State";
- this.saveStateToolStripMenuItem.Click += new System.EventHandler(this.saveStateToolStripMenuItem_Click);
- //
- // loadStateToolStripMenuItem
- //
- this.loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem";
- this.loadStateToolStripMenuItem.Size = new System.Drawing.Size(139, 22);
- this.loadStateToolStripMenuItem.Text = "Load State";
- this.loadStateToolStripMenuItem.Click += new System.EventHandler(this.loadStateToolStripMenuItem_Click);
- //
- // tASToolStripMenuItem
- //
- this.tASToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.loadTASToolStripMenuItem,
- this.load3ctToolStripMenuItem});
- this.tASToolStripMenuItem.Name = "tASToolStripMenuItem";
- this.tASToolStripMenuItem.Size = new System.Drawing.Size(38, 20);
- this.tASToolStripMenuItem.Text = "TAS";
- //
- // loadTASToolStripMenuItem
- //
- this.loadTASToolStripMenuItem.Name = "loadTASToolStripMenuItem";
- this.loadTASToolStripMenuItem.Size = new System.Drawing.Size(144, 22);
- this.loadTASToolStripMenuItem.Text = "Load TAS";
- this.loadTASToolStripMenuItem.Click += new System.EventHandler(this.loadTASToolStripMenuItem_Click);
- //
- // load3ctToolStripMenuItem
- //
- this.load3ctToolStripMenuItem.Name = "load3ctToolStripMenuItem";
- this.load3ctToolStripMenuItem.Size = new System.Drawing.Size(144, 22);
- this.load3ctToolStripMenuItem.Text = "Load .3ct TAS";
- this.load3ctToolStripMenuItem.Click += new System.EventHandler(this.load3ctToolStripMenuItem_Click);
- //
- // settingsToolStripMenuItem
- //
- this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.pPUClockToolStripMenuItem,
- this.decodeNTSCSignalsToolStripMenuItem,
- this.viewBoarderToolStripMenuItem,
- this.viewScaleToolStripMenuItem});
- this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
- this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
- this.settingsToolStripMenuItem.Text = "Settings";
- //
- // pPUClockToolStripMenuItem
- //
- this.pPUClockToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.phase0ToolStripMenuItem,
- this.phase1ToolStripMenuItem,
- this.phase2ToolStripMenuItem,
- this.phase3ToolStripMenuItem});
- this.pPUClockToolStripMenuItem.Name = "pPUClockToolStripMenuItem";
- this.pPUClockToolStripMenuItem.Size = new System.Drawing.Size(186, 22);
- this.pPUClockToolStripMenuItem.Text = "PPU Clock";
- //
- // phase0ToolStripMenuItem
- //
- this.phase0ToolStripMenuItem.Checked = true;
- this.phase0ToolStripMenuItem.CheckOnClick = true;
- this.phase0ToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
- this.phase0ToolStripMenuItem.Name = "phase0ToolStripMenuItem";
- this.phase0ToolStripMenuItem.Size = new System.Drawing.Size(114, 22);
- this.phase0ToolStripMenuItem.Text = "Phase 0";
- this.phase0ToolStripMenuItem.Click += new System.EventHandler(this.phase0ToolStripMenuItem_Click);
- //
- // phase1ToolStripMenuItem
- //
- this.phase1ToolStripMenuItem.CheckOnClick = true;
- this.phase1ToolStripMenuItem.Name = "phase1ToolStripMenuItem";
- this.phase1ToolStripMenuItem.Size = new System.Drawing.Size(114, 22);
- this.phase1ToolStripMenuItem.Text = "Phase 1";
- this.phase1ToolStripMenuItem.Click += new System.EventHandler(this.phase1ToolStripMenuItem_Click);
- //
- // phase2ToolStripMenuItem
- //
- this.phase2ToolStripMenuItem.CheckOnClick = true;
- this.phase2ToolStripMenuItem.Name = "phase2ToolStripMenuItem";
- this.phase2ToolStripMenuItem.Size = new System.Drawing.Size(114, 22);
- this.phase2ToolStripMenuItem.Text = "Phase 2";
- this.phase2ToolStripMenuItem.Click += new System.EventHandler(this.phase2ToolStripMenuItem_Click);
- //
- // phase3ToolStripMenuItem
- //
- this.phase3ToolStripMenuItem.CheckOnClick = true;
- this.phase3ToolStripMenuItem.Name = "phase3ToolStripMenuItem";
- this.phase3ToolStripMenuItem.Size = new System.Drawing.Size(114, 22);
- this.phase3ToolStripMenuItem.Text = "Phase 3";
- this.phase3ToolStripMenuItem.Click += new System.EventHandler(this.phase3ToolStripMenuItem_Click);
- //
- // decodeNTSCSignalsToolStripMenuItem
- //
- this.decodeNTSCSignalsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.trueToolStripMenuItem,
- this.showRawSignalsToolStripMenuItem,
- this.falseToolStripMenuItem});
- this.decodeNTSCSignalsToolStripMenuItem.Name = "decodeNTSCSignalsToolStripMenuItem";
- this.decodeNTSCSignalsToolStripMenuItem.Size = new System.Drawing.Size(186, 22);
- this.decodeNTSCSignalsToolStripMenuItem.Text = "Decode NTSC Signals";
- //
- // trueToolStripMenuItem
- //
- this.trueToolStripMenuItem.Name = "trueToolStripMenuItem";
- this.trueToolStripMenuItem.Size = new System.Drawing.Size(164, 22);
- this.trueToolStripMenuItem.Text = "True";
- this.trueToolStripMenuItem.Click += new System.EventHandler(this.trueToolStripMenuItem_Click);
- //
- // showRawSignalsToolStripMenuItem
- //
- this.showRawSignalsToolStripMenuItem.Name = "showRawSignalsToolStripMenuItem";
- this.showRawSignalsToolStripMenuItem.Size = new System.Drawing.Size(164, 22);
- this.showRawSignalsToolStripMenuItem.Text = "Show raw signals";
- this.showRawSignalsToolStripMenuItem.Click += new System.EventHandler(this.showRawSignalsToolStripMenuItem_Click);
- //
- // falseToolStripMenuItem
- //
- this.falseToolStripMenuItem.Checked = true;
- this.falseToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
- this.falseToolStripMenuItem.Name = "falseToolStripMenuItem";
- this.falseToolStripMenuItem.Size = new System.Drawing.Size(164, 22);
- this.falseToolStripMenuItem.Text = "False";
- this.falseToolStripMenuItem.Click += new System.EventHandler(this.falseToolStripMenuItem_Click);
- //
- // viewBoarderToolStripMenuItem
- //
- this.viewBoarderToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.toolstrip_ViewBorders_True,
- this.toolstrip_ViewBorders_False});
- this.viewBoarderToolStripMenuItem.Name = "viewBoarderToolStripMenuItem";
- this.viewBoarderToolStripMenuItem.Size = new System.Drawing.Size(186, 22);
- this.viewBoarderToolStripMenuItem.Text = "View Border";
- //
- // toolstrip_ViewBorders_True
- //
- this.toolstrip_ViewBorders_True.Name = "toolstrip_ViewBorders_True";
- this.toolstrip_ViewBorders_True.Size = new System.Drawing.Size(100, 22);
- this.toolstrip_ViewBorders_True.Text = "True";
- this.toolstrip_ViewBorders_True.Click += new System.EventHandler(this.trueToolStripMenuItem1_Click);
- //
- // toolstrip_ViewBorders_False
- //
- this.toolstrip_ViewBorders_False.Checked = true;
- this.toolstrip_ViewBorders_False.CheckState = System.Windows.Forms.CheckState.Checked;
- this.toolstrip_ViewBorders_False.Name = "toolstrip_ViewBorders_False";
- this.toolstrip_ViewBorders_False.Size = new System.Drawing.Size(100, 22);
- this.toolstrip_ViewBorders_False.Text = "False";
- this.toolstrip_ViewBorders_False.Click += new System.EventHandler(this.falseToolStripMenuItem1_Click);
- //
- // viewScaleToolStripMenuItem
- //
- this.viewScaleToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.xToolStripMenuItem,
- this.xToolStripMenuItem1,
- this.xToolStripMenuItem2,
- this.xToolStripMenuItem3,
- this.xToolStripMenuItem4,
- this.xToolStripMenuItem5,
- this.xToolStripMenuItem6,
- this.xToolStripMenuItem7});
- this.viewScaleToolStripMenuItem.Name = "viewScaleToolStripMenuItem";
- this.viewScaleToolStripMenuItem.Size = new System.Drawing.Size(186, 22);
- this.viewScaleToolStripMenuItem.Text = "View Scale";
- //
- // xToolStripMenuItem
- //
- this.xToolStripMenuItem.Name = "xToolStripMenuItem";
- this.xToolStripMenuItem.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem.Text = "1x";
- this.xToolStripMenuItem.Click += new System.EventHandler(this.xToolStripMenuItem_Click);
- //
- // xToolStripMenuItem1
- //
- this.xToolStripMenuItem1.Name = "xToolStripMenuItem1";
- this.xToolStripMenuItem1.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem1.Text = "2x";
- this.xToolStripMenuItem1.Click += new System.EventHandler(this.xToolStripMenuItem1_Click);
- //
- // xToolStripMenuItem2
- //
- this.xToolStripMenuItem2.Name = "xToolStripMenuItem2";
- this.xToolStripMenuItem2.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem2.Text = "3x";
- this.xToolStripMenuItem2.Click += new System.EventHandler(this.xToolStripMenuItem2_Click);
- //
- // xToolStripMenuItem3
- //
- this.xToolStripMenuItem3.Name = "xToolStripMenuItem3";
- this.xToolStripMenuItem3.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem3.Text = "4x";
- this.xToolStripMenuItem3.Click += new System.EventHandler(this.xToolStripMenuItem3_Click);
- //
- // xToolStripMenuItem4
- //
- this.xToolStripMenuItem4.Name = "xToolStripMenuItem4";
- this.xToolStripMenuItem4.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem4.Text = "5x";
- this.xToolStripMenuItem4.Click += new System.EventHandler(this.xToolStripMenuItem4_Click);
- //
- // xToolStripMenuItem5
- //
- this.xToolStripMenuItem5.Name = "xToolStripMenuItem5";
- this.xToolStripMenuItem5.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem5.Text = "6x";
- this.xToolStripMenuItem5.Click += new System.EventHandler(this.xToolStripMenuItem5_Click);
- //
- // xToolStripMenuItem6
- //
- this.xToolStripMenuItem6.Name = "xToolStripMenuItem6";
- this.xToolStripMenuItem6.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem6.Text = "7x";
- this.xToolStripMenuItem6.Click += new System.EventHandler(this.xToolStripMenuItem6_Click);
- //
- // xToolStripMenuItem7
- //
- this.xToolStripMenuItem7.Name = "xToolStripMenuItem7";
- this.xToolStripMenuItem7.Size = new System.Drawing.Size(86, 22);
- this.xToolStripMenuItem7.Text = "8x";
- this.xToolStripMenuItem7.Click += new System.EventHandler(this.xToolStripMenuItem7_Click);
- //
- // toolsToolStripMenuItem
- //
- this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.traceLoggerToolStripMenuItem,
- this.nametableViewerToolStripMenuItem,
- this.tASTimelineToolStripMenuItem,
- this.hexEditorToolStripMenuItem,
- this.cartridgeConnectorToolStripMenuItem});
- this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem";
- this.toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20);
- this.toolsToolStripMenuItem.Text = "Tools";
- //
- // traceLoggerToolStripMenuItem
- //
- this.traceLoggerToolStripMenuItem.Name = "traceLoggerToolStripMenuItem";
- this.traceLoggerToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
- this.traceLoggerToolStripMenuItem.Text = "TraceLogger";
- this.traceLoggerToolStripMenuItem.Click += new System.EventHandler(this.traceLoggerToolStripMenuItem_Click);
- //
- // nametableViewerToolStripMenuItem
- //
- this.nametableViewerToolStripMenuItem.Name = "nametableViewerToolStripMenuItem";
- this.nametableViewerToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
- this.nametableViewerToolStripMenuItem.Text = "Nametable Viewer";
- this.nametableViewerToolStripMenuItem.Click += new System.EventHandler(this.nametableViewerToolStripMenuItem_Click);
- //
- // tASTimelineToolStripMenuItem
- //
- this.tASTimelineToolStripMenuItem.Name = "tASTimelineToolStripMenuItem";
- this.tASTimelineToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
- this.tASTimelineToolStripMenuItem.Text = "TAS Timeline";
- this.tASTimelineToolStripMenuItem.Click += new System.EventHandler(this.tASTimelineToolStripMenuItem_Click);
- //
- // hexEditorToolStripMenuItem
- //
- this.hexEditorToolStripMenuItem.Name = "hexEditorToolStripMenuItem";
- this.hexEditorToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
- this.hexEditorToolStripMenuItem.Text = "Hex Editor";
- this.hexEditorToolStripMenuItem.Click += new System.EventHandler(this.hexEditorToolStripMenuItem_Click);
- //
- // pb_Screen
- //
- this.pb_Screen.AllowDrop = true;
- this.pb_Screen.BackColor = System.Drawing.Color.Black;
- this.pb_Screen.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
- this.pb_Screen.Location = new System.Drawing.Point(0, 27);
- this.pb_Screen.Name = "pb_Screen";
- this.pb_Screen.Size = new System.Drawing.Size(256, 240);
- this.pb_Screen.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
- this.pb_Screen.TabIndex = 1;
- this.pb_Screen.TabStop = false;
- //
- // cartridgeConnectorToolStripMenuItem
- //
- this.cartridgeConnectorToolStripMenuItem.Name = "cartridgeConnectorToolStripMenuItem";
- this.cartridgeConnectorToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
- this.cartridgeConnectorToolStripMenuItem.Text = "Cartridge Connector";
- this.cartridgeConnectorToolStripMenuItem.Click += new System.EventHandler(this.cartridgeConnectorToolStripMenuItem_Click);
- //
- // TriCNESGUI
- //
- this.AllowDrop = true;
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange;
- this.ClientSize = new System.Drawing.Size(256, 267);
- this.Controls.Add(this.pb_Screen);
- this.Controls.Add(this.menuStrip1);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.KeyPreview = true;
- this.MainMenuStrip = this.menuStrip1;
- this.MaximizeBox = false;
- this.MaximumSize = new System.Drawing.Size(272, 306);
- this.MinimizeBox = false;
- this.MinimumSize = new System.Drawing.Size(272, 306);
- this.Name = "TriCNESGUI";
- this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
- this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
- this.Text = "TriCNES GUI";
- this.menuStrip1.ResumeLayout(false);
- this.menuStrip1.PerformLayout();
- ((System.ComponentModel.ISupportInitialize)(this.pb_Screen)).EndInit();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.MenuStrip menuStrip1;
- private System.Windows.Forms.ToolStripMenuItem consoleToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem tASToolStripMenuItem;
- private PictureBoxWithInterpolationMode pb_Screen;
- private System.Windows.Forms.ToolStripMenuItem loadROMToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem loadTASToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem load3ctToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem resetToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem powerCycleToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem screenshotToolStripMenuItem;
- private ToolStripMenuItem settingsToolStripMenuItem;
- private ToolStripMenuItem pPUClockToolStripMenuItem;
- private ToolStripMenuItem phase0ToolStripMenuItem;
- private ToolStripMenuItem phase1ToolStripMenuItem;
- private ToolStripMenuItem phase2ToolStripMenuItem;
- private ToolStripMenuItem phase3ToolStripMenuItem;
- private ToolStripMenuItem decodeNTSCSignalsToolStripMenuItem;
- private ToolStripMenuItem trueToolStripMenuItem;
- private ToolStripMenuItem falseToolStripMenuItem;
- private ToolStripMenuItem viewScaleToolStripMenuItem;
- private ToolStripMenuItem xToolStripMenuItem;
- private ToolStripMenuItem xToolStripMenuItem1;
- private ToolStripMenuItem xToolStripMenuItem2;
- private ToolStripMenuItem xToolStripMenuItem3;
- private ToolStripMenuItem xToolStripMenuItem4;
- private ToolStripMenuItem xToolStripMenuItem5;
- private ToolStripMenuItem xToolStripMenuItem6;
- private ToolStripMenuItem xToolStripMenuItem7;
- private ToolStripMenuItem toolsToolStripMenuItem;
- private ToolStripMenuItem traceLoggerToolStripMenuItem;
- private ToolStripMenuItem viewBoarderToolStripMenuItem;
- private ToolStripMenuItem toolstrip_ViewBorders_True;
- private ToolStripMenuItem toolstrip_ViewBorders_False;
- private ToolStripMenuItem nametableViewerToolStripMenuItem;
- private ToolStripMenuItem showRawSignalsToolStripMenuItem;
- private ToolStripMenuItem saveStateToolStripMenuItem;
- private ToolStripMenuItem loadStateToolStripMenuItem;
- private ToolStripMenuItem tASTimelineToolStripMenuItem;
- private ToolStripMenuItem hexEditorToolStripMenuItem;
- private ToolStripMenuItem cartridgeConnectorToolStripMenuItem;
- }
-}
-
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.cs
deleted file mode 100644
index 47270520..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.cs
+++ /dev/null
@@ -1,1977 +0,0 @@
-using SDL2;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Windows.Forms;
-using System.Windows.Input;
-using TriCNES.mappers;
-
-namespace TriCNES
-{
- public partial class TriCNESGUI : Form
- {
- // This is the the main window for a user to interact with this emulator.
- // The logic for the emulator is contained entirely in a single C# file, for easy use importing it into other projects.
- // this form here is intended to be used an an example.
- // The intended use for this emulator is to run your own code specifically to collect data, but do with it as you please.
- // Cheers! ~ Chris "100th_Coin" Siebert
- public TriCNESGUI()
- {
- InitializeComponent();
- pb_Screen.DragEnter += new DragEventHandler(pb_Screen_DragEnter);
- pb_Screen.DragDrop += new DragEventHandler(pb_Screen_DragDrop);
- FormClosing += new FormClosingEventHandler(TriCNESGUI_Closing);
- SDL.SDL_Init(SDL.SDL_INIT_GAMECONTROLLER);
- SDL.SDL_GameControllerEventState(SDL.SDL_ENABLE);
- SDL.SDL_GameControllerUpdate();
- int c = SDL.SDL_NumJoysticks();
- if (c != 0)
- {
- joystickptr = SDL.SDL_JoystickOpen(0);
- gameControllerPrt = SDL.SDL_GameControllerOpen(0);
- }
- }
- IntPtr joystickptr;
- IntPtr gameControllerPrt;
-
- bool settings_ntsc;
- bool settings_ntscRaw;
- bool settings_border;
- byte settings_alignment;
-
- public Emulator EMU;
- public Thread EmuClock;
- string filePath;
- bool FDS;
- public TASProperties TASPropertiesForm;
- public TASProperties3ct TASPropertiesForm3ct;
- public TriCTraceLogger? TraceLogger;
- public TriCNTViewer? NametableViewer;
- public TriCTASTimeline? TasTimeline;
- public TriCHexEditor? HexEditor;
- public TriC72PinConnector? CartConnector;
-
- void RunUpkeep()
- {
- if (PendingScreenshot)
- {
- PendingScreenshot = false;
- if (EMU.PPU_DecodeSignal)
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- Clipboard.SetImage(EMU.BorderedNTSCScreen.Bitmap);
- }
- else
- {
- Clipboard.SetImage(EMU.NTSCScreen.Bitmap);
- }
- }
- else
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- Clipboard.SetImage(EMU.BorderedScreen.Bitmap);
- }
- else
- {
- Clipboard.SetImage(EMU.Screen.Bitmap);
- }
- }
- }
- if (Pending_ShowScreenBorders)
- {
- Pending_ShowScreenBorders = false;
- EMU.PPU_ShowScreenBorders = true;
- BeginInvoke(new MethodInvoker(delegate () { ResizeWindow(ScreenMult); }));
- }
- if (Pending_HideScreenBorders)
- {
- Pending_HideScreenBorders = false;
- EMU.PPU_ShowScreenBorders = false;
- BeginInvoke(new MethodInvoker(delegate () { ResizeWindow(ScreenMult); }));
- }
- if (PendingSaveState)
- {
- PendingSaveState = false;
- Savestate = EMU.SaveState();
- }
- if (PendingLoadState && Savestate != null && Savestate.Count > 0)
- {
- PendingLoadState = false;
- EMU.LoadState(Savestate);
- }
- if(Pending_EjectCartridge)
- {
- Pending_EjectCartridge = false;
- EMU.Cart.MapperChip = new Mapper_NULL();
- EMU.Cart.MapperChip.Cart = EMU.Cart;
- }
- if (TraceLogger != null)
- {
- EMU.Logging = TraceLogger.Logging;
- if (EMU.DebugLog == null)
- {
- EMU.DebugLog = new StringBuilder();
- }
- EMU.DebugRange_Low = TraceLogger.RangeLow;
- EMU.DebugRange_High = TraceLogger.RangeHigh;
- EMU.OnlyDebugInRange = TraceLogger.OnlyDebugInRange();
- EMU.LoggingPPU = TraceLogger.LogPPUCycles();
- }
- else if(EMU.Logging)
- {
- EMU.Logging = false;
- EMU.DebugLog = new StringBuilder();
- }
- if(HexEditor != null)
- {
- HexEditor.Update();
- }
- if (CartConnector != null)
- {
- CartConnector.Update72PinConnector();
- }
- EMU.Cart.MapperChip.TiltingCart = CartConnector != null;
- }
-
- void RunPostFramePhase()
- {
- if (TraceLogger != null)
- {
- if (TraceLogger.Logging)
- {
- TraceLogger.Update();
- if (TraceLogger.ClearEveryFrame())
- {
- EMU.DebugLog = new StringBuilder();
- }
- }
- }
- if (NametableViewer != null && !NametableViewer.IsDisposed)
- {
- RenderNametable();
- NametableViewer.Update(NametableBitmap.Bitmap);
- }
- if (pb_Screen.InvokeRequired)
- {
- pb_Screen.BeginInvoke(new MethodInvoker(
- delegate ()
- {
- if (EMU.PPU_DecodeSignal)
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- pb_Screen.Image = EMU.BorderedNTSCScreen.Bitmap;
- }
- else
- {
- pb_Screen.Image = EMU.NTSCScreen.Bitmap;
- }
- }
- else
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- pb_Screen.Image = EMU.BorderedScreen.Bitmap;
- }
- else
- {
- pb_Screen.Image = EMU.Screen.Bitmap;
- }
- }
- pb_Screen.Update();
- }));
- }
- else
- {
- if (EMU.PPU_DecodeSignal)
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- pb_Screen.Image = EMU.BorderedNTSCScreen.Bitmap;
- }
- else
- {
- pb_Screen.Image = EMU.NTSCScreen.Bitmap;
- }
- }
- else
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- pb_Screen.Image = EMU.BorderedScreen.Bitmap;
- }
- else
- {
- pb_Screen.Image = EMU.Screen.Bitmap;
- }
- }
- pb_Screen.Update();
- }
-
- }
-
- bool[] ControllerInputs()
- {
- bool[] joystickButtons = new bool[8];
-
- int c = SDL.SDL_NumJoysticks();
- if (c != 0)
- {
- SDL.SDL_GameControllerUpdate();
-
- joystickButtons[0] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0;
- joystickButtons[1] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0;
- joystickButtons[2] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0;
- joystickButtons[3] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP) != 0;
- joystickButtons[4] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START) != 0;
- joystickButtons[5] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK) != 0;
- joystickButtons[6] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X) != 0;
- joystickButtons[7] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) != 0;
- }
- else
- {
- for (int i = 0; i < 8; i++)
- {
- joystickButtons[i] = false;
- }
- }
- return joystickButtons;
- }
-
- byte RealtimeInputs()
- {
- bool[] joystickButtons = ControllerInputs();
-
- byte controller1 = 0;
- if (joystickButtons[7] || Keyboard.IsKeyDown(Key.X)) { controller1 |= 0x80; }
- if (joystickButtons[6] || Keyboard.IsKeyDown(Key.Z)) { controller1 |= 0x40; }
- if (joystickButtons[5] || Keyboard.IsKeyDown(Key.RightShift)) { controller1 |= 0x20; }
- if (joystickButtons[4] || Keyboard.IsKeyDown(Key.Enter)) { controller1 |= 0x10; }
- if (joystickButtons[3] || Keyboard.IsKeyDown(Key.Up)) { controller1 |= 0x08; }
- if (joystickButtons[2] || Keyboard.IsKeyDown(Key.Down)) { controller1 |= 0x04; }
- if (joystickButtons[1] || Keyboard.IsKeyDown(Key.Left)) { controller1 |= 0x02; }
- if (joystickButtons[0] || Keyboard.IsKeyDown(Key.Right)) { controller1 |= 0x01; }
- return controller1;
- }
-
- CancellationTokenSource cancel;
- void ClockEmulator(CancellationToken ct)
- {
- int frameCount = 0;
-
- while (!ct.IsCancellationRequested)
- {
- if (Form.ActiveForm != null)
- {
- if (Keyboard.IsKeyDown(Key.Q)) { PendingSaveState = true; }
- if (Keyboard.IsKeyDown(Key.W)) { PendingLoadState = true; }
-
- EMU.ControllerPort1 = RealtimeInputs();
- }
- RunUpkeep();
- EMU._CoreFrameAdvance();
- RunPostFramePhase();
- frameCount++;
- }
- }
-
- DirectBitmap NametableBitmap;
- public Bitmap RenderNametable()
- {
-
-
- if (NametableBitmap != null)
- {
- NametableBitmap.Dispose();
- }
- NametableBitmap = new DirectBitmap(512, 480);
- if (EMU.Cart == null)
- {
- return NametableBitmap.Bitmap;
- }
-
- int tx = 0;
- int ty = 0;
- int x = 0;
- int y = 0;
- int px = 0;
- int py = 0;
-
- int PatternTile;
- int pal = 0;
-
- bool ForceBackdropOnIndex0 = NametableViewer.UseBackdrop();
-
- while (ty < 2)
- {
- while (tx < 2)
- {
- while (y < 30)
- {
- while (x < 32)
- {
- PatternTile = EMU.ObservePPU((ushort)(0x2000 + 0x400 * tx + 0x800 * ty + x + y * 32));
- pal = EMU.ObservePPU((ushort)(0x2000 + 0x400 * (tx + 1) + 0x800 * ty - 0x40 + x / 4 + (y / 4) * 8));
- if ((x & 3) >= 2)
- {
- pal = pal >> 2;
- }
- if ((y & 3) >= 2)
- {
- pal = pal >> 4;
- }
- pal = pal & 3;
- while (py < 8)
- {
- while (px < 8)
- {
-
- int k = ((EMU.ObservePPU((ushort)(py + PatternTile * 16 + (!EMU.PPU_PatternSelect_Background ? 0 : 0x1000))) >> (7 - px)) & 1) + 2 * ((EMU.ObservePPU((ushort)(py + 8 + PatternTile * 16 + (!EMU.PPU_PatternSelect_Background ? 0 : 0x1000))) >> (7 - px)) & 1);
- if (k == 0 && ForceBackdropOnIndex0)
- {
- k = EMU.ObservePPU(0x3F00);
- }
- else
- {
- k = EMU.ObservePPU((ushort)(0x3F00 + k + pal * 4));
- }
- int col = unchecked((int)Emulator.NesPalInts[k & 0x3F]);
- NametableBitmap.SetPixel(tx * 0x100 + x * 8 + px, ty * 0xF0 + y * 8 + py, col);
- px++;
- }
- px = 0;
- py++;
- }
- py = 0;
- x++;
- }
-
- x = 0;
- y++;
- }
- y = 0;
- tx++;
- }
- tx = 0;
- ty++;
- }
-
- bool DrawScreenBoundary = NametableViewer.DrawBoundary();
- if (DrawScreenBoundary)
- {
- // convert the t register into X,Y coordinates
- /*
- The v and t registers are 15 bits:
- yyy NN YYYYY XXXXX
- ||| || ||||| +++++-- coarse X scroll
- ||| || +++++-------- coarse Y scroll
- ||| ++-------------- nametable select
- +++----------------- fine Y scroll
- */
- int X = ((EMU.PPU_t & 0b11111) << 3) | EMU.PPU_FineXScroll | ((EMU.PPU_t & 0b10000000000) >> 2);
- int Y = ((EMU.PPU_t & 0b1111100000) >> 2) | ((EMU.PPU_t & 0b111000000000000) >> 12) | ((EMU.PPU_t & 0b100000000000) >> 4);
- int i = 0;
- while (i <= 257)
- {
- NametableBitmap.SetPixel((X + 511 + i) & 511, (Y + 479) % 480, Color.White);
- NametableBitmap.SetPixel((X + 511 + i) & 511, (Y + 240) % 480, Color.White);
- i++;
- }
- i = 0;
- while (i <= 241)
- {
- NametableBitmap.SetPixel((X + 511) & 511, (Y + 479 + i) % 480, Color.White);
- NametableBitmap.SetPixel((X + 256) & 511, (Y + 479 + i) % 480, Color.White);
- i++;
- }
- }
- if (NametableViewer.OverlayScreen())
- {
- int X = ((EMU.PPU_t & 0b11111) << 3) | EMU.PPU_FineXScroll | ((EMU.PPU_t & 0b10000000000) >> 2);
- int Y = ((EMU.PPU_t & 0b1111100000) >> 2) | ((EMU.PPU_t & 0b111000000000000) >> 12) | ((EMU.PPU_t & 0b100000000000) >> 4);
- for (int xx = 0; xx < 256; xx++)
- {
- for (int yy = 0; yy < 240; yy++)
- {
- NametableBitmap.SetPixel((X + xx) & 511, (Y + yy) % 480, EMU.Screen.GetPixel(xx, yy));
- }
- }
- }
- return NametableBitmap.Bitmap;
- }
-
- string fds_bios; // file path to the FDS bios if one is loaded.
- public bool LoadROM(string FilePath)
- {
- if (FDS)
- {
- if (fds_bios == null || fds_bios.Length == 0)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\"))
- {
- InitDirectory += @"roms\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Title = "Select FDS BIOS",
- InitialDirectory = InitDirectory
- };
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- fds_bios = ofd.FileName;
- byte[] FDS_BIOS = File.ReadAllBytes(fds_bios);
- if (FDS_BIOS.Length != 0x2000)
- {
- fds_bios = "";
- return false;
- }
- }
- else
- {
- return false;
- }
- }
-
- Cartridge Cart = new Cartridge(filePath, fds_bios);
- EMU.Cart = Cart;
- Cart.Emu = EMU;
- return true;
- }
- else
- {
- Cartridge Cart = new Cartridge(filePath);
- EMU.Cart = Cart;
- Cart.Emu = EMU;
- return true;
- }
- return false;
- }
-
- public void InsertDisk(string filepath)
- {
- if(EMU.Cart.FDS != null)
- {
- EMU.Cart.FDS.InsertDisk(filepath);
- }
- }
-
- void ClockEmulator3CT(CancellationToken ct)
- {
- Cartridge[] CartArray = TASPropertiesForm3ct.CartridgeArray;
- int[] CyclesToSwapOn = TASPropertiesForm3ct.CyclesToSwapOn.ToArray();
- int[] CartsToSwapIn = TASPropertiesForm3ct.CartsToSwapIn.ToArray();
- EMU.Cart = CartArray[0];
-
- int i = 1; // what cycle is being executed next?
- int j = 0; // what step of the .3ct TAS is this?
- while (j < CyclesToSwapOn.Length)
- {
- if (i == CyclesToSwapOn[j]) // if there's a cart swap on this cycle
- {
- EMU.Cart = CartArray[CartsToSwapIn[j]]; // swap the cartridge to the next one in the list
- j++;
- }
- EMU._CoreCycleAdvance();
- i++;
- }
- // once the .3ct TAS is completed, continue running the emulator with whatever cartridge is loaded last.
- while (!ct.IsCancellationRequested)
- {
- RunUpkeep();
- EMU._CoreFrameAdvance();
- RunPostFramePhase();
- }
-
- }
-
- private void loadROMToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\"))
- {
- InitDirectory += @"roms\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Filter = "NES ROM files (*.nes)|*.nes",
- Title = "Select file",
- InitialDirectory = InitDirectory
- };
-
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
- filePath = ofd.FileName;
- FDS = Path.GetExtension(ofd.FileName) == ".fds";
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- EMU.PPUClock = settings_alignment;
- if (!LoadROM(filePath)) { return; }
- cancel = new CancellationTokenSource();
- EmuClock = new Thread(() => ClockEmulator(cancel.Token));
- EmuClock.SetApartmentState(ApartmentState.STA);
- EmuClock.IsBackground = true;
- EmuClock.Start();
- GC.Collect();
- }
- }
-
- private void loadTASToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Filter =
- "All TAS Files (.3c2, .3c3, .bk2, .tasproj, .fm2, .fm3, .fmv, .r08)|*.3c2;*.3c3;*.bk2;*.tasproj;*.fm2;*.fm3;*.fmv;*.r08" +
- "|TriCNES TAS File (.3c2, .3c3)|*.3c2;*.3c3" +
- "|Bizhawk Movie (.bk2)|*.bk2" +
- "|Bizhawk TAStudio (.tasproj)|*.tasproj" +
- "|FCEUX Movie (.fm2)|*.fm2" +
- "|FCEUX TAS Editor (.fm3)|*.fm3" +
- "|Famtastia Movie (.fmv)|*.fmv" +
- "|Replay Device (.r08)|*.r08",
- Title = "Select file",
- InitialDirectory = InitDirectory
- };
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- if (TASPropertiesForm != null)
- {
- TASPropertiesForm.Close();
- TASPropertiesForm.Dispose();
- }
- TASPropertiesForm = new TASProperties();
- TASPropertiesForm.TasFilePath = ofd.FileName;
- TASPropertiesForm.MainGUI = this;
- TASPropertiesForm.Init();
- TASPropertiesForm.Show();
- TASPropertiesForm.Location = Location;
- }
- }
-
- public void StartTAS()
- {
- if (filePath == "" || filePath == null)
- {
- MessageBox.Show("You need to select a ROM before running a TAS.");
- return;
- }
-
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
-
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
-
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
-
- if (!LoadROM(filePath)) { return; }
-
- EMU.TAS_ReadingTAS = true;
- EMU.TAS_InputLog = TASPropertiesForm.TasInputLog;
- EMU.TAS_ResetLog = TASPropertiesForm.TasResetLog;
- EMU.ClockFiltering = TASPropertiesForm.SubframeInputs();
- EMU.PPUClock = TASPropertiesForm.GetPPUClockPhase();
- EMU.CPUClock = TASPropertiesForm.GetCPUClockPhase();
- EMU.TAS_InputSequenceIndex = 0;
- switch (TASPropertiesForm.extension)
- {
- case ".bk2":
- case ".tasproj":
- {
- int i = 0;
- while (i < EMU.RAM.Length) //bizhawk RAM pattern
- {
- if ((i & 7) > 4)
- {
- EMU.RAM[i] = 0xFF;
- }
- else
- {
- EMU.RAM[i] = 0;
- }
- i++;
- }
- }
- break;
- case ".fm2":
- case ".fm3":
- {
- if (TASPropertiesForm.UseFCEUXFrame0Timing())
- {
- // FCEUX incorrectly starts at the beginning of scanline 240, and cycle 0 is *after* the reset instruction.
- // However, I think there's some other incorrect timing going on with FCEUX, and in order to sync TASes, I need to start at scanline 239, dot 312
- EMU.PPU_Scanline = 239;
- EMU.PPU_Dot = 312;
- // but of course, by starting here, the VBlank flag will be incorrectly set early.
- EMU.SyncFM2 = true; // so this bool prevents that.
- EMU.TAS_InputSequenceIndex--; // since this runs an extra vblank, this needs to be offset by 1
- }
- else
- {
- EMU.TAS_InputSequenceIndex++;
- EMU.PPU_Dot = 0;
- }
- // FCEUX also starts with this RAM pattern
- int i = 0;
- while (i < EMU.RAM.Length) //bizhawk RAM pattern
- {
- if ((i & 7) > 4)
- {
- EMU.RAM[i] = 0xFF;
- }
- else
- {
- EMU.RAM[i] = 0;
- }
- i++;
- }
- }
- break;
- case ".r08":
- {
- // This following comment block can be removed if you want to set up RAM for the Bad Apple TAS's .r08 file.
- /*
- string s = "0000000000000C000000000000000000E2000000001D1E000000000001000000984820BEFE68A8A5F7A6F8600000000010400000000000000000000000000000A2A58EFF07A216EA8EFD07020000000020200091318A11319131C8C430D0F14C40000000000000000101030000000000000000000000000000000000000000000000000000F000000000020000A0A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101000000000000000000000000000100000000000000000000000000000000000035000000008E002001008A4820BEFE68AA0C000000004C4000000001A804D9B4B4070004DAB4B4030004DBB4B4030005DCB4B4030004DDB4B4030004DEB4B4030004DFB4B4030004E0B4B4030004E1B4B4030004E2B4B4030004E3B4B4030004E4B4B4030004E5B4B4030004E6B4C886A080F5D000D00B00003F2FC7F8C8FE0024000F5200FB0400A9018D164085C04A8D1640AD16404A26C090F8A5C060A202206B0195C1CA10F8A000206B0191C2C8C4C190F6206B01F0E5206B0185C3206B0185C26CC200FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB00FB003AFB00FB00FB00FB10D2A27DA07DF50400040004D93525D8F70000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8410000F8410000F8250000F8250000F8410000F8410000F8250000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000F8010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000787A2021047F1918470000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F722CC891000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600A5";
- int i = 0;
- while (i < 0x800)
- {
- EMU.RAM[i] = byte.Parse(s.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
- i++;
- }
- */
- break;
- }
- }
-
- cancel = new CancellationTokenSource();
- EmuClock = new Thread(() => ClockEmulator(cancel.Token));
- EmuClock.SetApartmentState(ApartmentState.STA);
- EmuClock.IsBackground = true;
- EmuClock.Start();
- GC.Collect();
- }
-
- public void Start3CTTAS()
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
- if (TASPropertiesForm3ct.FromRESET())
- {
- if (EMU == null)
- {
- MessageBox.Show("The emulator needs to be powered on before running from RESET.");
- return;
- }
- EMU.ResetButton();
- }
- else
- {
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- EMU.PPUClock = settings_alignment;
- }
- foreach(Cartridge c in TASPropertiesForm3ct.CartridgeArray)
- {
- c.Emu = EMU;
- }
- cancel = new CancellationTokenSource();
- EmuClock = new Thread(() => ClockEmulator3CT(cancel.Token));
- EmuClock.IsBackground = true;
- EmuClock.Start();
- }
-
- private void load3ctToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Filter =
- "3CT TAS Files (.3ct)|*.3ct",
- Title = "Select file",
- InitialDirectory = InitDirectory
- };
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- if (TASPropertiesForm3ct != null)
- {
- TASPropertiesForm3ct.Close();
- TASPropertiesForm3ct.Dispose();
- }
- TASPropertiesForm3ct = new TASProperties3ct();
- TASPropertiesForm3ct.TasFilePath = ofd.FileName;
- TASPropertiesForm3ct.MainGUI = this;
- TASPropertiesForm3ct.Init();
- TASPropertiesForm3ct.Show();
- TASPropertiesForm3ct.Location = Location;
- }
- }
-
- private void resetToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (EMU != null)
- {
- EMU.ResetButton();
- }
- }
-
- private void powerCycleToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (EMU != null)
- {
- Emulator Emu2 = new Emulator();
- Emu2.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- Emu2.PPU_ShowScreenBorders = settings_border;
- Emu2.PPUClock = settings_alignment;
- Emu2.Cart = EMU.Cart;
- Emu2.Cart.Emu = Emu2;
- EMU = Emu2;
- if (!LoadROM(filePath)) { return; }
- EMU.ResetButton();
- }
- }
-
- bool PendingScreenshot;
- private void screenshotToolStripMenuItem_Click(object sender, EventArgs e)
- {
- PendingScreenshot = true;
- }
-
- private void pb_Screen_DragEnter(object sender, DragEventArgs e)
- {
- var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
- if (Path.GetExtension(filenames[0]) == ".nes" || Path.GetExtension(filenames[0]) == ".NES" || Path.GetExtension(filenames[0]) == ".fds" || Path.GetExtension(filenames[0]) == ".FDS") e.Effect = DragDropEffects.All;
- else e.Effect = DragDropEffects.None;
- }
-
- private void pb_Screen_DragDrop(object sender, DragEventArgs e)
- {
- var filenames = (string[])e.Data.GetData(DataFormats.FileDrop, false);
- string filename = filenames[0];
- filePath = filename;
- bool prev_FDS = FDS;
- FDS = Path.GetExtension(filePath).ToLower() == ".fds";
-
- if (!FDS || !prev_FDS)
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
-
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
- }
- if (FDS && prev_FDS)
- {
- InsertDisk(filePath);
- }
- else
- {
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- EMU.PPUClock = settings_alignment;
-
- if (!LoadROM(filePath)) { return; }
-
- cancel = new CancellationTokenSource();
- EmuClock = new Thread(() => ClockEmulator(cancel.Token));
- EmuClock.SetApartmentState(ApartmentState.STA);
- EmuClock.IsBackground = true;
- EmuClock.Start();
- }
- GC.Collect();
-
- }
- private void TriCNESGUI_Closing(Object sender, FormClosingEventArgs e)
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
- if (TASPropertiesForm != null)
- {
- TASPropertiesForm.Dispose();
- }
- if (TASPropertiesForm3ct != null)
- {
- TASPropertiesForm3ct.Dispose();
- }
- if (TraceLogger != null)
- {
- TraceLogger.Dispose();
- }
- if (NametableViewer != null)
- {
- NametableViewer.Dispose();
- }
- if(TasTimeline != null)
- {
- TasTimeline.Dispose();
- }
- if (HexEditor != null)
- {
- HexEditor.Dispose();
- }
- if (CartConnector != null)
- {
- CartConnector.Dispose();
- }
- Application.Exit();
- }
-
- private void phase0ToolStripMenuItem_Click(object sender, EventArgs e)
- {
- phase0ToolStripMenuItem.Checked = true;
- phase1ToolStripMenuItem.Checked = false;
- phase2ToolStripMenuItem.Checked = false;
- phase3ToolStripMenuItem.Checked = false;
- RebootWithAlignment(0);
- }
-
- private void phase1ToolStripMenuItem_Click(object sender, EventArgs e)
- {
- phase0ToolStripMenuItem.Checked = false;
- phase1ToolStripMenuItem.Checked = true;
- phase2ToolStripMenuItem.Checked = false;
- phase3ToolStripMenuItem.Checked = false;
- RebootWithAlignment(1);
- }
-
- private void phase2ToolStripMenuItem_Click(object sender, EventArgs e)
- {
- phase0ToolStripMenuItem.Checked = false;
- phase1ToolStripMenuItem.Checked = false;
- phase2ToolStripMenuItem.Checked = true;
- phase3ToolStripMenuItem.Checked = false;
- RebootWithAlignment(2);
- }
-
- private void phase3ToolStripMenuItem_Click(object sender, EventArgs e)
- {
- phase0ToolStripMenuItem.Checked = false;
- phase1ToolStripMenuItem.Checked = false;
- phase2ToolStripMenuItem.Checked = false;
- phase3ToolStripMenuItem.Checked = true;
- RebootWithAlignment(3);
- }
-
- private void RebootWithAlignment(byte Alignment)
- {
- if (EMU != null)
- {
- Emulator Emu2 = new Emulator();
- Emu2.Cart = EMU.Cart;
- Emu2.Cart.Emu = Emu2;
- EMU = Emu2;
- EMU.PPUClock = Alignment;
- EMU.CPUClock = 0;
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- }
- settings_alignment = Alignment;
- }
-
- private void trueToolStripMenuItem_Click(object sender, EventArgs e)
- {
- falseToolStripMenuItem.Checked = false;
- showRawSignalsToolStripMenuItem.Checked = false;
- trueToolStripMenuItem.Checked = true;
- if (EMU != null)
- {
- EMU.PPU_DecodeSignal = true;
- EMU.PPU_ShowRawNTSCSignal = false;
- }
- settings_ntsc = true;
- settings_ntscRaw = false;
- }
-
- private void falseToolStripMenuItem_Click(object sender, EventArgs e)
- {
- trueToolStripMenuItem.Checked = false;
- showRawSignalsToolStripMenuItem.Checked = false;
- falseToolStripMenuItem.Checked = true;
- if (EMU != null)
- {
- EMU.PPU_DecodeSignal = false;
- EMU.PPU_ShowRawNTSCSignal = false;
- }
- settings_ntsc = false;
- settings_ntscRaw = false;
- }
-
- private void showRawSignalsToolStripMenuItem_Click(object sender, EventArgs e)
- {
- trueToolStripMenuItem.Checked = false;
- showRawSignalsToolStripMenuItem.Checked = true;
- falseToolStripMenuItem.Checked = false;
- if (EMU != null)
- {
- EMU.PPU_DecodeSignal = true;
- EMU.PPU_ShowRawNTSCSignal = true;
- }
- settings_ntsc = true;
- settings_ntscRaw = true;
- }
-
- public void ResizeWindow(int scale)
- {
- int w = 256;
- int h = 240;
- if (EMU != null)
- {
- if (EMU.PPU_ShowScreenBorders)
- {
- w = 341;
- h = 262;
- }
- }
-
- Size pbs = new Size();
- pbs.Width = w * scale;
- pbs.Height = h * scale;
- Size ws = new Size();
- ws.Width = w * scale + 16;
- ws.Height = h * scale + 66;
- MinimumSize = ws;
- MaximumSize = ws;
- pb_Screen.Size = pbs;
- Width = ws.Width;
- Height = ws.Height;
- }
-
- int ScreenMult = 1;
- private void xToolStripMenuItem_Click(object sender, EventArgs e)
- {
- ScreenMult = 1;
- ResizeWindow(1);
- }
-
- private void xToolStripMenuItem1_Click(object sender, EventArgs e)
- {
- ScreenMult = 2;
- ResizeWindow(2);
- }
-
- private void xToolStripMenuItem2_Click(object sender, EventArgs e)
- {
- ScreenMult = 3;
- ResizeWindow(3);
- }
-
- private void xToolStripMenuItem3_Click(object sender, EventArgs e)
- {
- ScreenMult = 4;
- ResizeWindow(4);
- }
-
- private void xToolStripMenuItem4_Click(object sender, EventArgs e)
- {
- ScreenMult = 5;
- ResizeWindow(5);
- }
-
- private void xToolStripMenuItem5_Click(object sender, EventArgs e)
- {
- ScreenMult = 6;
- ResizeWindow(6);
- }
-
- private void xToolStripMenuItem6_Click(object sender, EventArgs e)
- {
- ScreenMult = 7;
- ResizeWindow(7);
- }
-
- private void xToolStripMenuItem7_Click(object sender, EventArgs e)
- {
- ScreenMult = 8;
- ResizeWindow(8);
- }
-
- private void traceLoggerToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if(TraceLogger != null)
- {
- TraceLogger.Focus();
- return;
- }
- TraceLogger = new TriCTraceLogger();
- TraceLogger.MainGUI = this;
- TraceLogger.Init();
- TraceLogger.Show();
- TraceLogger.Location = Location;
- }
- bool Pending_ShowScreenBorders;
- private void trueToolStripMenuItem1_Click(object sender, EventArgs e)
- {
- toolstrip_ViewBorders_False.Checked = false;
- toolstrip_ViewBorders_True.Checked = true;
- if (EMU != null)
- {
- Pending_ShowScreenBorders = true;
- }
- settings_border = true;
- }
- bool Pending_HideScreenBorders;
- private void falseToolStripMenuItem1_Click(object sender, EventArgs e)
- {
- toolstrip_ViewBorders_False.Checked = true;
- toolstrip_ViewBorders_True.Checked = false;
- if (EMU != null)
- {
- Pending_HideScreenBorders = true;
- }
- settings_border = false;
- }
-
- private void nametableViewerToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if(NametableViewer != null)
- {
- NametableViewer.Focus();
- return;
- }
- NametableViewer = new TriCNTViewer();
- NametableViewer.MainGUI = this;
- NametableViewer.Show();
- NametableViewer.Location = Location;
- }
-
- private void hexEditorToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (HexEditor != null)
- {
- HexEditor.Focus();
- return;
- }
- HexEditor = new TriCHexEditor();
- HexEditor.MainGUI = this;
- HexEditor.Show();
- HexEditor.Location = Location;
- }
-
- List Savestate = new List();
- bool PendingSaveState = false;
- private void saveStateToolStripMenuItem_Click(object sender, EventArgs e)
- {
- PendingSaveState = true;
- }
- bool PendingLoadState = false;
- private void loadStateToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (TasTimeline == null) // this would cause a desync otherwise, so forcefully prevent this.
- {
- PendingLoadState = true;
- }
- }
-
- private void tASTimelineToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if(TasTimeline != null)
- {
- TasTimeline.Focus();
- return;
- }
- bool EMUExists = (EMU != null);
- if (!EMUExists)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"roms\"))
- {
- InitDirectory += @"roms\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Filter = "NES ROM files (*.nes)|*.nes",
- Title = "Select file",
- InitialDirectory = InitDirectory
- };
-
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
- filePath = ofd.FileName;
- FDS = Path.GetExtension(ofd.FileName) == ".fds";
- TasTimeline = new TriCTASTimeline(this);
- TasTimeline.Show();
- TasTimeline.Location = Location;
- }
- }
- else
- {
- TasTimeline = new TriCTASTimeline(this);
- TasTimeline.Show();
- TasTimeline.Location = Location;
- }
- }
-
- public List ParseTasFile(string TasFilePath, out List Resets)
- {
- // determine file type
- string extension = Path.GetExtension(TasFilePath);
- // create list of inputs from the tas file, and make any settings changes if needed.
- byte[] ByteArray = File.ReadAllBytes(TasFilePath);
- List TASInputs = new List(); // Low byte is player 1, High byte is player 2.
- List TASResets = new List();
-
- switch (extension)
- {
- case ".bk2":
- case ".tasproj":
- {
- // .bk2 files are actually just .zip files!
- // Let's yoink "Input Log.txt" from this .bk2 file
- StringReader InputLog = new StringReader(new string(new StreamReader(ZipFile.OpenRead(TasFilePath).Entries.Where(x => x.Name.Equals("Input Log.txt", StringComparison.InvariantCulture)).FirstOrDefault().Open(), Encoding.UTF8).ReadToEnd().ToArray()));
- // now to parse the input log!
- InputLog.ReadLine(); // "[Input]"
- string key = InputLog.ReadLine(); // "LogKey: ... "
- bool Bk2_Port1 = key.Contains("P1");
- bool Bk2_Port2 = key.Contains("P2");
- string ln = InputLog.ReadLine();
- ushort u = 0;
- while (ln != null && ln.Length > 3)
- {
- int pipeIndex = ln.Substring(1, ln.Length - 1).IndexOf('|') + 1;
- char[] lnCharArray = ln.ToCharArray();
- bool reset = lnCharArray[pipeIndex - 1] == 'r';
- u = 0;
- if (Bk2_Port1)
- {
- u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x08 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x04 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x02 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x01 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x10 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x20 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x40 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x80 : 0);
- }
- else if (Bk2_Port2) // Are there any NES TASes that only feature controller 2?
- {
- u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x0800 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x0400 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x0200 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x0100 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x1000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x2000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x4000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x8000 : 0);
- }
- if (Bk2_Port1 && Bk2_Port2)
- {
- pipeIndex = ln.Substring(pipeIndex + 1, ln.Length - 1 - pipeIndex).IndexOf('|') + pipeIndex + 1;
- u |= (ushort)(lnCharArray[pipeIndex + 1] == 'U' ? 0x0800 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 2] == 'D' ? 0x0400 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 3] == 'L' ? 0x0200 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 4] == 'R' ? 0x0100 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 5] == 'S' ? 0x1000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 6] == 's' ? 0x2000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 7] == 'B' ? 0x4000 : 0);
- u |= (ushort)(lnCharArray[pipeIndex + 8] == 'A' ? 0x8000 : 0);
- }
- TASInputs.Add(u);
- TASResets.Add(reset);
- ln = InputLog.ReadLine();
- if (ln == "[/Input]")
- {
- break;
- }
- }
- }
- break;
- case ".fm2":
- {
- // change the alignment to use FCEUX's
- // header info of varying size
- // Every line of a header ends in $0A
- // Every header section is named. Example: $0A "romFileName"
- // Since the input log begins with "|" and none of the header section names begin with "|", I can assume $0A"|" is the start of the input log
- bool fm2_UsePort0 = false;
- bool fm2_UsePort1 = false;
-
- int i = 0;
- while (i < ByteArray.Length)
- {
- // parse for "port0 ?"
- if (ByteArray[i] == 0x0A &&
- ByteArray[i + 1] == 0x70 &&
- ByteArray[i + 2] == 0x6F &&
- ByteArray[i + 3] == 0x72 &&
- ByteArray[i + 4] == 0x74 &&
- ByteArray[i + 5] == 0x30 &&
- ByteArray[i + 6] == 0x20
- )
- {
- fm2_UsePort0 = ByteArray[i + 7] == 0x31;
- }
- // parse for "port1 ?"
- if (ByteArray[i] == 0x0A &&
- ByteArray[i + 1] == 0x70 &&
- ByteArray[i + 2] == 0x6F &&
- ByteArray[i + 3] == 0x72 &&
- ByteArray[i + 4] == 0x74 &&
- ByteArray[i + 5] == 0x31 &&
- ByteArray[i + 6] == 0x20
- )
- {
- fm2_UsePort1 = ByteArray[i + 7] == 0x31;
- }
-
- if (ByteArray[i] == 0x0A && ByteArray[i + 1] == 0x7C)
- {
- break;
- }
- i++;
- }
-
- ushort u = 0;
-
- int Port0Index = 0;
- int Port1Index = 0;
-
- while (i < ByteArray.Length)
- {
- if (ByteArray[i] == 0x0A)
- {
- if (i == ByteArray.Length - 1)
- {
- break;
- }
- if (ByteArray[i + 1] == 0x0A)
- {
- // The .fm2 TAS file format supports empty rows. Formatting quirk?
- i++;
- continue;
- }
- if (ByteArray[i + 1] == 0x23)
- {
- // The .fm2 TAS file format supports comments, in the following format:
- //\n### Comment
- // so basically, check for `#` as the next character.
-
- // And now we skip until the next new line.
- i++;
- continue;
- }
- bool reset = (ByteArray[i + 2] & 1) == 1;
- if (fm2_UsePort0)
- {
- Port0Index = i + 4;
- if (fm2_UsePort1)
- {
- Port1Index = i + 0xD;
- }
- }
- else if (fm2_UsePort1)
- {
- Port1Index = i + 0x6;
- }
-
- u = 0;
- if (fm2_UsePort0)
- {
- u |= (ushort)(ByteArray[Port0Index] == 0x2E ? 0 : 1);
- u |= (ushort)(ByteArray[Port0Index + 1] == 0x2E ? 0 : 2);
- u |= (ushort)(ByteArray[Port0Index + 2] == 0x2E ? 0 : 4);
- u |= (ushort)(ByteArray[Port0Index + 3] == 0x2E ? 0 : 8);
- u |= (ushort)(ByteArray[Port0Index + 4] == 0x2E ? 0 : 0x10);
- u |= (ushort)(ByteArray[Port0Index + 5] == 0x2E ? 0 : 0x20);
- u |= (ushort)(ByteArray[Port0Index + 6] == 0x2E ? 0 : 0x40);
- u |= (ushort)(ByteArray[Port0Index + 7] == 0x2E ? 0 : 0x80);
- }
- if (fm2_UsePort1)
- {
- u |= (ushort)(ByteArray[Port1Index] == 0x2E ? 0 : 0x100);
- u |= (ushort)(ByteArray[Port1Index + 1] == 0x2E ? 0 : 0x200);
- u |= (ushort)(ByteArray[Port1Index + 2] == 0x2E ? 0 : 0x400);
- u |= (ushort)(ByteArray[Port1Index + 3] == 0x2E ? 0 : 0x800);
- u |= (ushort)(ByteArray[Port1Index + 4] == 0x2E ? 0 : 0x1000);
- u |= (ushort)(ByteArray[Port1Index + 5] == 0x2E ? 0 : 0x2000);
- u |= (ushort)(ByteArray[Port1Index + 6] == 0x2E ? 0 : 0x4000);
- u |= (ushort)(ByteArray[Port1Index + 7] == 0x2E ? 0 : 0x8000);
- }
- TASInputs.Add(u);
- TASResets.Add(reset);
-
- }
- i++;
- }
- }
- break;
- case ".fm3":
- {
- // similar to fm2, this has a header of varying length.
- // But it also contains significantly more metadata after the input log.
- // we need to parse $0A"length "
- bool fm3_UsePort0 = false;
- bool fm3_UsePort1 = false;
- int i = 0;
- while (i < ByteArray.Length)
- {
- if (ByteArray[i] == 0x0A)
- {
- if (ByteArray[i] == 0x0A &&
- ByteArray[i + 1] == 0x70 &&
- ByteArray[i + 2] == 0x6F &&
- ByteArray[i + 3] == 0x72 &&
- ByteArray[i + 4] == 0x74 &&
- ByteArray[i + 5] == 0x30 &&
- ByteArray[i + 6] == 0x20
- )
- {
- fm3_UsePort0 = ByteArray[i + 7] == 0x31;
- }
- // parse for "port1 ?"
- if (ByteArray[i] == 0x0A &&
- ByteArray[i + 1] == 0x70 &&
- ByteArray[i + 2] == 0x6F &&
- ByteArray[i + 3] == 0x72 &&
- ByteArray[i + 4] == 0x74 &&
- ByteArray[i + 5] == 0x31 &&
- ByteArray[i + 6] == 0x20
- )
- {
- fm3_UsePort1 = ByteArray[i + 7] == 0x31;
- }
- // check if this is the header info for "length"
- if (ByteArray[i] == 0x0A)
- {
- if (ByteArray[i + 1] == 0x6C &&
- ByteArray[i + 2] == 0x65 &&
- ByteArray[i + 3] == 0x6E &&
- ByteArray[i + 4] == 0x67 &&
- ByteArray[i + 5] == 0x74 &&
- ByteArray[i + 6] == 0x68 &&
- ByteArray[i + 7] == 0x20)
- {
- // okay, so the length is in ascii...
- // let's figure out where the next $0A character is
- int next0A = i + 8;
- while (next0A < ByteArray.Length)
- {
- if (ByteArray[next0A] == 0x0A)
- {
- break;
- }
- next0A++;
- }
- // okay, so the string from i+8 though next0A is the length.
- byte[] StringArray = new byte[next0A - (i + 8)];
- Array.Copy(ByteArray, i + 8, StringArray, 0, StringArray.Length);
- int InputLogLength = int.Parse(Encoding.Default.GetString(StringArray));
- i = next0A + 2;
- int tempMul = 1;
- if (fm3_UsePort0) { tempMul++; }
- if (fm3_UsePort1) { tempMul++; }
- int InputLogByteLength = InputLogLength * tempMul;
- // first byte is always zero?
- // next byte is controller 1 (if enabled)
- // next byte is controller 2 (if enabled)
- ushort u = 0;
- while (i < next0A + 2 + InputLogByteLength)
- {
- bool reset = (ByteArray[i] & 1) == 1;
- i++;// dummy byte (?)
- u = 0;
- if (fm3_UsePort0) { u = ByteArray[i]; i++; }
- if (fm3_UsePort1) { u |= (ushort)(ByteArray[i] << 8); i++; }
- TASInputs.Add(u);
- TASResets.Add(reset);
- }
-
- }
-
- }
-
- }
- i++;
-
- }
-
-
-
- }
- break;
- case ".fmv":
- {
- int i = 0x90; // there's a 144 byte header
- bool fmv_UseController2 = (ByteArray[5] & 0b00010000) != 0;
- if (fmv_UseController2)
- {
- while (i < ByteArray.Length)
- {
- ushort u = (ushort)(FamtasiaInput2Standard(ByteArray[i]) | (FamtasiaInput2Standard(ByteArray[i + 1]) << 8));
- TASInputs.Add(u);
- i += 2;
- }
- }
- else
- {
- while (i < ByteArray.Length)
- {
- TASInputs.Add(FamtasiaInput2Standard(ByteArray[i]));
- i++;
- }
- }
- }
- break;
- case ".3c2":
- {
- // The .3c2 format is pretty much identical to the .r08 file format, but with a 1-byte header.
- // Bit 0: 0 = Latch Filtering. 1 = Clock Filtering.
- // Bit 1: 0 = Only controller 1. 1 = Controller 1 and controller 2.
- // Bit 2: 0 = No reset button. 1 = The reset button is used in this TAS.
-
- bool UseController2 = (ByteArray[0] & 2) != 0;
- bool UseReset = (ByteArray[0] & 4) != 0;
-
- byte b = 0;
- byte b2 = 0;
- int i = 1;
- while (i < ByteArray.Length)
- {
- b = ByteArray[i];
- i++;
- if (UseController2)
- {
- b2 = ByteArray[i];
- i++;
- }
- TASInputs.Add((ushort)(b | (b2 << 8)));
- if (UseReset)
- {
- bool res = (ByteArray[i] & 0x80) == 0x80; // I use bit 7 for the reset button. (bit 0 is for lag frames in the .3c3 format.)
- TASResets.Add(res);
- i++;
- }
- }
- TASInputs.Add(0); // append a zero to the end for safe measure.
- }
- break;
- case ".r08":
- {
- // the .r08 file format is conveniently already in the format I want for my emulator.
- byte b = 0;
- byte b2 = 0;
- int i = 0;
- while (i < ByteArray.Length)
- {
- b = ByteArray[i];
- b2 = ByteArray[i + 1];
- TASInputs.Add((ushort)(b | (b2 << 8)));
- i += 2;
- }
- TASInputs.Add(0); // append a zero to the end for safe measure.
- }
- break;
- case ".3c3":
- {
- // .3c3 is the format for my TAS timeline.
- // The big differences are:
- // - .3c3 saves the savestate information
- // - .3c3 saves the "lag frame" information as well. (So every frame is 3 bytes now.)
-
- // .3c3 has a 16 byte header.
- // It's just little-endian 32-bit integers, and the same 1-byte header used in .3c2's.
- // The first one determines how many bytes are in every savestate.
- // the second one determinines how many frames there are in this TAS.
- // I guess that means there's a limit of 2,147,483,647 frames in a .3c3 TAS file. God help me if I ever feel compelled to challenge this.
- // Then there's a handful of unused bytes. ByteArray[15] is the same format as the 1-byte header used in 3c2's.
- // Bit 0: 0 = Latch Filtering. 1 = Clock Filtering.
- // Bit 1: 0 = Only controller 1. 1 = Controller 1 and controller 2.
- // Bit 2: 0 = No reset button. 1 = The reset button is used in this TAS.
-
-
- int SavestateLength = ByteArray[0] | (ByteArray[1] << 8) | (ByteArray[2] << 16) | (ByteArray[3] << 24);
- int rerecords = ByteArray[4] | (ByteArray[5] << 8) | (ByteArray[6] << 16) | (ByteArray[7] << 24);
- int frameCount = ByteArray[8] | (ByteArray[9] << 8) | (ByteArray[10] << 16) | (ByteArray[11] << 24);
-
- bool UseController2 = (ByteArray[15] & 2) != 0;
- bool UseReset = (ByteArray[15] & 4) != 0;
-
- List> saveStates = new List>();
- List> saveStates2 = new List>();
- List lagFrames = new List();
-
- int FrameCountMult = 2;
- if (UseController2)
- {
- FrameCountMult++;
- }
-
- byte b = 0;
- byte b2 = 0;
- int i = 16;
- while (i < frameCount * FrameCountMult + 16)
- {
- b = ByteArray[i];
- i++;
- if (UseController2)
- {
- b2 = ByteArray[i];
- i++;
- }
- TASInputs.Add((ushort)(b | (b2 << 8)));
- bool lagframe = (ByteArray[i] & 1) == 1; // I use bit 0 for the lag frame info.
- lagFrames.Add(lagframe);
- if (UseReset)
- {
- bool res = (ByteArray[i] & 0x80) == 0x80; // I use bit 7 for the reset button.
- TASResets.Add(res);
- }
- i++;
- saveStates.Add(new List());
- saveStates2.Add(new List());
-
- }
-
- // and from here until you reach the end of the file, the data is arranged in the following format:
- // [32-bit int declaring the frame number, and 'n' bytes for the save state at that frame.]
-
- while (i < ByteArray.Length)
- {
- // read the 4 byte header.
- int frameIndex = ByteArray[i] | (ByteArray[i + 1] << 8) | (ByteArray[i + 2] << 16) | (ByteArray[i + 3] << 24);
- i += 4;
- int j = 0;
- while (j < SavestateLength)
- {
- saveStates[frameIndex].Add(ByteArray[i]);
- i++;
- j++;
- }
- }
-
- if (TasTimeline != null)
- {
- TriCTASTimeline.TimelineSavestates = saveStates;
- TriCTASTimeline.TimelineTempSavestates = saveStates2; // the empty list.
- TriCTASTimeline.LagFrames = lagFrames;
- TasTimeline.highestFrameEmulatedEver = frameCount - 1;
- TasTimeline.frameEmulated = frameCount - 1;
- TasTimeline.Rerecords = rerecords;
- }
- }
- break;
- // TODO: ask if the .tasd file format is a thing yet
- }
- if (TASResets.Count == 0) // If not using Resets, we still want to initialize the Resets list, in case they are added to the TAS timeline at a later point.
- {
- TASResets = new List(new bool[TASInputs.Count]);
- }
-
- Resets = TASResets;
- return TASInputs;
- }
- byte FamtasiaInput2Standard(byte input)
- {
- //famtasia format is SsABDULR
- byte b0 = (byte)(input & 0x8);
- byte b1 = (byte)(input & 0x4);
- byte b2 = (byte)(input & 0xC0);
- byte b3 = (byte)(input & 0x30);
- b0 >>= 1;
- b1 <<= 1;
- byte b4 = (byte)(b2 & 0x80);
- byte b5 = (byte)(b2 & 0x40);
- b4 >>= 1;
- b5 <<= 1;
- b2 = (byte)(b4 | b5);
- b2 >>= 2;
- b3 <<= 2;
- byte b = (byte)(b2 | b3 | b0 | b1 | (input & 0x3));
- return b;
- }
-
- public void CreateTASTimelineEmulator()
- {
- if (EmuClock != null)
- {
- cancel.Cancel();
- EmuClock.Join();
- }
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
- Timeline_Paused = true;
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- EMU.PPUClock = settings_alignment;
- if (!LoadROM(filePath)) { return; }
-
- cancel = new CancellationTokenSource();
- EmuClock = new Thread(() => ClockTimelineEmulator(cancel.Token));
- EmuClock.SetApartmentState(ApartmentState.STA);
- EmuClock.IsBackground = true;
- EmuClock.Start();
- }
-
- public bool Timeline_PendingPause;
- public bool Timeline_PendingResume;
- public bool Timeline_Paused;
- public bool Timeline_PendingFrameAdvance;
- public bool Timeline_PendingLoadState;
- public int Timeline_PendingFrameNumber;
- public bool Timeline_PendingHardReset;
- public bool Timeline_PendingArbitrarySavestate;
- public bool Timeline_AutoPlayUntilTarget;
- public int Timeline_AutoPlayTarget;
- public bool Timeline_PendingMouseDown;
- public bool Timeline_PendingMouseHeld;
- public bool Timeline_PendingResetScreen;
- public bool Timeline_PendingClockFiltering;
-
- public bool Timeline_HotkeyHeld_RShoulder;
- public bool Timeline_HotkeyHeld_RTrigger;
- public bool Timeline_HotkeyHeld_LTrigger;
-
- public List Timeline_LoadState;
- void ClockTimelineEmulator(CancellationToken ct)
- {
-
- while (!ct.IsCancellationRequested)
- {
- if (Timeline_PendingHardReset)
- {
- Timeline_PendingHardReset = false;
- if (EMU != null)
- {
- EMU.Dispose();
- GC.Collect();
- }
- EMU = new Emulator();
- EMU.PPU_DecodeSignal = settings_ntsc;
- EMU.PPU_ShowRawNTSCSignal = settings_ntscRaw;
- EMU.PPU_ShowScreenBorders = settings_border;
- EMU.PPUClock = settings_alignment;
- if (!LoadROM(filePath)) { return; }
-
- if (Timeline_PendingClockFiltering)
- {
- Timeline_PendingClockFiltering = false;
- EMU.TASTimelineClockFiltering = true;
- }
- }
- if (Timeline_PendingArbitrarySavestate)
- {
- Timeline_PendingArbitrarySavestate = false; // pretty much only ever set when loading a TAS.
- List state = EMU.SaveState();
- TriCTASTimeline.TimelineSavestates.Add(state);
- TriCTASTimeline.TimelineTempSavestates.Add(new List());
- }
-
- bool[] ControllerHotkeys = OtherControllerHotkeys();
-
- if (!Timeline_HotkeyHeld_RShoulder && ControllerHotkeys[0])
- {
- Timeline_PendingPause = !Timeline_Paused;
- Timeline_PendingResume = Timeline_Paused;
- }
-
- if ((ControllerHotkeys[2] && ControllerHotkeys[1]) || (!Timeline_HotkeyHeld_RTrigger && ControllerHotkeys[2]))
- {
- Timeline_PendingFrameAdvance = true;
- Timeline_PendingPause = !Timeline_Paused;
- }
- bool rewinding = false;
- if ((ControllerHotkeys[3] && ControllerHotkeys[1]) || (!Timeline_HotkeyHeld_LTrigger && ControllerHotkeys[3]))
- {
- TasTimeline.FrameRewind();
- Timeline_PendingPause = !Timeline_Paused;
- rewinding = true;
- }
-
- Timeline_HotkeyHeld_RShoulder = ControllerHotkeys[0];
- Timeline_HotkeyHeld_RTrigger = ControllerHotkeys[2];
- Timeline_HotkeyHeld_LTrigger = ControllerHotkeys[3];
-
- if (Timeline_PendingPause)
- {
- Timeline_PendingPause = false;
- Timeline_Paused = true;
- TasTimeline.ChangePlayPauseButtonText("Paused");
- }
- if (Timeline_PendingResume)
- {
- Timeline_PendingResume = false;
- Timeline_Paused = false;
- TasTimeline.ChangePlayPauseButtonText("Running");
- }
- if (Timeline_PendingMouseDown)
- {
- Timeline_PendingMouseDown = false;
- TasTimeline.TimelineMouseDownEvent();
- }
- if (Timeline_PendingMouseHeld)
- {
- Timeline_PendingMouseHeld = false;
- TasTimeline.TimelineMouseHeldEvent();
- }
- if (Timeline_PendingLoadState)
- {
- Timeline_PendingLoadState = false;
- PendingLoadState = true;
- Savestate = Timeline_LoadState;
- TasTimeline.frameIndex = Timeline_PendingFrameNumber;
- }
- if (Timeline_PendingResetScreen)
- {
- Timeline_PendingResetScreen = false;
- pb_Screen.Invoke(new MethodInvoker(
- delegate ()
- {
- Bitmap b = new Bitmap(pb_Screen.Image);
- for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { b.SetPixel(x, y, Color.Black); } }
- pb_Screen.Image = b;
- pb_Screen.Update();
- }));
- }
- bool FrameAdvance = false;
- if (Timeline_PendingFrameAdvance)
- {
- Timeline_PendingFrameAdvance = false;
- FrameAdvance = true;
- }
-
- if (Timeline_AutoPlayUntilTarget && TasTimeline.frameIndex >= Timeline_AutoPlayTarget)
- {
- Timeline_AutoPlayUntilTarget = false;
- }
-
- RunUpkeep();
- if (Timeline_Paused && !FrameAdvance && !Timeline_AutoPlayUntilTarget)
- {
- Thread.Sleep(50);
- }
- else
- {
- if (TasTimeline.frameIndex == TriCTASTimeline.TimelineSavestates.Count)
- {
- // create a savestate for the previous frame.
- if (TasTimeline.SavestateEveryFrame())
- {
- List state = EMU.SaveState();
- TriCTASTimeline.TimelineSavestates.Add(state);
- TasTimeline.SavestateLength = state.Count;
- }
- else
- {
- List state = new List();
- TriCTASTimeline.TimelineSavestates.Add(state);
- }
- if (TriCTASTimeline.TimelineSavestates[TasTimeline.frameIndex].Count > 0)
- {
- // if this savestate is not empty
- List state = new List();
- TriCTASTimeline.TimelineTempSavestates.Add(state);
- TasTimeline.TrimTempSavestates();
- //TriCTASTimeline.TEMPRerecordTracker.Add(TasTimeline.Rerecords);
- }
- else
- {
- // if this savestate is empty
- List state = EMU.SaveState();
- TriCTASTimeline.TimelineTempSavestates.Add(state);
- TasTimeline.TrimTempSavestates();
- //TriCTASTimeline.TEMPRerecordTracker.Add(TasTimeline.Rerecords);
- }
- }
- else if (TasTimeline.frameIndex < TriCTASTimeline.TimelineTempSavestates.Count && TriCTASTimeline.TimelineTempSavestates[TasTimeline.frameIndex].Count == 0)
- {
- List state = EMU.SaveState();
- TriCTASTimeline.TimelineTempSavestates[TasTimeline.frameIndex] = state;
- TasTimeline.TrimTempSavestates();
- }
-
- if (TasTimeline.RecordInputs() && !rewinding)
- {
- byte realtimeInputs = RealtimeInputs();
- if (TasTimeline.Player2())
- {
- EMU.ControllerPort2 = realtimeInputs;
- EMU.ControllerPort1 = 0;
- }
- else
- {
- EMU.ControllerPort1 = realtimeInputs;
- EMU.ControllerPort2 = 0;
- }
- ushort rimputs = (ushort)((EMU.ControllerPort2 << 8) | EMU.ControllerPort1);
- TriCTASTimeline.Inputs[TasTimeline.frameIndex] = rimputs;
- int row = TasTimeline.frameIndex - TasTimeline.TopFrame;
- if (row >= 0 && row < 40)
- {
- TasTimeline.RecalculateTimelineRow(row, rimputs);
- TasTimeline.RedrawTimelineRow(row, false);
- }
- if (TasTimeline.frameIndex < TasTimeline.frameEmulated)
- {
- TasTimeline.MarkStale(TasTimeline.frameIndex);
- Timeline_PendingLoadState = false; // the MarkStale() function typically loads a savestate, but we don't want that here.
- }
- }
- else
- {
- EMU.ControllerPort1 = (byte)(TriCTASTimeline.Inputs[TasTimeline.frameIndex] & 0xFF);
- EMU.ControllerPort2 = (byte)((TriCTASTimeline.Inputs[TasTimeline.frameIndex] & 0xFF00) >> 8);
- }
-
- EMU._CoreFrameAdvance();
- RunPostFramePhase();
- if (TasTimeline.frameIndex < TriCTASTimeline.Resets.Count && TriCTASTimeline.Resets[TasTimeline.frameIndex])
- {
- EMU.ResetButton();
- }
-
- if (!EMU.TASTimelineClockFiltering || !EMU.LagFrame)
- {
- TasTimeline.FrameAdvance();
- }
- else
- {
- //Timeline_PendingFrameAdvance = true; // keep running until a non-lag frame.
- }
- }
- }
- }
-
- public bool[] OtherControllerHotkeys()
- {
- bool[] joystickButtons = new bool[4];
-
- int c = SDL.SDL_NumJoysticks();
- if (c != 0)
- {
- SDL.SDL_GameControllerUpdate();
-
- joystickButtons[0] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0;
- joystickButtons[1] = SDL.SDL_GameControllerGetButton(gameControllerPrt, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0;
- joystickButtons[2] = SDL.SDL_GameControllerGetAxis(gameControllerPrt, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > 0.2;
- joystickButtons[3] = SDL.SDL_GameControllerGetAxis(gameControllerPrt, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT) > 0.2;
-
- }
- else
- {
- for (int i = 0; i < 4; i++)
- {
- joystickButtons[i] = false;
- }
- }
- return joystickButtons;
- }
- bool Pending_EjectCartridge;
-
- private void ejectCartridgeToolStripMenuItem_Click(object sender, EventArgs e)
- {
- // I highly doubt anybody will intentionally press this button other than myself right now as I test this.
- // It might be a good idea to remove this feature at some point, heh.
- Pending_EjectCartridge = true;
- }
-
- private void cartridgeConnectorToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (CartConnector != null)
- {
- CartConnector.Focus();
- return;
- }
- CartConnector = new TriC72PinConnector();
- CartConnector.MainGUI = this;
- CartConnector.Show();
- CartConnector.Location = Location;
- }
- }
-
- ///
- /// Inherits from PictureBox; adds Interpolation Mode Setting
- ///
- public class PictureBoxWithInterpolationMode : PictureBox
- {
- public InterpolationMode InterpolationMode { get; set; }
- public PictureBoxWithInterpolationMode()
- {
- SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
- SetStyle(ControlStyles.Opaque, true);
- SetStyle(ControlStyles.UserPaint, true);
- SetStyle(ControlStyles.AllPaintingInWmPaint, true);
- }
- protected override void OnPaint(PaintEventArgs paintEventArgs)
- {
- paintEventArgs.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
- paintEventArgs.Graphics.InterpolationMode = InterpolationMode;
- base.OnPaint(paintEventArgs);
- }
- }
-
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.resx
deleted file mode 100644
index ead2cdc7..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNESGUI.resx
+++ /dev/null
@@ -1,209 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 17, 17
-
-
- True
-
-
- True
-
-
- True
-
-
-
-
- AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA
- AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD/////////////
- //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA
- AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//
- /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA
- AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//
- ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA
- AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA
- AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/////////
- //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA
- AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA
- AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD/////////////
- ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/////////
- //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////
- //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////
- /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA
- zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP////////
- //////////////////////////////////8=
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.Designer.cs
deleted file mode 100644
index 9db389eb..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.Designer.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-namespace TriCNES
-{
- partial class TriCNTViewer
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCNTViewer));
- this.pictureBox1 = new System.Windows.Forms.PictureBox();
- this.cb_ForcePal0ToBackdrop = new System.Windows.Forms.CheckBox();
- this.cb_ScreenBoundary = new System.Windows.Forms.CheckBox();
- this.cb_OverlayScreen = new System.Windows.Forms.CheckBox();
- this.menuStrip1 = new System.Windows.Forms.MenuStrip();
- this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.screenshotToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
- this.menuStrip1.SuspendLayout();
- this.SuspendLayout();
- //
- // pictureBox1
- //
- this.pictureBox1.Location = new System.Drawing.Point(0, 21);
- this.pictureBox1.Name = "pictureBox1";
- this.pictureBox1.Size = new System.Drawing.Size(512, 480);
- this.pictureBox1.TabIndex = 0;
- this.pictureBox1.TabStop = false;
- //
- // cb_ForcePal0ToBackdrop
- //
- this.cb_ForcePal0ToBackdrop.AutoSize = true;
- this.cb_ForcePal0ToBackdrop.Location = new System.Drawing.Point(13, 508);
- this.cb_ForcePal0ToBackdrop.Name = "cb_ForcePal0ToBackdrop";
- this.cb_ForcePal0ToBackdrop.Size = new System.Drawing.Size(121, 17);
- this.cb_ForcePal0ToBackdrop.TabIndex = 1;
- this.cb_ForcePal0ToBackdrop.Text = "Use Backdrop Color";
- this.cb_ForcePal0ToBackdrop.UseVisualStyleBackColor = true;
- //
- // cb_ScreenBoundary
- //
- this.cb_ScreenBoundary.AutoSize = true;
- this.cb_ScreenBoundary.Location = new System.Drawing.Point(12, 531);
- this.cb_ScreenBoundary.Name = "cb_ScreenBoundary";
- this.cb_ScreenBoundary.Size = new System.Drawing.Size(136, 17);
- this.cb_ScreenBoundary.TabIndex = 2;
- this.cb_ScreenBoundary.Text = "Draw Screen Boundary";
- this.cb_ScreenBoundary.UseVisualStyleBackColor = true;
- //
- // cb_OverlayScreen
- //
- this.cb_OverlayScreen.AutoSize = true;
- this.cb_OverlayScreen.Location = new System.Drawing.Point(12, 554);
- this.cb_OverlayScreen.Name = "cb_OverlayScreen";
- this.cb_OverlayScreen.Size = new System.Drawing.Size(99, 17);
- this.cb_OverlayScreen.TabIndex = 3;
- this.cb_OverlayScreen.Text = "Overlay Screen";
- this.cb_OverlayScreen.UseVisualStyleBackColor = true;
- //
- // menuStrip1
- //
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.fileToolStripMenuItem});
- this.menuStrip1.Location = new System.Drawing.Point(0, 0);
- this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(512, 24);
- this.menuStrip1.TabIndex = 4;
- this.menuStrip1.Text = "menuStrip1";
- //
- // fileToolStripMenuItem
- //
- this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.screenshotToolStripMenuItem});
- this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
- this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
- this.fileToolStripMenuItem.Text = "File";
- //
- // screenshotToolStripMenuItem
- //
- this.screenshotToolStripMenuItem.Name = "screenshotToolStripMenuItem";
- this.screenshotToolStripMenuItem.Size = new System.Drawing.Size(132, 22);
- this.screenshotToolStripMenuItem.Text = "Screenshot";
- this.screenshotToolStripMenuItem.Click += new System.EventHandler(this.screenshotToolStripMenuItem_Click);
- //
- // TriCNTViewer
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(512, 579);
- this.Controls.Add(this.cb_OverlayScreen);
- this.Controls.Add(this.cb_ScreenBoundary);
- this.Controls.Add(this.cb_ForcePal0ToBackdrop);
- this.Controls.Add(this.pictureBox1);
- this.Controls.Add(this.menuStrip1);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.MainMenuStrip = this.menuStrip1;
- this.MaximizeBox = false;
- this.MaximumSize = new System.Drawing.Size(528, 618);
- this.MinimizeBox = false;
- this.MinimumSize = new System.Drawing.Size(528, 618);
- this.Name = "TriCNTViewer";
- this.Text = "Nametable Viewer";
- ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
- this.menuStrip1.ResumeLayout(false);
- this.menuStrip1.PerformLayout();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.PictureBox pictureBox1;
- private System.Windows.Forms.CheckBox cb_ForcePal0ToBackdrop;
- private System.Windows.Forms.CheckBox cb_ScreenBoundary;
- private System.Windows.Forms.CheckBox cb_OverlayScreen;
- private System.Windows.Forms.MenuStrip menuStrip1;
- private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem screenshotToolStripMenuItem;
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.cs
deleted file mode 100644
index 02a1790e..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Drawing;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- public partial class TriCNTViewer : Form
- {
- public TriCNESGUI MainGUI;
- public TriCNTViewer()
- {
- InitializeComponent();
- FormClosing += TriCNTViewer_Closing;
- }
-
- private void TriCNTViewer_Closing(Object sender, FormClosingEventArgs e)
- {
- if (MainGUI != null)
- {
- MainGUI.NametableViewer = null;
- }
- Dispose();
- }
-
- public void Update(Bitmap b)
- {
- MethodInvoker upd = delegate
- {
- pictureBox1.Image = b;
- pictureBox1.Update();
- };
- try
- {
- this.Invoke(upd);
- }
- catch (Exception e)
- {
-
- }
- }
-
- public bool UseBackdrop()
- {
- return cb_ForcePal0ToBackdrop.Checked;
- }
-
- public bool DrawBoundary()
- {
- return cb_ScreenBoundary.Checked;
- }
- public bool OverlayScreen()
- {
- return cb_OverlayScreen.Checked;
- }
-
- private void screenshotToolStripMenuItem_Click(object sender, EventArgs e)
- {
- Clipboard.SetImage(pictureBox1.Image);
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.resx
deleted file mode 100644
index 9c754a53..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCNTViewer.resx
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 17, 17
-
-
-
-
- AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAA
- AP8AAAD/AAAA/wAAAP8AAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAAAAAA
- AAD///////////////////////////////8AAAAAAAAAAAAAAP8AAAD/AAAAAAAAAAD/////////////
- //////////////////8AAAAAAAAAAAAAAP8AAAD/////////////////////////////////AAAAAAAA
- AAAAAAAAAAAAAP///////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//
- /////////////////////////////wAAAAAAAAAAAAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAA
- AP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//
- ////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAA
- AP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA////////
- //8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD///////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAA
- AAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAAAP8AAAD/AAAAAAAAAAD//////////wAA
- AP8AAAD/AAAAAAAAAAAAAAAAAAAAAP//////////AAAA/wAAAP///////////wAAAP8AAAD/AAAAAAAA
- AAAAAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAA/wAAAP8AAAAAAAAAAP//
- ////////AAAA/wAAAP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA////////////AAAA/wAA
- AP8AAAAAAAAAAAAAAAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAA//////////8AAAD/AAAA/wAA
- AAAAAAAA//////////8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD/////////
- //8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAD//////////wAAAP8AAAD//////////////////////wAA
- AP8AAAD/AAAAAAAAAAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAA
- AAD//////////wAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA////////////AAAAAAAAAAD/////////////
- ////////AAAA/wAAAP8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/////////
- //8AAAAAAAAAAP//////////AAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD///////////8AAAAAAAAAAAAA
- AAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////
- //8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////
- /////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////wAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////A8D8DwPA/AwPA
- zAwDwMwMww8A8MMPAPDDDwDwww8A8MMPAPDDDwDwww8A8MMPAPADMDMDAzAzA8/A/A/PwPwP////////
- //////////////////////////////////8=
-
-
-
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.Designer.cs
deleted file mode 100644
index 6ce338fb..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.Designer.cs
+++ /dev/null
@@ -1,426 +0,0 @@
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- partial class TriCTASTimeline
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.components = new System.ComponentModel.Container();
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TriCTASTimeline));
- this.pb_Timeline = new System.Windows.Forms.PictureBox();
- this.timelineScrollbar = new System.Windows.Forms.VScrollBar();
- this.b_FrameAdvance = new System.Windows.Forms.Button();
- this.b_FrameBack = new System.Windows.Forms.Button();
- this.menuStrip1 = new System.Windows.Forms.MenuStrip();
- this.tASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.loadTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.saveTASToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.saveWithSavestatesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.exportTor08ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.cellFrequencyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.perVBlankToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.perControllerStrobeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.b_play = new System.Windows.Forms.Button();
- this.contextMenuStrip_Timeline = new System.Windows.Forms.ContextMenuStrip(this.components);
- this.deleteFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.insertFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.truncateMovieToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.savestateThisFrameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.b_JumptoCursor = new System.Windows.Forms.Button();
- this.cb_FollowCursor = new System.Windows.Forms.CheckBox();
- this.tb_FollowDistance = new System.Windows.Forms.TrackBar();
- this.cb_SavestateEveryFrame = new System.Windows.Forms.CheckBox();
- this.tb_TempSavestates = new System.Windows.Forms.TextBox();
- this.label_TempSavestates = new System.Windows.Forms.Label();
- this.Tooltip_ = new System.Windows.Forms.ToolTip(this.components);
- this.label_AutoSavestateThreshold = new System.Windows.Forms.Label();
- this.tb_AutoSavestateThreshold = new System.Windows.Forms.TextBox();
- this.cb_RecordInputs = new System.Windows.Forms.CheckBox();
- this.cb_player2 = new System.Windows.Forms.CheckBox();
- ((System.ComponentModel.ISupportInitialize)(this.pb_Timeline)).BeginInit();
- this.menuStrip1.SuspendLayout();
- this.contextMenuStrip_Timeline.SuspendLayout();
- ((System.ComponentModel.ISupportInitialize)(this.tb_FollowDistance)).BeginInit();
- this.SuspendLayout();
- //
- // pb_Timeline
- //
- this.pb_Timeline.Location = new System.Drawing.Point(12, 27);
- this.pb_Timeline.Name = "pb_Timeline";
- this.pb_Timeline.Size = new System.Drawing.Size(353, 657);
- this.pb_Timeline.TabIndex = 7;
- this.pb_Timeline.TabStop = false;
- //
- // timelineScrollbar
- //
- this.timelineScrollbar.Location = new System.Drawing.Point(372, 27);
- this.timelineScrollbar.Name = "timelineScrollbar";
- this.timelineScrollbar.Size = new System.Drawing.Size(19, 657);
- this.timelineScrollbar.TabIndex = 2;
- //
- // b_FrameAdvance
- //
- this.b_FrameAdvance.Location = new System.Drawing.Point(601, 38);
- this.b_FrameAdvance.Name = "b_FrameAdvance";
- this.b_FrameAdvance.Size = new System.Drawing.Size(75, 23);
- this.b_FrameAdvance.TabIndex = 1;
- this.b_FrameAdvance.Text = ">";
- this.b_FrameAdvance.UseVisualStyleBackColor = true;
- this.b_FrameAdvance.Click += new System.EventHandler(this.b_FrameAdvance_Click);
- //
- // b_FrameBack
- //
- this.b_FrameBack.Location = new System.Drawing.Point(439, 38);
- this.b_FrameBack.Name = "b_FrameBack";
- this.b_FrameBack.Size = new System.Drawing.Size(75, 23);
- this.b_FrameBack.TabIndex = 2;
- this.b_FrameBack.Text = "<";
- this.b_FrameBack.UseVisualStyleBackColor = true;
- this.b_FrameBack.Click += new System.EventHandler(this.b_FrameBack_Click);
- //
- // menuStrip1
- //
- this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.tASToolStripMenuItem,
- this.settingsToolStripMenuItem});
- this.menuStrip1.Location = new System.Drawing.Point(0, 0);
- this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(745, 24);
- this.menuStrip1.TabIndex = 3;
- this.menuStrip1.Text = "menuStrip1";
- //
- // tASToolStripMenuItem
- //
- this.tASToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.loadTASToolStripMenuItem,
- this.saveTASToolStripMenuItem,
- this.saveWithSavestatesToolStripMenuItem,
- this.exportTor08ToolStripMenuItem});
- this.tASToolStripMenuItem.Name = "tASToolStripMenuItem";
- this.tASToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
- this.tASToolStripMenuItem.Text = "File";
- //
- // loadTASToolStripMenuItem
- //
- this.loadTASToolStripMenuItem.Name = "loadTASToolStripMenuItem";
- this.loadTASToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.loadTASToolStripMenuItem.Text = "Load";
- this.loadTASToolStripMenuItem.Click += new System.EventHandler(this.loadTASToolStripMenuItem_Click);
- //
- // saveTASToolStripMenuItem
- //
- this.saveTASToolStripMenuItem.Name = "saveTASToolStripMenuItem";
- this.saveTASToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.saveTASToolStripMenuItem.Text = "Save";
- this.saveTASToolStripMenuItem.Click += new System.EventHandler(this.saveTASToolStripMenuItem_Click);
- //
- // saveWithSavestatesToolStripMenuItem
- //
- this.saveWithSavestatesToolStripMenuItem.Name = "saveWithSavestatesToolStripMenuItem";
- this.saveWithSavestatesToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.saveWithSavestatesToolStripMenuItem.Text = "Save with savestates";
- this.saveWithSavestatesToolStripMenuItem.Click += new System.EventHandler(this.saveWithSavestatesToolStripMenuItem_Click);
- //
- // exportTor08ToolStripMenuItem
- //
- this.exportTor08ToolStripMenuItem.Name = "exportTor08ToolStripMenuItem";
- this.exportTor08ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
- this.exportTor08ToolStripMenuItem.Text = "Export to .r08";
- this.exportTor08ToolStripMenuItem.Click += new System.EventHandler(this.exportTor08ToolStripMenuItem_Click);
- //
- // settingsToolStripMenuItem
- //
- this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.cellFrequencyToolStripMenuItem});
- this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
- this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
- this.settingsToolStripMenuItem.Text = "Settings";
- //
- // cellFrequencyToolStripMenuItem
- //
- this.cellFrequencyToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.perVBlankToolStripMenuItem,
- this.perControllerStrobeToolStripMenuItem});
- this.cellFrequencyToolStripMenuItem.Name = "cellFrequencyToolStripMenuItem";
- this.cellFrequencyToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
- this.cellFrequencyToolStripMenuItem.Text = "Cell Frequency";
- //
- // perVBlankToolStripMenuItem
- //
- this.perVBlankToolStripMenuItem.Checked = true;
- this.perVBlankToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked;
- this.perVBlankToolStripMenuItem.Name = "perVBlankToolStripMenuItem";
- this.perVBlankToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
- this.perVBlankToolStripMenuItem.Text = "Per VBlank";
- this.perVBlankToolStripMenuItem.Click += new System.EventHandler(this.perVBlankToolStripMenuItem_Click);
- //
- // perControllerStrobeToolStripMenuItem
- //
- this.perControllerStrobeToolStripMenuItem.Name = "perControllerStrobeToolStripMenuItem";
- this.perControllerStrobeToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
- this.perControllerStrobeToolStripMenuItem.Text = "Per Controller Strobe";
- this.perControllerStrobeToolStripMenuItem.Click += new System.EventHandler(this.perControllerStrobeToolStripMenuItem_Click);
- //
- // b_play
- //
- this.b_play.Location = new System.Drawing.Point(521, 38);
- this.b_play.Name = "b_play";
- this.b_play.Size = new System.Drawing.Size(75, 23);
- this.b_play.TabIndex = 4;
- this.b_play.Text = "Paused";
- this.b_play.UseVisualStyleBackColor = true;
- this.b_play.Click += new System.EventHandler(this.b_play_Click);
- //
- // contextMenuStrip_Timeline
- //
- this.contextMenuStrip_Timeline.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.deleteFrameToolStripMenuItem,
- this.insertFrameToolStripMenuItem,
- this.truncateMovieToolStripMenuItem,
- this.savestateThisFrameToolStripMenuItem});
- this.contextMenuStrip_Timeline.Name = "contextMenuStrip_Timeline";
- this.contextMenuStrip_Timeline.Size = new System.Drawing.Size(184, 92);
- //
- // deleteFrameToolStripMenuItem
- //
- this.deleteFrameToolStripMenuItem.Name = "deleteFrameToolStripMenuItem";
- this.deleteFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
- this.deleteFrameToolStripMenuItem.Text = "Delete Frame";
- this.deleteFrameToolStripMenuItem.Click += new System.EventHandler(this.deleteFrameToolStripMenuItem_Click);
- //
- // insertFrameToolStripMenuItem
- //
- this.insertFrameToolStripMenuItem.Name = "insertFrameToolStripMenuItem";
- this.insertFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
- this.insertFrameToolStripMenuItem.Text = "Insert Frame";
- this.insertFrameToolStripMenuItem.Click += new System.EventHandler(this.insertFrameToolStripMenuItem_Click);
- //
- // truncateMovieToolStripMenuItem
- //
- this.truncateMovieToolStripMenuItem.Name = "truncateMovieToolStripMenuItem";
- this.truncateMovieToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
- this.truncateMovieToolStripMenuItem.Text = "Truncate Movie";
- this.truncateMovieToolStripMenuItem.Click += new System.EventHandler(this.truncateMovieToolStripMenuItem_Click);
- //
- // savestateThisFrameToolStripMenuItem
- //
- this.savestateThisFrameToolStripMenuItem.Name = "savestateThisFrameToolStripMenuItem";
- this.savestateThisFrameToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
- this.savestateThisFrameToolStripMenuItem.Text = "Savestate This Frame";
- this.savestateThisFrameToolStripMenuItem.Click += new System.EventHandler(this.savestateThisFrameToolStripMenuItem_Click);
- //
- // b_JumptoCursor
- //
- this.b_JumptoCursor.Location = new System.Drawing.Point(236, 699);
- this.b_JumptoCursor.Name = "b_JumptoCursor";
- this.b_JumptoCursor.Size = new System.Drawing.Size(99, 23);
- this.b_JumptoCursor.TabIndex = 5;
- this.b_JumptoCursor.Text = "Jump to Cursor";
- this.b_JumptoCursor.UseVisualStyleBackColor = true;
- this.b_JumptoCursor.Click += new System.EventHandler(this.b_JumptoCursor_Click);
- //
- // cb_FollowCursor
- //
- this.cb_FollowCursor.AutoSize = true;
- this.cb_FollowCursor.Checked = true;
- this.cb_FollowCursor.CheckState = System.Windows.Forms.CheckState.Checked;
- this.cb_FollowCursor.Location = new System.Drawing.Point(26, 703);
- this.cb_FollowCursor.Name = "cb_FollowCursor";
- this.cb_FollowCursor.Size = new System.Drawing.Size(89, 17);
- this.cb_FollowCursor.TabIndex = 6;
- this.cb_FollowCursor.Text = "Follow Cursor";
- this.cb_FollowCursor.UseVisualStyleBackColor = true;
- //
- // tb_FollowDistance
- //
- this.tb_FollowDistance.LargeChange = 1;
- this.tb_FollowDistance.Location = new System.Drawing.Point(118, 699);
- this.tb_FollowDistance.Maximum = 39;
- this.tb_FollowDistance.Name = "tb_FollowDistance";
- this.tb_FollowDistance.Size = new System.Drawing.Size(104, 45);
- this.tb_FollowDistance.TabIndex = 8;
- this.tb_FollowDistance.Value = 20;
- this.tb_FollowDistance.Scroll += new System.EventHandler(this.tb_FollowDistance_Scroll);
- //
- // cb_SavestateEveryFrame
- //
- this.cb_SavestateEveryFrame.AutoSize = true;
- this.cb_SavestateEveryFrame.Location = new System.Drawing.Point(409, 112);
- this.cb_SavestateEveryFrame.Name = "cb_SavestateEveryFrame";
- this.cb_SavestateEveryFrame.Size = new System.Drawing.Size(136, 17);
- this.cb_SavestateEveryFrame.TabIndex = 9;
- this.cb_SavestateEveryFrame.Text = "Savestate Every Frame";
- this.cb_SavestateEveryFrame.UseVisualStyleBackColor = true;
- //
- // tb_TempSavestates
- //
- this.tb_TempSavestates.Location = new System.Drawing.Point(410, 132);
- this.tb_TempSavestates.MaxLength = 5;
- this.tb_TempSavestates.Name = "tb_TempSavestates";
- this.tb_TempSavestates.Size = new System.Drawing.Size(41, 20);
- this.tb_TempSavestates.TabIndex = 10;
- this.tb_TempSavestates.Text = "120";
- this.Tooltip_.SetToolTip(this.tb_TempSavestates, resources.GetString("tb_TempSavestates.ToolTip"));
- this.tb_TempSavestates.TextChanged += new System.EventHandler(this.tb_TempSavestates_TextChanged);
- this.tb_TempSavestates.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tb_FilterForNumbers);
- //
- // label_TempSavestates
- //
- this.label_TempSavestates.AutoSize = true;
- this.label_TempSavestates.Location = new System.Drawing.Point(457, 139);
- this.label_TempSavestates.Name = "label_TempSavestates";
- this.label_TempSavestates.Size = new System.Drawing.Size(139, 13);
- this.label_TempSavestates.TabIndex = 11;
- this.label_TempSavestates.Text = "Temporary Savestate Count";
- this.Tooltip_.SetToolTip(this.label_TempSavestates, resources.GetString("label_TempSavestates.ToolTip"));
- //
- // Tooltip_
- //
- this.Tooltip_.AutoPopDelay = 500000;
- this.Tooltip_.InitialDelay = 500;
- this.Tooltip_.ReshowDelay = 100;
- //
- // label_AutoSavestateThreshold
- //
- this.label_AutoSavestateThreshold.AutoSize = true;
- this.label_AutoSavestateThreshold.Location = new System.Drawing.Point(457, 165);
- this.label_AutoSavestateThreshold.Name = "label_AutoSavestateThreshold";
- this.label_AutoSavestateThreshold.Size = new System.Drawing.Size(130, 13);
- this.label_AutoSavestateThreshold.TabIndex = 13;
- this.label_AutoSavestateThreshold.Text = "Auto-Savestate Threshold";
- this.Tooltip_.SetToolTip(this.label_AutoSavestateThreshold, "Automatically make a savestate every \'n\' frames.\r\nA larger value will consume les" +
- "s RAM, but leave gaps that will need to be re-emulated when loading earlier fram" +
- "es.\r\n");
- //
- // tb_AutoSavestateThreshold
- //
- this.tb_AutoSavestateThreshold.Location = new System.Drawing.Point(410, 158);
- this.tb_AutoSavestateThreshold.MaxLength = 5;
- this.tb_AutoSavestateThreshold.Name = "tb_AutoSavestateThreshold";
- this.tb_AutoSavestateThreshold.Size = new System.Drawing.Size(41, 20);
- this.tb_AutoSavestateThreshold.TabIndex = 12;
- this.tb_AutoSavestateThreshold.Text = "500";
- this.Tooltip_.SetToolTip(this.tb_AutoSavestateThreshold, "Automatically make a savestate every \'n\' frames.\r\nA larger value will consume les" +
- "s RAM, but leave gaps that will need to be re-emulated when loading earlier fram" +
- "es.");
- this.tb_AutoSavestateThreshold.TextChanged += new System.EventHandler(this.tb_AutoSavestateThreshold_TextChanged);
- this.tb_AutoSavestateThreshold.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tb_FilterForNumbers);
- //
- // cb_RecordInputs
- //
- this.cb_RecordInputs.AutoSize = true;
- this.cb_RecordInputs.Location = new System.Drawing.Point(409, 89);
- this.cb_RecordInputs.Name = "cb_RecordInputs";
- this.cb_RecordInputs.Size = new System.Drawing.Size(93, 17);
- this.cb_RecordInputs.TabIndex = 14;
- this.cb_RecordInputs.Text = "Record Inputs";
- this.Tooltip_.SetToolTip(this.cb_RecordInputs, "If checked, the inputs you provide will be recorded on the timeline.\r\nThis will o" +
- "verwrite older frames if you rewind.");
- this.cb_RecordInputs.UseVisualStyleBackColor = true;
- //
- // cb_player2
- //
- this.cb_player2.AutoSize = true;
- this.cb_player2.Location = new System.Drawing.Point(508, 89);
- this.cb_player2.Name = "cb_player2";
- this.cb_player2.Size = new System.Drawing.Size(64, 17);
- this.cb_player2.TabIndex = 15;
- this.cb_player2.Text = "Player 2";
- this.Tooltip_.SetToolTip(this.cb_player2, "If checked, the inputs you provide will be recorded on the timeline.\r\nThis will o" +
- "verwrite older frames if you rewind.");
- this.cb_player2.UseVisualStyleBackColor = true;
- //
- // TriCTASTimeline
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(745, 743);
- this.Controls.Add(this.cb_player2);
- this.Controls.Add(this.cb_RecordInputs);
- this.Controls.Add(this.label_AutoSavestateThreshold);
- this.Controls.Add(this.tb_AutoSavestateThreshold);
- this.Controls.Add(this.label_TempSavestates);
- this.Controls.Add(this.tb_TempSavestates);
- this.Controls.Add(this.cb_SavestateEveryFrame);
- this.Controls.Add(this.tb_FollowDistance);
- this.Controls.Add(this.timelineScrollbar);
- this.Controls.Add(this.pb_Timeline);
- this.Controls.Add(this.cb_FollowCursor);
- this.Controls.Add(this.b_JumptoCursor);
- this.Controls.Add(this.b_play);
- this.Controls.Add(this.menuStrip1);
- this.Controls.Add(this.b_FrameBack);
- this.Controls.Add(this.b_FrameAdvance);
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.Name = "TriCTASTimeline";
- this.Text = "TAS Timeline";
- ((System.ComponentModel.ISupportInitialize)(this.pb_Timeline)).EndInit();
- this.menuStrip1.ResumeLayout(false);
- this.menuStrip1.PerformLayout();
- this.contextMenuStrip_Timeline.ResumeLayout(false);
- ((System.ComponentModel.ISupportInitialize)(this.tb_FollowDistance)).EndInit();
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
- private System.Windows.Forms.Button b_FrameAdvance;
- private System.Windows.Forms.VScrollBar timelineScrollbar;
- private System.Windows.Forms.Button b_FrameBack;
- private System.Windows.Forms.MenuStrip menuStrip1;
- private System.Windows.Forms.ToolStripMenuItem tASToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem loadTASToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem saveTASToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem exportTor08ToolStripMenuItem;
- private System.Windows.Forms.Button b_play;
- private System.Windows.Forms.ContextMenuStrip contextMenuStrip_Timeline;
- private System.Windows.Forms.ToolStripMenuItem deleteFrameToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem insertFrameToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem truncateMovieToolStripMenuItem;
- private System.Windows.Forms.Button b_JumptoCursor;
- private System.Windows.Forms.CheckBox cb_FollowCursor;
- private System.Windows.Forms.PictureBox pb_Timeline;
- private System.Windows.Forms.TrackBar tb_FollowDistance;
- private System.Windows.Forms.ToolStripMenuItem savestateThisFrameToolStripMenuItem;
- private System.Windows.Forms.CheckBox cb_SavestateEveryFrame;
- private System.Windows.Forms.TextBox tb_TempSavestates;
- private System.Windows.Forms.Label label_TempSavestates;
- private System.Windows.Forms.ToolTip Tooltip_;
- private System.Windows.Forms.Label label_AutoSavestateThreshold;
- private System.Windows.Forms.TextBox tb_AutoSavestateThreshold;
- private System.Windows.Forms.ToolStripMenuItem saveWithSavestatesToolStripMenuItem;
- private CheckBox cb_RecordInputs;
- private ToolStripMenuItem cellFrequencyToolStripMenuItem;
- private ToolStripMenuItem perVBlankToolStripMenuItem;
- private ToolStripMenuItem perControllerStrobeToolStripMenuItem;
- private CheckBox cb_player2;
- }
-}
\ No newline at end of file
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.cs
deleted file mode 100644
index 95b17e80..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.cs
+++ /dev/null
@@ -1,1474 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.IO;
-using System.Timers;
-using System.Windows.Forms;
-
-namespace TriCNES
-{
- public partial class TriCTASTimeline : Form
- {
- public Font Font_Consolas;
- public Brush Brush_LeftColumn;
- public Brush Brush_LeftColumn_Saved;
- public Brush Brush_LeftColumn_TempSaved;
- public Brush Brush_HighlightedCell;
- public Brush Brush_WhiteCellP1;
- public Brush Brush_WhiteCellP2;
- public Brush Brush_GreenCellP1;
- public Brush Brush_GreenCellP2;
- public Brush Brush_GreenCellP1_Stale;
- public Brush Brush_GreenCellP2_Stale;
- public Brush Brush_RedCellP1;
- public Brush Brush_RedCellP2;
- public Brush Brush_RedCellP1_Stale;
- public Brush Brush_RedCellP2_Stale;
-
- struct Vector2
- {
- public int x;
- public int y;
- public Vector2(int X, int Y)
- {
- x = X;
- y = Y;
- }
- public override string ToString()
- {
- return x.ToString() + ", " + y.ToString();
- }
- }
-
- public struct TimelineCell
- {
- public bool Checked;
- public bool LagFrame;
- public bool Stale;
- public bool Emulated;
- public TimelineCell(bool t)
- {
- Checked = false;
- LagFrame = false;
- Stale = false;
- Emulated = false;
- }
- }
- public int SavestateLength;
- public int Rerecords;
- public TriCTASTimeline(TriCNESGUI Maingui)
- {
- MainGUI = Maingui;
- InitializeComponent();
-
- Font_Consolas = new Font("Consolas", 8);
- Brush_LeftColumn = new SolidBrush(Color.LightGray);
- Brush_LeftColumn_Saved = new SolidBrush(Color.Wheat);
- Brush_LeftColumn_TempSaved = new SolidBrush(Color.LemonChiffon);
- Brush_HighlightedCell = new SolidBrush(Color.LightBlue);
- Brush_WhiteCellP1 = new SolidBrush(Color.WhiteSmoke);
- Brush_WhiteCellP2 = new SolidBrush(Color.FromArgb(255, 230, 230, 230));
- Brush_GreenCellP1 = new SolidBrush(Color.FromArgb(255, 200, 240, 200));
- Brush_GreenCellP2 = new SolidBrush(Color.FromArgb(255, 190, 232, 190));
- Brush_GreenCellP1_Stale = new SolidBrush(Color.FromArgb(255, 205, 220, 205));
- Brush_GreenCellP2_Stale = new SolidBrush(Color.FromArgb(255, 195, 215, 195));
- Brush_RedCellP1 = new SolidBrush(Color.FromArgb(255, 240, 200, 200));
- Brush_RedCellP2 = new SolidBrush(Color.FromArgb(255, 232, 190, 190));
- Brush_RedCellP1_Stale = new SolidBrush(Color.FromArgb(255, 220, 205, 205));
- Brush_RedCellP2_Stale = new SolidBrush(Color.FromArgb(255, 215, 195, 195));
- Inputs = new List();
-
- Start();
- Inputs.Add(0);
- Resets.Add(false);
-
- timelineBitmap = new Bitmap(80 + 16 * 17 + 1, 41 * 16 + 1);
- G = Graphics.FromImage(timelineBitmap);
- pb_Timeline.Image = timelineBitmap;
- pb_Timeline.MouseDown += mouseDownEvent;
- pb_Timeline.MouseUp += mouseUpEvent;
- pb_Timeline.ContextMenuStrip = contextMenuStrip_Timeline;
- pb_Timeline.MouseWheel += mouseWheelEvent;
- timelineScrollbar.ValueChanged += timelineScrollbar_ValueChanged;
-
- //loop timer
- loopTimer = new System.Timers.Timer();
- loopTimer.Interval = 50;// interval in milliseconds
- loopTimer.Enabled = false;
- loopTimer.Elapsed += loopTimerEvent;
- loopTimer.AutoReset = true;
-
- autosave = new System.Timers.Timer();
- autosave.Interval = 60000;
- autosave.Elapsed += autosaveEvent;
- autosave.AutoReset = true;
- autosave.Enabled = true;
-
-
- Shown += TriCTASTimeline_Shown;
- FormClosing += TriCTASTimeline_Closing;
- }
-
- private void TriCTASTimeline_Closing(Object sender, FormClosingEventArgs e)
- {
- if (MainGUI != null)
- {
- MainGUI.TasTimeline = null;
- }
- Dispose();
- }
-
- private void mouseWheelEvent(object sender, MouseEventArgs e)
- {
- int result = timelineScrollbar.Value - Math.Sign(e.Delta);
- if (result > timelineScrollbar.Maximum - timelineScrollbar.LargeChange + 1)
- {
- result = timelineScrollbar.Maximum - timelineScrollbar.LargeChange + 1;
- }
- else if (result < 0)
- {
- result = 0;
- }
- timelineScrollbar.Value = result;
- }
-
- void TriCTASTimeline_Shown(object sender, EventArgs e)
- {
- RefreshTopOfTimeline();
- }
-
- void RefreshTopOfTimeline()
- {
- Rectangle[] GridOverlay = new Rectangle[]
- {
- new Rectangle(0,0,80,16),
- new Rectangle(80 + 16*0,0,16,16),
- new Rectangle(80 + 16*1,0,16,16),
- new Rectangle(80 + 16*2,0,16,16),
- new Rectangle(80 + 16*3,0,16,16),
- new Rectangle(80 + 16*4,0,16,16),
- new Rectangle(80 + 16*5,0,16,16),
- new Rectangle(80 + 16*6,0,16,16),
- new Rectangle(80 + 16*7,0,16,16),
- new Rectangle(80 + 16*8,0,16,16),
- new Rectangle(80 + 16*9,0,16,16),
- new Rectangle(80 + 16*10,0,16,16),
- new Rectangle(80 + 16*11,0,16,16),
- new Rectangle(80 + 16*12,0,16,16),
- new Rectangle(80 + 16*13,0,16,16),
- new Rectangle(80 + 16*14,0,16,16),
- new Rectangle(80 + 16*15,0,16,16),
- new Rectangle(80 + 16*16,0,16,16)
- };
- G.FillRectangle(Brush_LeftColumn, new Rectangle(0, 0, 80 + 16 * 17, 16));
- G.DrawRectangles(Pens.Black, GridOverlay);
- G.DrawString(ClockFiltering ? "Input #" : "Frame #", Font_Consolas, Brushes.Black, 0, 0);
- G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 0, 0);
- G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 1, 0);
- G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 2, 0);
- G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 3, 0);
- G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 4, 0);
- G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 5, 0);
- G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 6, 0);
- G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 7, 0);
- G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 8, 0);
- G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 9, 0);
- G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 10, 0);
- G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 11, 0);
- G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 12, 0);
- G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 13, 0);
- G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 14, 0);
- G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 15, 0);
- G.DrawString("r", Font_Consolas, Brushes.Black, 80 + 16 * 16, 0);
- RefreshTimeline();
- }
-
- public Graphics G;
- public Bitmap timelineBitmap;
-
- private static System.Timers.Timer loopTimer;
-
- private static System.Timers.Timer autosave;
-
- private void loopTimerEvent(Object source, ElapsedEventArgs e)
- {
- //this does whatever you want to happen while clicking on the button
- MainGUI.Timeline_PendingMouseHeld = true;
- }
-
- private void autosaveEvent(Object source, ElapsedEventArgs e)
- {
- if (Inputs.Count > 1000)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- InitDirectory += "autosave.3c2";
- FileStream fs = File.OpenWrite(InitDirectory);
- for (int i = 0; i < Inputs.Count; i++)
- {
- fs.WriteByte((byte)Inputs[i]);
- fs.WriteByte((byte)(Inputs[i] >> 8));
- }
- fs.Close();
- }
-
- }
-
- public void TimelineMouseHeldEvent()
- {
- MethodInvoker upd = delegate
- {
- int mouseX = MousePosition.X - Left - pb_Timeline.Left - 8;
- int mouseY = MousePosition.Y - Top - pb_Timeline.Top - 48;
-
- int Column = mouseX >= 80 ? (mouseX - 80) / 16 : -1;
- if (Column > 15) { Column = 15; }
-
- int Row = mouseY >= 0 ? mouseY / 16 : -1;
- if (Row > 39) { Row = 39; }
-
- Vector2 mousePos = new Vector2(Column, Row);
-
- if (mouseHeld_initPos.y >= 0)
- {
- if (mouseHeld_initPos.x >= 0)
- {
- // we clicked on a cell
- int spos = Math.Min(mousePos.y, mouseHeld_initPos.y);
- int tpos = Math.Max(mousePos.y, mouseHeld_initPos.y);
- if (spos < 0)
- {
- spos = 0;
- }
-
- for (int i = spos; i <= tpos; i++)
- {
- if (mouseHeld_initPos.x == 16)
- {
- TimelineGrid[i][17].Checked = mouseHeld_setInput;
- RedrawTimelineRow(i, false);
- int frame = i + TopFrame;
- while (frame >= Inputs.Count)
- {
- Inputs.Add(0);
- Resets.Add(false);
- }
- if (Inputs.Count + 39 > timelineScrollbar.Maximum)
- {
- timelineScrollbar.Maximum = Inputs.Count + 38;
- }
- Resets[frame] = mouseHeld_setInput;
- }
- else
- {
- bool state = GetCellInputStatus(new Vector2(mouseHeld_initPos.x, i));
- if (state != mouseHeld_setInput)
- {
- ushort input = SetCellInputStatus(new Vector2(mouseHeld_initPos.x, i), mouseHeld_setInput);
- TimelineGrid[i][mouseHeld_initPos.x + 1].Checked = mouseHeld_setInput;
-
- RecalculateTimelineRow(i, input);
- RedrawTimelineRow(i, false);
- }
- }
- }
- int checkStale = spos + TopFrame;
- if (checkStale < frameEmulated)
- {
- MarkStale(checkStale);
- }
- }
- else
- {
- // we clicked on the frame number.
-
- }
- }
-
-
-
- };
- this.Invoke(upd);
- }
-
- public int TopFrame = 0;
-
- Vector2 mouseHeld_initPos;
- bool mouseHeld_setInput;
- private void mouseDownEvent(object sender, MouseEventArgs e)
- {
- if (e.Button == MouseButtons.Right)
- {
- return;
- }
- loopTimer.Enabled = true;
- int mouseX = MousePosition.X - Left - pb_Timeline.Left - 8;
- int mouseY = MousePosition.Y - Top - pb_Timeline.Top - 48;
-
- int Column = mouseX >= 80 ? (mouseX - 80) / 16 : -1;
- if (Column > 16) { Column = 16; }
-
- int Row = mouseY >= 0 ? mouseY / 16 : -1;
- if (Row > 39) { Row = 39; }
-
- mouseHeld_initPos = new Vector2(Column, Row);
-
- MainGUI.Timeline_PendingMouseDown = true;
-
- }
-
- public void TimelineMouseDownEvent()
- {
- MethodInvoker upd = delegate
- {
- if (mouseHeld_initPos.y >= 0)
- {
- if (mouseHeld_initPos.x >= 0)
- {
- // we clicked on a cell
- mouseHeld_setInput = !GetCellInputStatus(mouseHeld_initPos);
- int frameClicked = TopFrame + mouseHeld_initPos.y;
-
- if (mouseHeld_initPos.x == 16)
- {
- TimelineGrid[mouseHeld_initPos.y][17].Checked = mouseHeld_setInput;
- RedrawTimelineRow(mouseHeld_initPos.y, false);
- while (frameClicked >= Inputs.Count)
- {
- Inputs.Add(0);
- Resets.Add(false);
- }
- if (Inputs.Count + 39 > timelineScrollbar.Maximum)
- {
- timelineScrollbar.Maximum = Inputs.Count + 38;
- }
- Resets[frameClicked] = mouseHeld_setInput;
- }
- else
- {
- // calculate if that cell had an input or not.
- ushort input = SetCellInputStatus(mouseHeld_initPos, mouseHeld_setInput);
- TimelineGrid[mouseHeld_initPos.y][mouseHeld_initPos.x + 1].Checked = mouseHeld_setInput;
-
- RecalculateTimelineRow(mouseHeld_initPos.y, input);
- RedrawTimelineRow(mouseHeld_initPos.y, false);
- }
- if (frameClicked < frameEmulated)
- {
- MarkStale(frameClicked);
- }
- }
- else
- {
- // we clicked on the frame number.
- // TODO: Move the cursor to this frame.
- // - If there exists a savestate for this frame, then simply laod it.
- // - Otherwise, we haven't seen this frame yet. Load the last savestate in the list, then emulate to this frame.
- int frameClicked = TopFrame + mouseHeld_initPos.y;
- if (frameClicked == frameIndex)
- {
- return; // we're already on that frame!
- }
- if (frameClicked < frameEmulated && (TimelineSavestates[frameClicked].Count == SavestateLength || TimelineTempSavestates[frameClicked].Count == SavestateLength))
- {
- int prevrow = frameIndex - TopFrame;
- frameIndex = frameClicked - 1; // and we're good to go.
- if (frameIndex == -1)
- {
- frameIndex = 0;
- MainGUI.Timeline_LoadState = TimelineSavestates[0];
- MainGUI.Timeline_PendingLoadState = true;
- MainGUI.Timeline_PendingFrameNumber = 0;
- UpdateTimelineRowStatus(prevrow);
- RedrawTimelineRow(prevrow, false);
- UpdateTimelineRowStatus(0);
- RedrawTimelineRow(0, false);
- MainGUI.Timeline_PendingResetScreen = true;
- }
- else
- {
- MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex
- MainGUI.Timeline_PendingLoadState = true;
- MainGUI.Timeline_PendingFrameNumber = frameIndex;
- MainGUI.Timeline_PendingFrameAdvance = true;
-
- UpdateTimelineRowStatus(prevrow);
- RedrawTimelineRow(prevrow, false);
- }
-
-
- }
- else
- {
- int row = frameIndex - TopFrame;
- int prevFrame = frameIndex;
- frameIndex = frameClicked;
- if (frameIndex > frameEmulated)
- {
- frameIndex = frameEmulated;
- }
- UpdateTimelineRowStatus(row);
- RedrawTimelineRow(row, false);
- if (frameIndex < 0)
- {
- frameIndex = 0;
- }
- else
- {
- bool Unneeded = false;
- MainGUI.Timeline_LoadState = GrabMostRecentSavestate(prevFrame, out Unneeded); // This function updates frameIndex
- if (!Unneeded)
- {
- MainGUI.Timeline_PendingLoadState = true;
- MainGUI.Timeline_PendingFrameNumber = frameIndex;
- }
- }
- // Now we need to emulate all the way until we reach the target frame.
- MainGUI.Timeline_AutoPlayUntilTarget = true;
- MainGUI.Timeline_AutoPlayTarget = frameClicked;
- row = frameIndex - TopFrame;
-
- UpdateTimelineRowStatus(row);
- RedrawTimelineRow(row, false);
-
- if (frameIndex > frameEmulated)
- {
- frameEmulated = frameIndex;
- }
- if (frameIndex > highestFrameEmulatedEver)
- {
- highestFrameEmulatedEver = frameIndex;
- }
- while (frameIndex >= Inputs.Count)
- {
- Inputs.Add(0);
- Resets.Add(false);
- }
- if (Inputs.Count + 39 > timelineScrollbar.Maximum)
- {
- timelineScrollbar.Maximum = Inputs.Count + 38;
- }
- }
-
- }
- }
- };
- this.Invoke(upd); // Use delegates here because this could otherwise modify the state of the timeline mid-rendering the timeline. (causing runtime errors)
- }
-
- public void MarkStale(int Frame)
- {
- frameEmulated = Frame; // mark everything after this as "stale"
- if (frameIndex >= Frame)
- {
- Rerecords++;
- frameIndex = Frame;
- TimelineSavestates.RemoveRange(frameIndex + 1, TimelineSavestates.Count - (frameIndex + 1));
- TimelineTempSavestates.RemoveRange(frameIndex + 1, TimelineTempSavestates.Count - (frameIndex + 1));
- //TEMPRerecordTracker.RemoveRange(frameIndex + 1, TEMPRerecordTracker.Count - (frameIndex + 1));
- MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex
- System.GC.Collect();
- MainGUI.Timeline_PendingLoadState = true;
- MainGUI.Timeline_PendingFrameNumber = frameIndex;
- }
- RefreshTimeline();
- }
- private void mouseUpEvent(object sender, MouseEventArgs e)
- {
- if (e.Button == MouseButtons.Right)
- {
- return;
- }
- loopTimer.Enabled = false;
- }
-
- bool GetCellInputStatus(Vector2 pos)
- {
- if (pos.x < 0 || pos.y < 0)
- {
- return false; // should never happen
- }
- int frame = pos.y + TopFrame;
- // get frame index
- if (frame >= Inputs.Count)
- {
- return false;
- }
- if (pos.x == 16)
- {
- return Resets[frame];
- }
- int shift = (7 - (pos.x & 7)) | (pos.x & 8);
- return ((Inputs[frame] >> shift) & 1) == 1;
- }
- ushort SetCellInputStatus(Vector2 pos, bool state)
- {
- if (pos.x < 0 || pos.y < 0)
- {
- return 0; // should never happen
- }
- // get frame index
- int frame = pos.y + TopFrame;
- int shift = (7 - (pos.x & 7)) | (pos.x & 8);
- int prevInputs = Inputs.Count;
- while (frame >= Inputs.Count)
- {
- // Add new inputs until you reach this frame
- if (state)
- {
- //Inputs.Add((ushort)(1 << shift));
- Inputs.Add(0);
- Resets.Add(false);
- }
- else
- {
- Inputs.Add(0);
- Resets.Add(false);
- }
- }
- if ((Inputs.Count > prevInputs))
- {
- timelineScrollbar.Maximum = Inputs.Count + 38;
- }
- if (state)
- {
- Inputs[frame] |= (ushort)(1 << shift);
- }
- else
- {
- Inputs[frame] &= (ushort)(0xFFFF ^ (1 << shift));
- }
- return Inputs[frame];
- }
-
- public TriCNESGUI MainGUI;
-
- public static List LagFrames;
- public static List TimelineGrid;
- public static List> TimelineSavestates;
- public static List> TimelineTempSavestates;
- public static List TEMPRerecordTracker;
- public static List Resets;
-
- public static List Inputs; // high byte = controller 2
-
- public int frameIndex;
- public int highestFrameEmulatedEver; // highest emulated frame
- public int frameEmulated; // highest emulated frame for the purposes of tracking stale frames
-
- int AutoSavestateThreshold = 500;
- int TempSavestates = 120;
-
- public void Start()
- {
- TimelineGrid = new List();
- TimelineSavestates = new List>();
- TimelineTempSavestates = new List>();
- LagFrames = new List();
- Resets = new List();
- //TEMPRerecordTracker = new List();
-
-
- for (int i = 0; i < 40; i++)
- {
- TimelineCell[] t = new TimelineCell[18];
- for (int j = 0; j < t.Length; j++)
- {
- t[0] = new TimelineCell(false);
- }
- TimelineGrid.Add(t);
- }
-
- Rerecords = 0;
- frameIndex = 0;
-
- timelineScrollbar.Maximum = Inputs.Count + 38;
- timelineScrollbar.LargeChange = 40;
-
- MainGUI.CreateTASTimelineEmulator();
-
- List state = MainGUI.EMU.SaveState();
- TimelineSavestates.Add(state);
- TimelineTempSavestates.Add(new List());
- SavestateLength = state.Count;
- //TEMPRerecordTracker.Add(Rerecords);
- }
-
-
- int ScrollbarValue = 0;
- private void timelineScrollbar_ValueChanged(object sender, EventArgs e)
- {
- TopFrame = timelineScrollbar.Value;
- if (TopFrame != ScrollbarValue)
- {
- ScrollbarValue = TopFrame;
- RefreshTimeline();
- }
- }
-
- private void b_FrameAdvance_Click(object sender, EventArgs e)
- {
- MainGUI.Timeline_PendingFrameAdvance = true;
- }
-
- private void b_FrameBack_Click(object sender, EventArgs e)
- {
- FrameRewind();
- }
-
- public void FrameRewind()
- {
- int row = frameIndex - TopFrame;
- int rowm1 = row - 1;
- frameIndex -= 2;
- if (frameIndex < 0)
- {
- frameIndex = 0;
- }
-
- UpdateTimelineRowStatus(row);
- RedrawTimelineRow(row, false);
-
- MainGUI.Timeline_LoadState = GrabMostRecentSavestate(); // This function updates frameIndex
- MainGUI.Timeline_PendingLoadState = true;
- MainGUI.Timeline_PendingFrameNumber = frameIndex;
- if (frameIndex == 0)
- {
- UpdateTimelineRowStatus(0);
- RedrawTimelineRow(0, false);
- MainGUI.Timeline_PendingResetScreen = true;
- }
- else
- {
- MainGUI.Timeline_PendingFrameNumber = frameIndex;
- MainGUI.Timeline_PendingFrameAdvance = true;
- }
- if (frameIndex != rowm1)
- {
- MainGUI.Timeline_AutoPlayUntilTarget = true;
- MainGUI.Timeline_AutoPlayTarget = rowm1;
- }
- }
-
- public void FrameAdvance()
- {
- frameIndex++;
- if (frameIndex > LagFrames.Count)
- {
- LagFrames.Add(MainGUI.EMU.LagFrame);
- }
- else
- {
- LagFrames[frameIndex - 1] = MainGUI.EMU.LagFrame;
- }
- if (frameIndex > Resets.Count)
- {
- Resets.Add(false);
- }
- if (frameIndex > frameEmulated)
- {
- frameEmulated = frameIndex;
- }
- if (frameIndex > highestFrameEmulatedEver)
- {
- highestFrameEmulatedEver = frameIndex;
- }
- if (frameIndex == TimelineSavestates.Count)
- {
- // create a savestate for the previous frame.
- if (cb_SavestateEveryFrame.Checked || frameIndex % AutoSavestateThreshold == 0)
- {
- List state = MainGUI.EMU.SaveState();
- TimelineSavestates.Add(state);
- }
- else
- {
- List state = new List();
- TimelineSavestates.Add(state);
- }
-
- if (TimelineSavestates[frameIndex].Count > 0)
- {
- // if this savestate is not empty
- List state = new List();
- TimelineTempSavestates.Add(state);
- }
- else
- {
- // if this savestate is empty
- List state = MainGUI.EMU.SaveState();
- TimelineTempSavestates.Add(state);
- }
-
- //TEMPRerecordTracker.Add(Rerecords);
-
- }
- else if (TimelineSavestates[frameIndex].Count != SavestateLength && cb_SavestateEveryFrame.Checked)
- {
- List state = MainGUI.EMU.SaveState();
- TimelineSavestates[frameIndex] = state;
- //TEMPRerecordTracker[frameIndex] = Rerecords;
- }
- TrimTempSavestates();
-
- while (frameIndex >= Inputs.Count)
- {
- Inputs.Add(0);
- Resets.Add(false);
- MethodInvoker upd = delegate
- {
- timelineScrollbar.Maximum = Inputs.Count + 38;
- };
- this.BeginInvoke(upd);
- }
- int row = frameIndex - TopFrame;
-
- bool didFullRefresh = false;
-
- if (cb_FollowCursor.Checked)
- {
- if (row >= TimelineGrid.Count || row > FollowDistance)
- {
- TopFrame = frameIndex - FollowDistance;
- if (TopFrame != ScrollbarValue && TopFrame >= 0)
- {
- didFullRefresh = true;
- RefreshTimeline();
- MethodInvoker upd = delegate
- {
- ScrollbarValue = TopFrame;
- timelineScrollbar.Value = TopFrame;
- };
- this.Invoke(upd);
- }
- }
- }
- if (!didFullRefresh)
- {
- UpdateTimelineRowStatus(row - 1);
- RedrawTimelineRow(row - 1, false);
- UpdateTimelineRowStatus(row);
- RedrawTimelineRow(row, false);
- }
- }
-
- private void loadTASToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- OpenFileDialog ofd = new OpenFileDialog()
- {
- FileName = "",
- Filter =
- "TriCNES TAS File (.3c2, .3c3)|*.3c2;*.3c3" +
- "|Bizhawk Movie (.bk2)|*.bk2" +
- "|Bizhawk TAStudio (.tasproj)|*.tasproj" +
- "|FCEUX Movie (.fm2)|*.fm2" +
- "|FCEUX TAS Editor (.fm3)|*.fm3" +
- "|Famtastia Movie (.fmv)|*.fmv" +
- "|Replay Device (.r08)|*.r08" +
- "|All TAS Files (.3c2, .3c3, .bk2, .tasproj, .fm2, .fm3, .fmv, .r08)|*.3c2;*.3c3;*.bk2;*.tasproj;*.fm2;*.fm3;*.fmv;*.r08",
- Title = "Select file",
- InitialDirectory = InitDirectory
- };
- if (ofd.ShowDialog() == DialogResult.OK)
- {
- frameIndex = 0;
- Inputs = MainGUI.ParseTasFile(ofd.FileName, out Resets);
- string extension = Path.GetExtension(ofd.FileName);
- timelineScrollbar.Maximum = Inputs.Count + 38;
- MainGUI.Timeline_PendingHardReset = true;
-
- if (extension != ".3c3")
- {
- LagFrames = new List();
- TimelineSavestates = new List>();
- TimelineTempSavestates = new List>();
- // savestates are initialized in the Timeline_PendingArbitrarySavestate
- MainGUI.Timeline_PendingArbitrarySavestate = true;
-
- ScrollbarValue = 0;
- timelineScrollbar.Value = 0;
- TopFrame = 0;
- highestFrameEmulatedEver = 0;
- frameEmulated = 0;
- }
- if (extension == ".3c2")
- {
- byte[] b = File.ReadAllBytes(ofd.FileName); // Terribly inefficient to load the entire file a second time, but whatever.
- ClockFiltering = (b[0] & 1) == 1;
- }
- if (extension == ".3c3")
- {
- byte[] b = File.ReadAllBytes(ofd.FileName); // Terribly inefficient to load the entire file a second time, but whatever.
- ClockFiltering = (b[15] & 1) == 1;
- }
-
- RefreshTimeline();
- GC.Collect();
- }
- }
-
- private void saveTASToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- SaveFileDialog sfd = new SaveFileDialog()
- {
- FileName = "",
- Filter = "TriCNES TAS File (.3c2)|*.3c2",
- Title = "Save a .3c2 TAS File",
- InitialDirectory = InitDirectory
- };
- sfd.ShowDialog();
-
- if (sfd.FileName != "")
- {
- FileStream fs = (FileStream)sfd.OpenFile();
- // Determine if controller 2 is used.
- bool UseController2 = false;
- for (int i = 0; i < Inputs.Count; i++)
- {
- if ((Inputs[i] & 0xFF00) != 0)
- {
- UseController2 = true;
- break;
- }
- }
- // Determine if the RESET button is used.
- bool UseResets = false;
- for (int i = 0; i < Inputs.Count; i++)
- {
- if (i < Resets.Count && Resets[i])
- {
- UseResets = true;
- break;
- }
- }
- byte Header = 0;
- Header |= (byte)(ClockFiltering ? 1 : 0);
- Header |= (byte)(UseController2 ? 2 : 0);
- Header |= (byte)(UseResets ? 4 : 0);
- fs.WriteByte(Header);
- for (int i = 0; i < Inputs.Count; i++)
- {
- fs.WriteByte((byte)Inputs[i]);
- if (UseController2) { fs.WriteByte((byte)(Inputs[i] >> 8)); }
- if (UseResets) { fs.WriteByte((byte)(Resets[i] ? 0x80 : 0)); }
- }
- fs.Close();
- }
- }
-
- private void saveWithSavestatesToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- SaveFileDialog sfd = new SaveFileDialog()
- {
- FileName = "",
- Filter = "TriCNES TAS File (.3c3)|*.3c3",
- Title = "Save a .3c3 TAS File",
- InitialDirectory = InitDirectory
- };
- sfd.ShowDialog();
-
- if (sfd.FileName != "")
- {
- FileStream fs = (FileStream)sfd.OpenFile();
- // save the length of the savestates
- fs.WriteByte((byte)SavestateLength);
- fs.WriteByte((byte)(SavestateLength >> 8));
- fs.WriteByte((byte)(SavestateLength >> 16));
- fs.WriteByte((byte)(SavestateLength >> 24));
-
- // save the number of rerecords. (I personally don't care about this statistic, but other people do, so I'll add it!)
- fs.WriteByte((byte)Rerecords);
- fs.WriteByte((byte)(Rerecords >> 8));
- fs.WriteByte((byte)(Rerecords >> 16));
- fs.WriteByte((byte)(Rerecords >> 24));
-
- // save the length of the TAS
- fs.WriteByte((byte)Inputs.Count);
- fs.WriteByte((byte)(Inputs.Count >> 8));
- fs.WriteByte((byte)(Inputs.Count >> 16));
- fs.WriteByte((byte)(Inputs.Count >> 24));
-
- fs.WriteByte(0); // 3 currently unused bytes.
- fs.WriteByte(0);
- fs.WriteByte(0);
-
- // Determine if controller 2 is used.
- bool UseController2 = false;
- for (int i = 0; i < Inputs.Count; i++)
- {
- if ((Inputs[i] & 0xFF00) != 0)
- {
- UseController2 = true;
- break;
- }
- }
- // Determine if the RESET button is used.
- bool UseResets = false;
- for (int i = 0; i < Inputs.Count; i++)
- {
- if (i < Resets.Count && Resets[i])
- {
- UseResets = true;
- break;
- }
- }
- byte Header15 = 0;
- Header15 |= (byte)(ClockFiltering ? 1 : 0);
- Header15 |= (byte)(UseController2 ? 2 : 0);
- Header15 |= (byte)(UseResets ? 4 : 0);
- fs.WriteByte(Header15);
-
- for (int i = 0; i < Inputs.Count; i++)
- {
- fs.WriteByte((byte)Inputs[i]);
- if (UseController2) { fs.WriteByte((byte)(Inputs[i] >> 8)); }
- if (UseResets) { fs.WriteByte((byte)((Resets[i] ? 0x80 : 0) | (i < LagFrames.Count ? (LagFrames[i] ? 1 : 0) : 0))); }
- else { fs.WriteByte((byte)(i < LagFrames.Count ? (LagFrames[i] ? 1 : 0) : 0)); }
- }
-
- for (int i = 0; i < TimelineSavestates.Count; i++)
- {
- if (TimelineSavestates[i].Count != 0)
- {
- // save the frame index
- fs.WriteByte((byte)i);
- fs.WriteByte((byte)(i >> 8));
- fs.WriteByte((byte)(i >> 16));
- fs.WriteByte((byte)(i >> 24));
- // save every byte of the savestate
- for (int j = 0; j < SavestateLength; j++)
- {
- fs.WriteByte(TimelineSavestates[i][j]);
- }
- }
- }
-
- fs.Close();
- }
- }
-
- private void exportTor08ToolStripMenuItem_Click(object sender, EventArgs e)
- {
- string InitDirectory = AppDomain.CurrentDomain.BaseDirectory;
- if (Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + @"tas\"))
- {
- InitDirectory += @"tas\";
- }
- SaveFileDialog sfd = new SaveFileDialog()
- {
- FileName = "",
- Filter = "Replay Device (.r08)|*.r08",
- Title = "Save a .r08 TAS File",
- InitialDirectory = InitDirectory
- };
- sfd.ShowDialog();
-
- if (sfd.FileName != "")
- {
- FileStream fs = (FileStream)sfd.OpenFile();
-
- if (ClockFiltering)
- {
- // the .r08 file format is identical to my input list.
- for (int i = 0; i < Inputs.Count; i++)
- {
- fs.WriteByte((byte)Inputs[i]);
- fs.WriteByte((byte)(Inputs[i] >> 8));
- }
- }
- else
- {
- if (LagFrames.Count < Inputs.Count)
- {
- MessageBox.Show("The .r08 exporter needs to know which frames are lag frames.\nOnly frames that have been emulated on the timeline will be exported.");
- }
- for (int i = 0; i < Inputs.Count; i++)
- {
- // the .r08 file format doesn't include lag frames.
- if (i < LagFrames.Count && !LagFrames[i])
- {
- fs.WriteByte((byte)Inputs[i]);
- fs.WriteByte((byte)(Inputs[i] >> 8));
- }
- }
- }
-
- fs.Close();
- }
-
- }
-
- bool Paused = true;
-
- private void b_play_Click(object sender, EventArgs e)
- {
- Paused = !Paused;
- b_play.Text = Paused ? "Paused" : "Running";
- MainGUI.Timeline_PendingResume = !Paused;
- MainGUI.Timeline_PendingPause = Paused;
- if (Paused)
- {
- GC.Collect();
- }
- }
-
- private void deleteFrameToolStripMenuItem_Click(object sender, EventArgs e)
- {
-
- Inputs.RemoveAt(frameIndex);
- int HighlightedFrame = frameIndex;
- if (HighlightedFrame < frameEmulated)
- {
- MarkStale(HighlightedFrame);
- }
- timelineScrollbar.Maximum = Inputs.Count + 38;
- RefreshTimeline();
- }
-
- private void insertFrameToolStripMenuItem_Click(object sender, EventArgs e)
- {
- Inputs.Insert(frameIndex, 0);
- int HighlightedFrame = frameIndex;
- if (HighlightedFrame < frameEmulated)
- {
- MarkStale(HighlightedFrame);
- }
- timelineScrollbar.Maximum = Inputs.Count + 38;
- RefreshTimeline();
- }
-
- public bool Player2()
- {
- return cb_player2.Checked;
- }
-
- private void truncateMovieToolStripMenuItem_Click(object sender, EventArgs e)
- {
- Inputs.RemoveRange(frameIndex + 1, Inputs.Count - (frameIndex + 1));
- if (TimelineSavestates.Count > frameIndex)
- {
- TimelineSavestates.RemoveRange(frameIndex + 1, TimelineSavestates.Count - (frameIndex + 1));
- }
- if (TimelineTempSavestates.Count > frameIndex)
- {
- TimelineTempSavestates.RemoveRange(frameIndex + 1, TimelineTempSavestates.Count - (frameIndex + 1));
- }
- if (LagFrames.Count > frameIndex)
- {
- LagFrames.RemoveRange(frameIndex, LagFrames.Count - (frameIndex));
- }
- System.GC.Collect();
- highestFrameEmulatedEver = frameIndex;
- frameEmulated = frameIndex;
- timelineScrollbar.Maximum = Inputs.Count + 38;
- RefreshTimeline();
- }
-
- private void b_JumptoCursor_Click(object sender, EventArgs e)
- {
- int scroll = frameIndex - FollowDistance;
- if (scroll < 0)
- {
- scroll = 0;
- }
- timelineScrollbar.Value = scroll;
- TopFrame = scroll;
- RefreshTimeline();
- }
-
- public void RefreshTimeline()
- {
- // redraw the entire timeline's bitmap.
- for (int i = 0; i < 40; i++)
- {
- int frame = TopFrame + i;
- RecalculateTimelineRow(i, frame < Inputs.Count ? Inputs[frame] : (ushort)0);
- TimelineGrid[i][17].Checked = false;
- if (frame >= 0 && frame < Resets.Count)
- {
- TimelineGrid[i][17].Checked = Resets[frame];
- }
-
- RedrawTimelineRow(i, true);
- }
- MethodInvoker upd = delegate
- {
- pb_Timeline.Image = timelineBitmap;
- pb_Timeline.Update();
- };
- this.Invoke(upd);
- }
- public void RedrawTimelineRow(int row, bool batch)
- {
- if (row < 0 || row >= TimelineGrid.Count)
- {
- return;
- }
-
- MethodInvoker upd = delegate
- {
- int rowp1 = row + 1;
- Rectangle[] GridOverlay = new Rectangle[]
- {
- new Rectangle(0,rowp1*16,80,16),
- new Rectangle(80 + 16*0,rowp1*16,16,16),
- new Rectangle(80 + 16*1,rowp1*16,16,16),
- new Rectangle(80 + 16*2,rowp1*16,16,16),
- new Rectangle(80 + 16*3,rowp1*16,16,16),
- new Rectangle(80 + 16*4,rowp1*16,16,16),
- new Rectangle(80 + 16*5,rowp1*16,16,16),
- new Rectangle(80 + 16*6,rowp1*16,16,16),
- new Rectangle(80 + 16*7,rowp1*16,16,16),
- new Rectangle(80 + 16*8,rowp1*16,16,16),
- new Rectangle(80 + 16*9,rowp1*16,16,16),
- new Rectangle(80 + 16*10,rowp1*16,16,16),
- new Rectangle(80 + 16*11,rowp1*16,16,16),
- new Rectangle(80 + 16*12,rowp1*16,16,16),
- new Rectangle(80 + 16*13,rowp1*16,16,16),
- new Rectangle(80 + 16*14,rowp1*16,16,16),
- new Rectangle(80 + 16*15,rowp1*16,16,16),
- new Rectangle(80 + 16*16,rowp1*16,16,16)
- };
- if (frameIndex - TopFrame == row)
- {
- G.FillRectangle(Brush_HighlightedCell, new Rectangle(0, rowp1 * 16, 80 + 16 * 17, 16));
- }
- else if (!TimelineGrid[row][0].Emulated)
- {
- G.FillRectangle(Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16));
- G.FillRectangle(Brush_WhiteCellP1, new Rectangle(80, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(Brush_WhiteCellP2, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(Brush_WhiteCellP1, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16));
- }
- else if (TimelineGrid[row][0].Stale)
- {
- G.FillRectangle(Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1_Stale : Brush_GreenCellP1_Stale, new Rectangle(80, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP2_Stale : Brush_GreenCellP2_Stale, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1_Stale : Brush_GreenCellP1_Stale, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16));
- }
- else
- {
- G.FillRectangle(TimelineSavestates[row + TopFrame].Count == SavestateLength ? Brush_LeftColumn_Saved : TimelineTempSavestates[row + TopFrame].Count == SavestateLength ? Brush_LeftColumn_TempSaved : Brush_LeftColumn, new Rectangle(0, rowp1 * 16, 80, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1 : Brush_GreenCellP1, new Rectangle(80, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP2 : Brush_GreenCellP2, new Rectangle(80 + 16 * 8, rowp1 * 16, 16 * 8, 16));
- G.FillRectangle(TimelineGrid[row][0].LagFrame ? Brush_RedCellP1 : Brush_GreenCellP1, new Rectangle(80 + 16 * 16, rowp1 * 16, 16, 16));
- }
-
- G.DrawRectangles(Pens.Black, GridOverlay);
- string rownum = (row + TopFrame).ToString();
- //if(row + TopFrame < TEMPRerecordTracker.Count)
- //{
- // rownum += " (" + TEMPRerecordTracker[row + TopFrame].ToString() + ")";
- //}
- G.DrawString(rownum, Font_Consolas, Brushes.Black, 0, rowp1 * 16);
- if (TimelineGrid[row][1].Checked) { G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 0, rowp1 * 16); }
- if (TimelineGrid[row][2].Checked) { G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 1, rowp1 * 16); }
- if (TimelineGrid[row][3].Checked) { G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 2, rowp1 * 16); }
- if (TimelineGrid[row][4].Checked) { G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 3, rowp1 * 16); }
- if (TimelineGrid[row][5].Checked) { G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 4, rowp1 * 16); }
- if (TimelineGrid[row][6].Checked) { G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 5, rowp1 * 16); }
- if (TimelineGrid[row][7].Checked) { G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 6, rowp1 * 16); }
- if (TimelineGrid[row][8].Checked) { G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 7, rowp1 * 16); }
- if (TimelineGrid[row][9].Checked) { G.DrawString("A", Font_Consolas, Brushes.Black, 80 + 16 * 8, rowp1 * 16); }
- if (TimelineGrid[row][10].Checked) { G.DrawString("B", Font_Consolas, Brushes.Black, 80 + 16 * 9, rowp1 * 16); }
- if (TimelineGrid[row][11].Checked) { G.DrawString("s", Font_Consolas, Brushes.Black, 80 + 16 * 10, rowp1 * 16); }
- if (TimelineGrid[row][12].Checked) { G.DrawString("S", Font_Consolas, Brushes.Black, 80 + 16 * 11, rowp1 * 16); }
- if (TimelineGrid[row][13].Checked) { G.DrawString("U", Font_Consolas, Brushes.Black, 80 + 16 * 12, rowp1 * 16); }
- if (TimelineGrid[row][14].Checked) { G.DrawString("D", Font_Consolas, Brushes.Black, 80 + 16 * 13, rowp1 * 16); }
- if (TimelineGrid[row][15].Checked) { G.DrawString("L", Font_Consolas, Brushes.Black, 80 + 16 * 14, rowp1 * 16); }
- if (TimelineGrid[row][16].Checked) { G.DrawString("R", Font_Consolas, Brushes.Black, 80 + 16 * 15, rowp1 * 16); }
- if (TimelineGrid[row][17].Checked) { G.DrawString("r", Font_Consolas, Brushes.Black, 80 + 16 * 16, rowp1 * 16); }
- if (!batch)
- {
- pb_Timeline.Image = timelineBitmap;
- pb_Timeline.Update();
- }
- };
- this.BeginInvoke(upd);
-
-
- }
- public void RecalculateTimelineRow(int row, ushort input)
- {
- // redraw one row of the timeline
- if (row < 0 || row >= TimelineGrid.Count)
- {
- return;
- }
- TimelineGrid[row][8].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][7].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][6].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][5].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][4].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][3].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][2].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][1].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][16].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][15].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][14].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][13].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][12].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][11].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][10].Checked = (input & 1) == 1;
- input >>= 1;
- TimelineGrid[row][9].Checked = (input & 1) == 1;
-
- UpdateTimelineRowStatus(row);
- }
- public void UpdateTimelineRowStatus(int row)
- {
- if (row < 0 || row >= TimelineGrid.Count)
- {
- return;
- }
-
- // redraw one row of the timeline
- int frame = (TopFrame + row);
-
- bool Emulated = frame < highestFrameEmulatedEver;
- bool Stale = Emulated && (frame > frameEmulated);
- bool LagFrame = false;
- if (LagFrames.Count > frame)
- {
- LagFrame = Emulated && (LagFrames[frame]);
- }
- TimelineGrid[row][0].Emulated = Emulated;
- TimelineGrid[row][0].Stale = Stale;
- TimelineGrid[row][0].LagFrame = LagFrame;
- }
- int FollowDistance = 20;
- private void tb_FollowDistance_Scroll(object sender, EventArgs e)
- {
- FollowDistance = tb_FollowDistance.Value;
- }
-
- public bool SavestateEveryFrame()
- {
- return cb_SavestateEveryFrame.Checked;
- }
- public bool RecordInputs()
- {
- return cb_RecordInputs.Checked;
- }
- List GrabMostRecentSavestate()
- {
- int l = TimelineSavestates[frameIndex].Count;
- if (l != SavestateLength)
- {
- l = TimelineTempSavestates[frameIndex].Count;
- if (l == SavestateLength)
- {
- return TimelineTempSavestates[frameIndex];
- }
- }
- bool temp = false;
- while (l != SavestateLength)
- {
- frameIndex--;
- l = TimelineSavestates[frameIndex].Count;
- if (l != SavestateLength)
- {
- l = TimelineTempSavestates[frameIndex].Count;
- if (l == SavestateLength)
- {
- temp = true;
- break;
- }
- }
- }
- return temp ? TimelineTempSavestates[frameIndex] : TimelineSavestates[frameIndex];
- }
-
- List GrabMostRecentSavestate(int TargetFrame, out bool HitTargetFrame)
- {
- if (frameIndex == TargetFrame)
- {
- HitTargetFrame = true;
- return null;
- }
- bool temp = false;
- int l = TimelineSavestates[frameIndex].Count;
- if (l != SavestateLength)
- {
- l = TimelineTempSavestates[frameIndex].Count;
- if (l == SavestateLength)
- {
- HitTargetFrame = false;
- return TimelineTempSavestates[frameIndex];
- }
- }
- while (l != SavestateLength)
- {
- frameIndex--;
- if (frameIndex == TargetFrame)
- {
- HitTargetFrame = true;
- return null;
- }
- l = TimelineSavestates[frameIndex].Count;
- if (l != SavestateLength)
- {
- l = TimelineTempSavestates[frameIndex].Count;
- if (l == SavestateLength)
- {
- temp = true;
- break;
- }
- }
- }
- HitTargetFrame = false;
- return temp ? TimelineTempSavestates[frameIndex] : TimelineSavestates[frameIndex];
- }
-
- private void savestateThisFrameToolStripMenuItem_Click(object sender, EventArgs e)
- {
- if (frameIndex == TimelineSavestates.Count)
- {
- List state = MainGUI.EMU.SaveState();
- TimelineSavestates.Add(state);
- if (TimelineTempSavestates[frameIndex].Count > 0)
- {
- TimelineTempSavestates[frameIndex] = new List(); // remove temp savestate for this frame
- }
- }
- else
- {
- List state = MainGUI.EMU.SaveState();
- TimelineSavestates[frameIndex] = state;
- if (TimelineTempSavestates[frameIndex].Count > 0)
- {
- TimelineTempSavestates[frameIndex] = new List(); // remove temp savestate for this frame
- }
- GC.Collect();
- }
- }
-
- public void TrimTempSavestates()
- {
- int deletion = frameIndex - TempSavestates;
- if (deletion >= 0 && deletion < TimelineTempSavestates.Count && TimelineTempSavestates[deletion].Count > 0)
- {
- TimelineTempSavestates[deletion] = new List(); // remove temp savestate for this frame
- int row = deletion - TopFrame;
- if (row >= 0 && row < 40)
- {
- UpdateTimelineRowStatus(row);
- RedrawTimelineRow(row, false);
- }
- }
- }
-
- private void tb_FilterForNumbers(object sender, KeyPressEventArgs e)
- {
- if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
- {
- e.Handled = true;
- }
- }
-
- private void tb_AutoSavestateThreshold_TextChanged(object sender, EventArgs e)
- {
- int i = 0;
- if (int.TryParse(tb_AutoSavestateThreshold.Text, out i))
- {
- AutoSavestateThreshold = i;
- }
-
- }
-
- private void tb_TempSavestates_TextChanged(object sender, EventArgs e)
- {
- int i = 0;
- if (int.TryParse(tb_TempSavestates.Text, out i))
- {
- TempSavestates = i;
- }
- }
-
- public void ChangePlayPauseButtonText(string str)
- {
- MethodInvoker upd = delegate
- {
- b_play.Text = str;
- b_play.Update();
- };
- this.Invoke(upd);
- }
-
- public bool ClockFiltering;
- private void perVBlankToolStripMenuItem_Click(object sender, EventArgs e)
- {
- perVBlankToolStripMenuItem.Checked = true;
- perControllerStrobeToolStripMenuItem.Checked = false;
- ClockFiltering = false;
- // reset the TAS and mark everything as stale!
- ResetTASAndMarkEverythingStale();
- MainGUI.Timeline_PendingClockFiltering = false;
- RefreshTopOfTimeline();
- }
-
- private void perControllerStrobeToolStripMenuItem_Click(object sender, EventArgs e)
- {
- perVBlankToolStripMenuItem.Checked = false;
- perControllerStrobeToolStripMenuItem.Checked = true;
- ClockFiltering = true;
- ResetTASAndMarkEverythingStale();
- MainGUI.Timeline_PendingClockFiltering = true;
- RefreshTopOfTimeline();
- }
-
- void ResetTASAndMarkEverythingStale()
- {
- frameIndex = 0;
- timelineScrollbar.Maximum = Inputs.Count + 38;
- MainGUI.Timeline_PendingHardReset = true;
-
- LagFrames = new List();
- TimelineSavestates = new List>();
- TimelineTempSavestates = new List>();
- // savestates are initialized in the Timeline_PendingArbitrarySavestate
- MainGUI.Timeline_PendingArbitrarySavestate = true;
-
- ScrollbarValue = 0;
- timelineScrollbar.Value = 0;
- TopFrame = 0;
- highestFrameEmulatedEver = 0;
- frameEmulated = 0;
-
- RefreshTimeline();
- }
- }
-}
diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.resx
deleted file mode 100644
index 33d26909..00000000
--- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTASTimeline.resx
+++ /dev/null
@@ -1,222 +0,0 @@
-
-
-
-
-