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 @@

- Build Status License: GPL-3.0-or-later Version Rust: 1.96
+ Build Status License: GPL-3.0-or-later Version Rust: 1.99
AccuracyCoin nestest Mapper families Try in browser
Platform

@@ -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 -![Screenshot](https://github.com/user-attachments/assets/56a25c5d-5c2f-493f-85bd-90bb192b1322) - -![Screenshot2](https://github.com/user-attachments/assets/5e6771fe-0696-4e27-9fdc-b16fd1b407ef) - -![Screenshot3](https://github.com/user-attachments/assets/1689f379-7eb8-445e-9632-81c3a3de2301) 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - 132, 17 - - - 331, 17 - - - In order to move backwards on the timeline, the emulator needs to keep savestetes for earlier frames. -Savestates require copies of every variable in the emulator, and can be a bit RAM consuming. -These temporary savestates will record the previous 'n' frames before being deleted, allowing you to fix mistakes in the previous 'n' frames. -You could also choose to create a savestate on every frame if you would prefer. - - - - In order to move backwards on the timeline, the emulator needs to keep savestetes for earlier frames. -Savestates require copies of every variable in the emulator, and can be a bit RAM consuming. -These temporary savestates will record the previous 'n' frames before being deleted, allowing you to fix mistakes in the previous 'n' frames. -You could also choose to create a savestate on every frame if you would prefer. - - - - - 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/TriCTraceLogger.Designer.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.Designer.cs deleted file mode 100644 index 70c6a35d..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.Designer.cs +++ /dev/null @@ -1,170 +0,0 @@ -namespace TriCNES -{ - partial class TriCTraceLogger - { - /// - /// 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(TriCTraceLogger)); - this.b_ToggleButton = new System.Windows.Forms.CheckBox(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.rtb_TraceLog = new System.Windows.Forms.RichTextBox(); - this.cb_LogInRange = new System.Windows.Forms.CheckBox(); - this.tb_RangeLow = new System.Windows.Forms.TextBox(); - this.tb_RangeHigh = new System.Windows.Forms.TextBox(); - this.cb_ClearEveryFrame = new System.Windows.Forms.CheckBox(); - this.cb_LogPPU = new System.Windows.Forms.CheckBox(); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); - // - // b_ToggleButton - // - this.b_ToggleButton.Appearance = System.Windows.Forms.Appearance.Button; - this.b_ToggleButton.AutoSize = true; - this.b_ToggleButton.Location = new System.Drawing.Point(12, 497); - this.b_ToggleButton.Name = "b_ToggleButton"; - this.b_ToggleButton.Size = new System.Drawing.Size(80, 23); - this.b_ToggleButton.TabIndex = 0; - this.b_ToggleButton.Text = "Start Logging"; - this.b_ToggleButton.UseVisualStyleBackColor = true; - this.b_ToggleButton.CheckedChanged += new System.EventHandler(this.b_ToggleButton_CheckedChanged); - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.rtb_TraceLog); - this.groupBox1.Location = new System.Drawing.Point(12, 27); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(931, 464); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Trace Log"; - // - // rtb_TraceLog - // - this.rtb_TraceLog.DetectUrls = false; - this.rtb_TraceLog.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.rtb_TraceLog.Location = new System.Drawing.Point(7, 20); - this.rtb_TraceLog.Name = "rtb_TraceLog"; - this.rtb_TraceLog.Size = new System.Drawing.Size(918, 438); - this.rtb_TraceLog.TabIndex = 56; - this.rtb_TraceLog.Text = ""; - this.rtb_TraceLog.WordWrap = false; - // - // cb_LogInRange - // - this.cb_LogInRange.AutoSize = true; - this.cb_LogInRange.Location = new System.Drawing.Point(165, 498); - this.cb_LogInRange.Name = "cb_LogInRange"; - this.cb_LogInRange.Size = new System.Drawing.Size(139, 17); - this.cb_LogInRange.TabIndex = 2; - this.cb_LogInRange.Text = "Only Log Within Range:"; - this.cb_LogInRange.UseVisualStyleBackColor = true; - this.cb_LogInRange.CheckedChanged += new System.EventHandler(this.cb_LogInRange_CheckedChanged); - // - // tb_RangeLow - // - this.tb_RangeLow.Enabled = false; - this.tb_RangeLow.Location = new System.Drawing.Point(298, 495); - this.tb_RangeLow.MaxLength = 4; - this.tb_RangeLow.Name = "tb_RangeLow"; - this.tb_RangeLow.ReadOnly = true; - this.tb_RangeLow.Size = new System.Drawing.Size(32, 20); - this.tb_RangeLow.TabIndex = 3; - this.tb_RangeLow.Text = "0000"; - this.tb_RangeLow.TextChanged += new System.EventHandler(this.tb_RangeLow_TextChanged); - // - // tb_RangeHigh - // - this.tb_RangeHigh.Enabled = false; - this.tb_RangeHigh.Location = new System.Drawing.Point(336, 495); - this.tb_RangeHigh.MaxLength = 4; - this.tb_RangeHigh.Name = "tb_RangeHigh"; - this.tb_RangeHigh.ReadOnly = true; - this.tb_RangeHigh.Size = new System.Drawing.Size(32, 20); - this.tb_RangeHigh.TabIndex = 4; - this.tb_RangeHigh.Text = "FFFF"; - this.tb_RangeHigh.TextChanged += new System.EventHandler(this.tb_RangeHigh_TextChanged); - // - // cb_ClearEveryFrame - // - this.cb_ClearEveryFrame.AutoSize = true; - this.cb_ClearEveryFrame.Checked = true; - this.cb_ClearEveryFrame.CheckState = System.Windows.Forms.CheckState.Checked; - this.cb_ClearEveryFrame.Location = new System.Drawing.Point(165, 521); - this.cb_ClearEveryFrame.Name = "cb_ClearEveryFrame"; - this.cb_ClearEveryFrame.Size = new System.Drawing.Size(133, 17); - this.cb_ClearEveryFrame.TabIndex = 5; - this.cb_ClearEveryFrame.Text = "Clear Log Every Frame"; - this.cb_ClearEveryFrame.UseVisualStyleBackColor = true; - // - // cb_LogPPU - // - this.cb_LogPPU.AutoSize = true; - this.cb_LogPPU.Location = new System.Drawing.Point(165, 548); - this.cb_LogPPU.Name = "cb_LogPPU"; - this.cb_LogPPU.Size = new System.Drawing.Size(103, 17); - this.cb_LogPPU.TabIndex = 6; - this.cb_LogPPU.Text = "Log PPU Cycles"; - this.cb_LogPPU.UseVisualStyleBackColor = true; - // - // TriCTraceLogger - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(953, 577); - this.Controls.Add(this.cb_LogPPU); - this.Controls.Add(this.cb_ClearEveryFrame); - this.Controls.Add(this.tb_RangeHigh); - this.Controls.Add(this.tb_RangeLow); - this.Controls.Add(this.cb_LogInRange); - this.Controls.Add(this.groupBox1); - this.Controls.Add(this.b_ToggleButton); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MaximumSize = new System.Drawing.Size(969, 616); - this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(969, 616); - this.Name = "TriCTraceLogger"; - this.Text = "Trace Logger"; - this.groupBox1.ResumeLayout(false); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.CheckBox b_ToggleButton; - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.RichTextBox rtb_TraceLog; - private System.Windows.Forms.CheckBox cb_LogInRange; - private System.Windows.Forms.TextBox tb_RangeLow; - private System.Windows.Forms.TextBox tb_RangeHigh; - private System.Windows.Forms.CheckBox cb_ClearEveryFrame; - private System.Windows.Forms.CheckBox cb_LogPPU; - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.cs deleted file mode 100644 index 389f6b11..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace TriCNES -{ - public partial class TriCTraceLogger : Form - { - public TriCNESGUI MainGUI; - public bool Logging; - public TriCTraceLogger() - { - InitializeComponent(); - FormClosing += new FormClosingEventHandler(TriCTraceLogger_Closing); - } - - private void TriCTraceLogger_Closing(Object sender, FormClosingEventArgs e) - { - if (MainGUI != null) - { - MainGUI.TraceLogger = null; - } - Dispose(); - } - - public void Init() - { - rtb_TraceLog.SelectionTabs = new int[] { 0, 56, 56 * 2, 56 * 3, 56 * 4, 56 * 5, 56 * 6, 56 * 7, 56 * 8, 56 * 9, 56 * 10 }; - } - String Log; - public void Update() - { - if (MainGUI.EMU.DebugLog != null) - { - Log = MainGUI.EMU.DebugLog.ToString(); - MethodInvoker upd = delegate - { - rtb_TraceLog.Text = Log; - }; - try - { - this.Invoke(upd); - } - catch (Exception e) - { - - } - } - } - - private void b_ToggleButton_CheckedChanged(object sender, EventArgs e) - { - Logging = b_ToggleButton.Checked; - b_ToggleButton.Text = Logging ? "Stop Logging" : "Start Logging"; - } - - private void cb_LogInRange_CheckedChanged(object sender, EventArgs e) - { - tb_RangeHigh.ReadOnly = !cb_LogInRange.Checked; - tb_RangeHigh.Enabled = cb_LogInRange.Checked; - tb_RangeLow.ReadOnly = !cb_LogInRange.Checked; - tb_RangeLow.Enabled = cb_LogInRange.Checked; - } - - private void tb_RangeLow_TextChanged(object sender, EventArgs e) - { - RangeLow = 0; - ushort.TryParse(tb_RangeLow.Text, System.Globalization.NumberStyles.HexNumber, null, out RangeLow); - } - - private void tb_RangeHigh_TextChanged(object sender, EventArgs e) - { - RangeHigh = 0xFFFF; - ushort.TryParse(tb_RangeHigh.Text, System.Globalization.NumberStyles.HexNumber, null, out RangeHigh); - } - public ushort RangeLow; - public ushort RangeHigh; - - public bool OnlyDebugInRange() - { - return cb_LogInRange.Checked; - } - public bool ClearEveryFrame() - { - return cb_ClearEveryFrame.Checked; - } - - public bool LogPPUCycles() - { - return cb_LogPPU.Checked; - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.resx b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.resx deleted file mode 100644 index a380ada5..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/forms/TriCTraceLogger.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/mappers/Mapper_AOROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_AOROM.cs deleted file mode 100644 index 8b9b874c..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_AOROM.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_AOROM : Mapper - { - // ines Mapper 7 - public byte Mapper_7_BankSelect; - public override 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[(0x8000 * (Mapper_7_BankSelect & 0x07) + (CPU_AddressIn & 0x7FFF)) & (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 override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_7_BankSelect = Input; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - return Cart.PRGROM[(0x8000 * (Mapper_7_BankSelect & 0x07) + (Address & 0x7FFF)) & (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. - } - return Cart.Emu.dataBus; - } - public override 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] = (Mapper_7_BankSelect & 0x10) != 0; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = (Mapper_7_BankSelect & 0x10) != 0; - } - } - public override 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)(((Mapper_7_BankSelect & 0x10) == 0) ? 0 : 0x400); - return Cart.Emu.VRAM[Addr]; - } - } - public override 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); } - } - State.Add(Mapper_7_BankSelect); - return State; - } - public override 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++]; } - } - Mapper_7_BankSelect = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_CNROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_CNROM.cs deleted file mode 100644 index 7f4e276e..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_CNROM.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_CNROM : Mapper - { - // ines Mapper 3 - public byte Mapper_3_CHRBank; - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_3_CHRBank = (byte)(Input & 0x3); - } - } - public override int FetchPatternAddress(ushort Address) - { - return (Mapper_3_CHRBank * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - public override 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); } - } - State.Add(Mapper_3_CHRBank); - return State; - } - public override 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++]; } - } - Mapper_3_CHRBank = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FDS.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FDS.cs deleted file mode 100644 index e860deb4..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FDS.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_FDS : Mapper - { - // The Famicom Disk System - public byte[] FDS_BIOS; - - public byte FDS_4023_IOEnable; - public byte FDS_4025_Control; - - public Mapper_FDS(byte[] fds_bios) - { - FDS_BIOS = fds_bios; - } - - public override 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(); - ushort Address = CPU_AddressIn; - - if (CPU_AddressIn >= 0xE000) - { - // read from the FDS BIOS - CPU_DataOut = FDS_BIOS[Address & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (Address >= 0x6000) - { - // read from the FDS PRG RAM - CPU_DataOut = Cart.PRGRAM[Address-0x6000]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (Address >= 4030 && Address <= 0x403F) - { - // Read from the FDS Registers - Address &= 0xF; - switch (Address) - { - default: break; - case 0: - { - // FDS Status ($4030) - CPU_DataOut = 0; - CPU_DataOut |= (byte)((FDS_4025_Control >> 3) & 1); // 4030.3 = 4025.3 - CPU_DataOut |= (byte)((Cart.FDS.DiskAddress >= Cart.FDS.Disk.Length) ? 0x40 : 0); // 4030.6 = End of Disk - CPU_DataOut |= (byte)(Cart.FDS.Status_ByteTransferFlag ? 0x80 : 0); // 4030.7 = Byte Transfer Flag - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - case 1: - { - // Disk Data Input ($4031) - CPU_DataOut = Cart.FDS.ShiftRegisterLatch; - Connector_SetUpCPUDataPins(CPU_DataOut); - Cart.FDS.Status_ByteTransferFlag = false; - Connector_IRQPin(false); //acknowledge the IRQ - } - break; - case 2: - { - // Disk Drive Status ($4032) - CPU_DataOut = 0; - if(Cart.FDS.CurrentState == DiskDrive.RamAdapterState.INSERTING) - { - CPU_DataOut |= 1; - } - if (!(((FDS_4025_Control & 2) == 0) && (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.RUNNING || Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE))) - { - CPU_DataOut |= 2; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - case 3: - { - // External Connector Input ($4033) - CPU_DataOut = 0x80; // The battery is good. - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - } - } - - return; - } - public override int FetchPatternAddress(ushort Address) - { - return Address; - } - - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x6000 && Address < 0xE000) - { - Cart.PRGRAM[Address-0x6000] = Input; - return; - } - else if (Address > 0x401F) - { - ushort tempo = (ushort)(Address & 0x40FF); - switch (tempo) - { - case 0x4023: - FDS_4023_IOEnable = Input; - if((FDS_4023_IOEnable & 1) == 0) - { - // Disable disk I/O registers - Connector_IRQPin(false); //acknowledge the IRQ - FDS_4025_Control &= 0xF3; - FDS_4025_Control |= 6; - } - return; - case 0x4024: - Cart.FDS.Status_ByteTransferFlag = false; - return; - case 0x4025: - if((FDS_4025_Control & 0x40) == 0 && (Input & 0x40) != 0) - { - Cart.FDS.lookingForEndOfGap = true; - } - FDS_4025_Control = Input; - if ((Input & 1) != 0) - { - if (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE) - { - Cart.FDS.CurrentState = DiskDrive.RamAdapterState.SPINUP; - } - } - if((FDS_4025_Control & 2) != 0) - { - // debugging: put breakpoint here - } - if ((FDS_4025_Control & 2) == 0) - { - // debugging: put breakpoint here - } - return; - } - } - } - - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xE000) - { - // read from the FDS BIOS - return FDS_BIOS[Address & 0x1FFF]; - } - else if (Address >= 0x6000) - { - // read from the FDS PRG RAM - return Cart.PRGRAM[Address - 0x6000]; - } - else if (Address >= 4030 && Address <= 0x403F) - { - // Read from the FDS Registers - Address &= 0xF; - switch (Address) - { - default: break; - case 0: - { - // FDS Status ($4030) - byte t = 0; - t |= (byte)((FDS_4025_Control >> 3) & 1); // 4030.3 = 4025.3 - t |= (byte)((Cart.FDS.DiskAddress >= Cart.FDS.Disk.Length) ? 0x40 : 0); // 4030.6 = End of Disk - t |= (byte)(Cart.FDS.Status_ByteTransferFlag ? 0x80 : 0); // 4030.7 = Byte Transfer Flag - return t; - } - break; - case 1: - { - // Disk Data Input ($4031) - return Cart.FDS.ShiftRegisterLatch; - } - break; - case 2: - { - // Disk Drive Status ($4032) - byte t = 0; - if (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.INSERTING) - { - t |= 1; - } - if (!(((FDS_4025_Control & 2) == 0) && (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.RUNNING || Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE))) - { - t |= 2; - } - return t; - } - break; - case 3: - { - // External Connector Input ($4033) - return 0x80; // The battery is good. - } - break; - } - } - return Cart.Emu.dataBus; - } - - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - if (((FDS_4025_Control >> 3) & 1) == 1) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = (((FDS_4025_Control >> 3) & 1) == 1) ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - } - } - public override 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)(((((FDS_4025_Control >> 3) & 1) == 1) ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override void FDS_ByteTransferFlag() - { - if((FDS_4025_Control & 0x80) != 0) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - public override byte FDS_Get4025() - { - return FDS_4025_Control; - } - - public override 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); } - } - State.Add(FDS_4025_Control); - State.Add((byte)Cart.FDS.clock); - State.Add((byte)(Cart.FDS.clock >> 8)); - State.Add((byte)(Cart.FDS.clock >> 16)); - State.Add((byte)(Cart.FDS.clock >> 24)); - State.Add((byte)Cart.FDS.CurrentState); - State.Add(Cart.FDS.ShiftRegister); - State.Add(Cart.FDS.ShiftRegisterLatch); - State.Add((byte)Cart.FDS.DiskAddress); - State.Add((byte)(Cart.FDS.DiskAddress >> 8)); - State.Add((byte)(Cart.FDS.DiskAddress >> 16)); - State.Add((byte)(Cart.FDS.DiskAddress >> 24)); - State.Add(Cart.FDS.DiskAddressFine); - State.Add((byte)(Cart.FDS.Status_ByteTransferFlag ? 1 : 0)); - State.Add((byte)(Cart.FDS.lookingForEndOfGap ? 1 : 0)); - - return State; - } - public override 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++]; } - } - FDS_4025_Control = State[p++]; - Cart.FDS.clock = State[p++]; - Cart.FDS.clock |= (ushort)(State[p++] << 8); - Cart.FDS.clock |= (ushort)(State[p++] << 16); - Cart.FDS.clock |= (ushort)(State[p++] << 24); - Cart.FDS.CurrentState = (DiskDrive.RamAdapterState)State[p++]; - Cart.FDS.ShiftRegister = State[p++]; - Cart.FDS.ShiftRegisterLatch = State[p++]; - Cart.FDS.DiskAddress = State[p++]; - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 8); - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 16); - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 24); - Cart.FDS.DiskAddressFine = State[p++]; - Cart.FDS.Status_ByteTransferFlag = State[p++] == 1; - Cart.FDS.lookingForEndOfGap = State[p++] == 1; - - exitIndex = p; - } - - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FME7.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FME7.cs deleted file mode 100644 index 008e01da..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_FME7.cs +++ /dev/null @@ -1,321 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_FME7 : Mapper - { - // ines Mapper 69 - public byte Mapper_69_CMD; - public byte Mapper_69_CHR_1K0; - public byte Mapper_69_CHR_1K1; - public byte Mapper_69_CHR_1K2; - public byte Mapper_69_CHR_1K3; - public byte Mapper_69_CHR_1K4; - public byte Mapper_69_CHR_1K5; - public byte Mapper_69_CHR_1K6; - public byte Mapper_69_CHR_1K7; - public byte Mapper_69_Bank_6; - public bool Mapper_69_Bank_6_isRAM; - public bool Mapper_69_Bank_6_isRAMEnabled; - public byte Mapper_69_Bank_8; - public byte Mapper_69_Bank_A; - public byte Mapper_69_Bank_C; - public byte Mapper_69_NametableMirroring; // 0 = Vertical 1 = Horizontal 2 = One Screen Mirroring from $2000 ("1ScA") 3 = One Screen Mirroring from $2400 ("1ScB") - public bool Mapper_69_EnableIRQ; - public bool Mapper_69_EnableIRQCounterDecrement; - public ushort Mapper_69_IRQCounter; // When enabled the 16-bit IRQ counter is decremented once per CPU cycle. When the IRQ counter is decremented from $0000 to $FFFF an IRQ is generated. - public override 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 (CPU_AddressIn >= 0x6000) - { - ushort tempo = (ushort)(CPU_AddressIn % 0x2000); - if (CPU_AddressIn >= 0x6000) - { - //actions - if (CPU_AddressIn < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - - } - } - else - { //read from ROM - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_6 * 0x2000 + tempo) % Cart.PRGROM.Length]; - - } - } - else if (CPU_AddressIn < 0xA000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_8 * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn < 0xC000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_A * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn < 0xE000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_C * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else - { - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x2000 + tempo]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x6000) - { - //actions - if (Address < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - //writing to RAM - Cart.PRGRAM[Address & 0x1FFF] = Input; - } //else, writing to open bus - } //else it's ROM. writing here does nothing. - } - else if (Address < 0xA000) - { - Mapper_69_CMD = (byte)(Input & 0x0F); - } - else if (Address < 0xC000) - { - switch (Mapper_69_CMD) - { - case 0: Mapper_69_CHR_1K0 = Input; break; - case 1: Mapper_69_CHR_1K1 = Input; break; - case 2: Mapper_69_CHR_1K2 = Input; break; - case 3: Mapper_69_CHR_1K3 = Input; break; - case 4: Mapper_69_CHR_1K4 = Input; break; - case 5: Mapper_69_CHR_1K5 = Input; break; - case 6: Mapper_69_CHR_1K6 = Input; break; - case 7: Mapper_69_CHR_1K7 = Input; break; - case 8: Mapper_69_Bank_6 = (byte)(Input & 0x3F); Mapper_69_Bank_6_isRAM = (Input & 0x40) != 0; Mapper_69_Bank_6_isRAMEnabled = (Input & 0x80) != 0; break; - case 9: Mapper_69_Bank_8 = (byte)(Input & 0x3F); break; - case 10: Mapper_69_Bank_A = (byte)(Input & 0x3F); break; - case 11: Mapper_69_Bank_C = (byte)(Input & 0x3F); break; - case 12: Mapper_69_NametableMirroring = (byte)(Input & 0x3); break; - case 13: Mapper_69_EnableIRQ = (Input & 0x1) != 0; Mapper_69_EnableIRQCounterDecrement = (Input & 0x80) != 0; Connector_IRQPin(false); break; - case 14: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF00) | Input); break; - case 15: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF) | (Input << 8)); break; - } - } // else do nothing - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x6000) - { - ushort tempo = (ushort)(Address % 0x2000); - if (Address >= 0x6000) - { - //actions - if (Address < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - } - else - { //read from ROM - return Cart.PRGROM[(Mapper_69_Bank_6 * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - } - else if (Address < 0xA000) - { - return Cart.PRGROM[(Mapper_69_Bank_8 * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else if (Address < 0xC000) - { - return Cart.PRGROM[(Mapper_69_Bank_A * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else if (Address < 0xE000) - { - return Cart.PRGROM[(Mapper_69_Bank_C * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else - { - return Cart.PRGROM[Cart.PRGROM.Length - 0x2000 + tempo]; - } - } - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - if (Address < 0x400) { return (Mapper_69_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x800) { Address &= 0x3FF; return (Mapper_69_CHR_1K1 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0xC00) { Address &= 0x3FF; return (Mapper_69_CHR_1K2 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x3FF; return (Mapper_69_CHR_1K3 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1400) { Address &= 0x3FF; return (Mapper_69_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x3FF; return (Mapper_69_CHR_1K5 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1C00) { Address &= 0x3FF; return (Mapper_69_CHR_1K6 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x3FF; return (Mapper_69_CHR_1K7 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - break; - case 1: //horizontal - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - break; - case 2: //one-screen A - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = false; } - break; - case 3: //one-screen B - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = true; } - break; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x400) != 0; - break; - case 1: //horizontal - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x800) != 0; - break; - case 2: //one-screen A - Cart.Emu.SeventyTwoPinConnector[21] = false; - break; - case 3: //one-screen B - Cart.Emu.SeventyTwoPinConnector[21] = true; - break; - } - } - } - public override 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); - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - Addr |= (ushort)(((Address & 0x400) != 0) ? 0x400 : 0); - break; - case 1: //horizontal - Addr |= (ushort)(((Address & 0x800) != 0) ? 0x400 : 0); - break; - case 2: //one-screen A - break; - case 3: //one-screen B - Addr |= 0x400; - break; - } - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_69_CMD); - State.Add(Mapper_69_CHR_1K0); - State.Add(Mapper_69_CHR_1K1); - State.Add(Mapper_69_CHR_1K2); - State.Add(Mapper_69_CHR_1K3); - State.Add(Mapper_69_CHR_1K4); - State.Add(Mapper_69_CHR_1K5); - State.Add(Mapper_69_CHR_1K6); - State.Add(Mapper_69_CHR_1K7); - State.Add(Mapper_69_Bank_6); - State.Add((byte)(Mapper_69_Bank_6_isRAM ? 1 : 0)); - State.Add((byte)(Mapper_69_Bank_6_isRAMEnabled ? 1 : 0)); - State.Add(Mapper_69_Bank_8); - State.Add(Mapper_69_Bank_A); - State.Add(Mapper_69_Bank_C); - State.Add(Mapper_69_NametableMirroring); - State.Add((byte)(Mapper_69_EnableIRQ ? 1 : 0)); - State.Add((byte)(Mapper_69_EnableIRQCounterDecrement ? 1 : 0)); - State.Add((byte)Mapper_69_IRQCounter); - State.Add((byte)(Mapper_69_IRQCounter >> 8)); - return State; - } - public override 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++]; } - } - Mapper_69_CMD = State[p++]; - Mapper_69_CHR_1K0 = State[p++]; - Mapper_69_CHR_1K1 = State[p++]; - Mapper_69_CHR_1K2 = State[p++]; - Mapper_69_CHR_1K3 = State[p++]; - Mapper_69_CHR_1K4 = State[p++]; - Mapper_69_CHR_1K5 = State[p++]; - Mapper_69_CHR_1K6 = State[p++]; - Mapper_69_CHR_1K7 = State[p++]; - Mapper_69_Bank_6 = State[p++]; - Mapper_69_Bank_6_isRAM = (State[p++] & 1) == 1; - Mapper_69_Bank_6_isRAMEnabled = (State[p++] & 1) == 1; - Mapper_69_Bank_8 = State[p++]; - Mapper_69_Bank_A = State[p++]; - Mapper_69_Bank_C = State[p++]; - Mapper_69_NametableMirroring = State[p++]; - Mapper_69_EnableIRQ = (State[p++] & 1) == 1; - Mapper_69_EnableIRQCounterDecrement = (State[p++] & 1) == 1; - Mapper_69_IRQCounter = State[p++]; - Mapper_69_IRQCounter |= (ushort)(State[p++] << 8); - exitIndex = p; - } - public override void CPUClock() - { - // The sunsoft FME-7 mapper chip has an IRQ counter that ticks down once per CPU cycle. - if (Mapper_69_EnableIRQCounterDecrement) - { - ushort temp = Mapper_69_IRQCounter; - Mapper_69_IRQCounter--; - if (Mapper_69_EnableIRQ && temp < Mapper_69_IRQCounter) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_GxROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_GxROM.cs deleted file mode 100644 index a42dbd3a..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_GxROM.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_GxROM : Mapper - { - // ines Mapper 66 - public byte Mapper_66_BankSelect_CHR; - public byte Mapper_66_BankSelect_PRG; - public override int FetchPatternAddress(ushort Address) - { - return (Mapper_66_BankSelect_CHR * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - public override 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 (CPU_AddressIn >= 0x8000) - { - CPU_DataOut = Cart.PRGROM[0x8000 * (Mapper_66_BankSelect_PRG) + (CPU_AddressIn & 0x7FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_66_BankSelect_CHR = (byte)(Input & 0x3); - Mapper_66_BankSelect_PRG = (byte)((Input & 0x30) >> 4); - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - return Cart.PRGROM[0x8000 * (Mapper_66_BankSelect_PRG) + (Address & 0x7FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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); } - } - State.Add(Mapper_66_BankSelect_CHR); - State.Add(Mapper_66_BankSelect_PRG); - return State; - } - public override 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++]; } - } - Mapper_66_BankSelect_CHR = State[p++]; - Mapper_66_BankSelect_PRG = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC1.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC1.cs deleted file mode 100644 index 96afee4a..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC1.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC1 : Mapper - { - // ines Mapper 1 - public byte Mapper_1_ShiftRegister; - public byte Mapper_1_Control = 0x0C; //0x8000 - public byte Mapper_1_CHR0; //0xA000 - public byte Mapper_1_CHR1; //0xC000 - public byte Mapper_1_PRG; //0xE000 - public bool Mapper_1_PB; - public override 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 (CPU_AddressIn >= 0x8000) - { - // The bank mode for MMC1: - byte MMC1PRGROMBankMode = (byte)((Mapper_1_Control & 0b01100) >> 2); - switch (MMC1PRGROMBankMode) - { - case 0: - case 1: - { - // switch 32 KB at $8000, ignoring low bit of bank number - ushort tempo = (ushort)(CPU_AddressIn & 0x7FFF); - CPU_DataOut = Cart.PRGROM[(0x8000 * (Mapper_1_PRG & 0x0E) + tempo) % Cart.PRGROM.Length]; - } - break; - case 2: - // fix first bank at $8000 and switch 16 KB bank at $C000 - if (CPU_AddressIn >= 0xC000) - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[0x4000 * (Mapper_1_PRG) + tempo]; - } - else - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[tempo]; - } - break; - case 3: - // fix last bank at $C000 and switch 16 KB bank at $8000 - if (CPU_AddressIn >= 0xC000) - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; - } - else - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[(0x4000 * (Mapper_1_PRG & 0x0F) + tempo) & (Cart.PRGROM.Length - 1)]; - } - break; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else // if the address is < $8000 - { - if (((Mapper_1_PRG & 0x10) == 0) && CPU_AddressIn >= 0x6000) // if Work RAM is enabled - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - // else, open bus. - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0x8000) //WRAM not available on MMC1A - { - if (((Mapper_1_PRG & 0x10) == 0) /*&& Mapper != 1*/) - { - //Battery backed RAM - Cart.PRGRAM[Address & 0x1FFF] = Input; - return; - } - else - { - return; //do nothing - } - } - else - { // shift the shirftRegister and add the new bit - Mapper_1_PB = (Mapper_1_ShiftRegister & 1) == 1; - Mapper_1_ShiftRegister >>= 1; - Mapper_1_ShiftRegister |= (byte)((Input & 1) << 4); - } - if (Mapper_1_PB) // if the '1' that was initialized in bit 4 is shifted into the bus - { - // copy shift register to the desired internal register. - switch (Address & 0xE000) - { - case 0x8000: //control - Mapper_1_Control = Mapper_1_ShiftRegister; - break; - case 0xA000: //CHR0 - Mapper_1_CHR0 = Mapper_1_ShiftRegister; - break; - case 0xC000: //CHR1 - Mapper_1_CHR1 = Mapper_1_ShiftRegister; - break; - case 0xE000: //PRG - Mapper_1_PRG = Mapper_1_ShiftRegister; - break; - } - Mapper_1_ShiftRegister = 0b10000; - } - if ((Input & 0b10000000) != 0) - { - Mapper_1_ShiftRegister = 0b10000; - Mapper_1_Control |= 0b01100; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - // The bank mode for MMC1: - byte MMC1PRGROMBankMode = (byte)((Mapper_1_Control & 0b01100) >> 2); - switch (MMC1PRGROMBankMode) - { - case 0: - case 1: - { - // switch 32 KB at $8000, ignoring low bit of bank number - ushort tempo = (ushort)(Address & 0x7FFF); - return Cart.PRGROM[(0x8000 * (Mapper_1_PRG & 0x0E) + tempo) % Cart.PRGROM.Length]; - } - break; - case 2: - // fix first bank at $8000 and switch 16 KB bank at $C000 - if (Address >= 0xC000) - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[0x4000 * (Mapper_1_PRG) + tempo]; - } - else - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[tempo]; - } - break; - case 3: - // fix last bank at $C000 and switch 16 KB bank at $8000 - if (Address >= 0xC000) - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; - } - else - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[(0x4000 * (Mapper_1_PRG & 0x0F) + tempo) & (Cart.PRGROM.Length - 1)]; - } - break; - } - } - else // if the address is < $8000 - { - if (((Mapper_1_PRG & 0x10) == 0) && Address >= 0x6000) // if Work RAM is enabled - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - // else, open bus. - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - // bit 4 of Mapper_1_Control controls how the pattern tables are swapped. if set, 2 banks of 4Kib. Otherwise, 1 8Kib bank - if ((Mapper_1_Control & 0x10) != 0) - { - // with the MMC1 chip, you can swap out the pattern tables. - // address < 0x1000 is the first pattern table, else, the second pattern table. - // if the final write for the MMC1 shift register was in the $A000 - $BFFF, this updates Mapper_1_CHR0 - // if the final write for the MMC1 shift register was in the $B000 - $CFFF, this updates Mapper_1_CHR1 - if (Address < 0x1000) { return ((Mapper_1_CHR0 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0xFFF; return ((Mapper_1_CHR1 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1); } - } - else // one swappable bank that changes both pattern tables. - { - // this uses the value written to Mapper_1_CHR0 - return ((Mapper_1_CHR0 & 0b11111110) * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = false; } - break; - case 1: //one screen, high - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = true; } - break; - case 2: //vertical - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - break; - case 3: //horizontal - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - break; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - Cart.Emu.SeventyTwoPinConnector[21] = false; - break; - case 1: //one screen, high - Cart.Emu.SeventyTwoPinConnector[21] = true; - break; - case 2: //vertical - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x400) != 0; - break; - case 3: //horizontal - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x800) != 0; - break; - } - } - } - public override 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); - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - break; - case 1: //one screen, high - Addr |= 0x400; - break; - case 2: //vertical - Addr |= (ushort)(((Address & 0x400) != 0) ? 0x400 : 0); - break; - case 3: //horizontal - Addr |= (ushort)(((Address & 0x800) != 0) ? 0x400 : 0); - break; - } - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_1_ShiftRegister); - State.Add(Mapper_1_Control); - State.Add(Mapper_1_CHR0); - State.Add(Mapper_1_CHR1); - State.Add(Mapper_1_ShiftRegister); - State.Add((byte)(Mapper_1_PB ? 1 : 0)); - return State; - } - public override 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++]; } - } - Mapper_1_ShiftRegister = State[p++]; - Mapper_1_Control = State[p++]; - Mapper_1_CHR0 = State[p++]; - Mapper_1_CHR1 = State[p++]; - Mapper_1_ShiftRegister = State[p++]; - Mapper_1_PB = (State[p++] & 1) == 1; - exitIndex = p; - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC2.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC2.cs deleted file mode 100644 index 92d0c976..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC2.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC2 : Mapper - { - // ines Mapper 9 - public byte Mapper_9_BankSelect; - public byte Mapper_9_CHR0_FD; - public byte Mapper_9_CHR0_FE; - public byte Mapper_9_CHR1_FD; - public byte Mapper_9_CHR1_FE; - public bool Mapper_9_NametableMirroring; - public bool Mapper_9_Latch0_FE; - public bool Mapper_9_Latch1_FE; - public override 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 (CPU_AddressIn >= 0xA000) - { - CPU_DataOut = Cart.PRGROM[((Cart.PRG_Size - 2) << 14) | (CPU_AddressIn & 0x7FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if(CPU_AddressIn >= 0x8000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_9_BankSelect << 13) | (CPU_AddressIn & 0x1FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0xA000) - { - // nothing - } - else if (Address < 0xB000) // PRG Bank select - { - Mapper_9_BankSelect = (byte)(Input & 0x0F); - } - else if (Address < 0xC000) // CHR0 Bank select - { - Mapper_9_CHR0_FD = (byte)(Input & 0x1F); - } - else if (Address < 0xD000) // CHR0 Bank select - { - Mapper_9_CHR0_FE = (byte)(Input & 0x1F); - } - else if (Address < 0xE000) // CHR1 Bank select - { - Mapper_9_CHR1_FD = (byte)(Input & 0x1F); - } - else if (Address < 0xF000) // CHR1 Bank select - { - Mapper_9_CHR1_FE = (byte)(Input & 0x1F); - } - else // Nametable mirroring - { - Mapper_9_NametableMirroring = (Input & 0x1) == 1; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xA000) - { - return Cart.PRGROM[((Cart.PRG_Size - 2) << 14) | (Address & 0x7FFF)]; - } - else if (Address >= 0x8000) - { - return Cart.PRGROM[(Mapper_9_BankSelect << 13) | (Address & 0x1FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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]) { - PPU_DataOut = Cart.CHRROM[CHR_Address]; - Connector_SetUpPPUDataPins(PPU_DataOut); - // MMC2 has registers that do things based on the address being read. - if (Address == 0x0FD8) - { - Mapper_9_Latch0_FE = false; - } - else if (Address == 0x0FE8) - { - Mapper_9_Latch0_FE = true; - } - else if (Address >= 0x1FD8 && Address <= 0x1FDF) - { - Mapper_9_Latch1_FE = false; - } - else if (Address >= 0x1FE8 && Address <= 0x1FEF) - { - Mapper_9_Latch1_FE = true; - } - } // Reads - if (!Cart.Emu.SeventyTwoPinConnector[55] && Cart.UsingCHRRAM) { Cart.CHRROM[CHR_Address] = PPU_DataIn; } // Writes - } - } - - public override int FetchPatternAddress(ushort Address) - { - ushort Addr = Address; - if (Address < 0x1000) { return (Mapper_9_Latch0_FE ? Mapper_9_CHR0_FE : Mapper_9_CHR0_FD) * 0x1000 + Addr; } - else { Addr &= 0xFFF; return (Mapper_9_Latch1_FE ? Mapper_9_CHR1_FE : Mapper_9_CHR1_FD) * 0x1000 + Addr; } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - if (Mapper_9_NametableMirroring) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = Mapper_9_NametableMirroring ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - } - } - public override 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)((Mapper_9_NametableMirroring ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_9_BankSelect); - State.Add(Mapper_9_CHR0_FD); - State.Add(Mapper_9_CHR0_FE); - State.Add(Mapper_9_CHR1_FD); - State.Add(Mapper_9_CHR1_FE); - State.Add((byte)(Mapper_9_NametableMirroring ? 1 : 0)); - State.Add((byte)(Mapper_9_Latch0_FE ? 1 : 0)); - State.Add((byte)(Mapper_9_Latch1_FE ? 1 : 0)); return State; - } - public override 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++]; } - } - Mapper_9_BankSelect = State[p++]; - Mapper_9_CHR0_FD = State[p++]; - Mapper_9_CHR0_FE = State[p++]; - Mapper_9_CHR1_FD = State[p++]; - Mapper_9_CHR1_FE = State[p++]; - Mapper_9_NametableMirroring = (State[p++] & 1) == 1; - Mapper_9_Latch0_FE = (State[p++] & 1) == 1; - Mapper_9_Latch1_FE = (State[p++] & 1) == 1; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC3.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC3.cs deleted file mode 100644 index e35b6d3c..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_MMC3.cs +++ /dev/null @@ -1,487 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC3 : Mapper - { - // ines Mapper 4 - public byte Mapper_4_8000; // The value written to $8000 (or any even address between $8000 and $9FFE) - public byte Mapper_4_BankA; // The PRG bank between $A000 and $BFFF - public byte Mapper_4_Bank8C; // The PRG bank that could either be at $8000 through 9FFF, or $C000 through $DFFF - public byte Mapper_4_CHR_2K0; - public byte Mapper_4_CHR_2K8; - public byte Mapper_4_CHR_1K0; - public byte Mapper_4_CHR_1K4; - public byte Mapper_4_CHR_1K8; - public byte Mapper_4_CHR_1KC; - public byte Mapper_4_IRQLatch; - public byte Mapper_4_IRQCounter; - public bool Mapper_4_EnableIRQ; - public bool Mapper_4_ReloadIRQCounter; - public bool Mapper_4_NametableMirroring; // MMC3 has it's own way of controlling how the nametables are mirrored. - public byte Mapper_4_PRGRAMProtect; - public byte Mapper_4_M2Filter; - public bool Mapper_4_PPUA12; - public override 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 (CPU_AddressIn >= 0xE000) // This bank is fixed the the final PRG bank of the ROM - { - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x3FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0xC000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$C000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (CPU_AddressIn & 0x1FFF)]; - } - else - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x1FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0xA000) - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_BankA << 13) | (CPU_AddressIn & 0x1FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0x8000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x1FFF)]; - } - else - { - //$C000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (CPU_AddressIn & 0x1FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0x6000) - { - if (Cart.SubMapper == 1) // MMC6 - { - if ((Mapper_4_8000 & 0x20) != 0) - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if (CPU_AddressIn >= 0x7000 && CPU_AddressIn <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x20) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x3FF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - else if (CPU_AddressIn >= 0x7200 && CPU_AddressIn <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x3FF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - } - else - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0x8000) - { //Battery backed RAM - - if (Cart.SubMapper == 1) // MMC6 - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if ((Mapper_4_8000 & 0x20) != 0) - { - if (Address >= 0x7000 && Address <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x10) != 0) - { - Cart.PRGRAM[Address & 0x3FF] = Input; - - } - } - else if (Address >= 0x7200 && Address <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x40) != 0) - { - Cart.PRGRAM[Address & 0x3FF] = Input; - } - } - } - } - else if ((Mapper_4_PRGRAMProtect & 0xC0) == 0x80) // bit 7 enables PRG RAM, bit 6 disables writing there. - { - Cart.PRGRAM[Address & 0x1FFF] = Input; - } - return; - } - else - { - ushort tempo = (ushort)(Address & 0xE001); - switch (tempo) - { - case 0x8000: - Mapper_4_8000 = Input; - return; - case 0x8001: - byte mode = (byte)(Mapper_4_8000 & 7); - switch (mode) - { - case 0: //PPU ($0000 - $07FF) ?+ $1000 - Mapper_4_CHR_2K0 = (byte)(Input & 0xFE); - return; - case 1: //PPU ($0800 - $0FFF) ?+ $1000 - Mapper_4_CHR_2K8 = (byte)(Input & 0xFE); - return; - case 2: //PPU ($1000 - $13FF) ?- $1000 - Mapper_4_CHR_1K0 = Input; - return; - case 3: //PPU ($1400 - $17FF) ?- $1000 - Mapper_4_CHR_1K4 = Input; - return; - case 4: //PPU ($1800 - $1BFF) ?- $1000 - Mapper_4_CHR_1K8 = Input; - return; - case 5: //PPU ($1C00 - $1FFF) ?- $1000 - Mapper_4_CHR_1KC = Input; - return; - case 6: //PRG ($8000 - $9FFF) ?+ 0x4000 - Mapper_4_Bank8C = (byte)(Input & (Cart.PRG_Size * 2 - 1)); - return; - case 7: //PRG ($A000 - $BFFF) - Mapper_4_BankA = (byte)(Input & (Cart.PRG_Size * 2 - 1)); - return; - } - return; - case 0xA000: - Mapper_4_NametableMirroring = (Input & 1) == 1; - return; - case 0xA001: - Mapper_4_PRGRAMProtect = Input; - return; - case 0xC000: - Mapper_4_IRQLatch = Input; - return; - case 0xC001: - Mapper_4_IRQCounter = 0xFF; - Mapper_4_ReloadIRQCounter = true; - return; - case 0xE000: - Mapper_4_EnableIRQ = false; - Connector_IRQPin(false); // Acknowledge the IRQ - return; - case 0xE001: - Mapper_4_EnableIRQ = true; - return; - } - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xE000) // This bank is fixed the the final PRG bank of the ROM - { - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x3FFF)]; - } - else if (Address >= 0xC000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$C000 swappable - return Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; - } - else - { - //$8000 swappable - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; - } - } - else if (Address >= 0xA000) - { - //$8000 swappable - return Cart.PRGROM[(Mapper_4_BankA << 13) | (Address & 0x1FFF)]; - } - else if (Address >= 0x8000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$8000 swappable - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; - } - else - { - //$C000 swappable - return Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; - } - } - else if (Address >= 0x6000) - { - if (Cart.SubMapper == 1) // MMC6 - { - if ((Mapper_4_8000 & 0x20) != 0) - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if (Address >= 0x7000 && Address <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x20) != 0) - { - return Cart.PRGRAM[Address & 0x3FF]; - } - } - else if (Address >= 0x7200 && Address <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - return Cart.PRGRAM[Address & 0x3FF]; - } - } - } - } - else - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - } - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - //Writes to $8000 determine the mode, writes to $8001 determine the banks - if ((Mapper_4_8000 & 0x80) == 0) // bit 7 of the previous write to $8000 determines which pattern table is 2 2kb banks, and which is 4 1kb banks. - { - if (Address < 0x800) { return (Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x7FF; return (Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1400) { Address &= 0x3FF; return (Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x3FF; return (Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1C00) { Address &= 0x3FF; return (Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x3FF; return (Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - else - { - if (Address < 0x400) { return (Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x800) { Address &= 0x3FF; return (Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0xC00) { Address &= 0x3FF; return (Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x3FF; return (Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x7FF; return (Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x7FF; return (Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (Cart.AlternativeNametableArrangement) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57] || Cart.Emu.SeventyTwoPinConnector[61]; } - } - else - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - } - - if (Mapper_4_NametableMirroring) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - if (Cart.AlternativeNametableArrangement) - { - Cart.Emu.SeventyTwoPinConnector[61] = (Cart.Emu.PPU_AddressBus & 0x0800) != 0; - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0 || Cart.Emu.SeventyTwoPinConnector[61]; - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - } - Cart.Emu.SeventyTwoPinConnector[21] = Mapper_4_NametableMirroring ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - - } - } - public override 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 if (Cart.AlternativeNametableArrangement && Address >= 0x2800) // 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. - return Cart.PRGVRAM[Address]; - } - else - { - ushort Addr = (ushort)(Address & 0x3FF); - Addr |= (ushort)((Mapper_4_NametableMirroring ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - if (Cart.PRGVRAM != null) - { - foreach (Byte b in Cart.PRGVRAM) { State.Add(b); } - } - State.Add(Mapper_4_8000); - State.Add(Mapper_4_BankA); - State.Add(Mapper_4_Bank8C); - State.Add(Mapper_4_CHR_2K0); - State.Add(Mapper_4_CHR_2K8); - State.Add(Mapper_4_CHR_1K0); - State.Add(Mapper_4_CHR_1K4); - State.Add(Mapper_4_CHR_1K8); - State.Add(Mapper_4_CHR_1KC); - State.Add(Mapper_4_IRQLatch); - State.Add(Mapper_4_IRQCounter); - State.Add((byte)(Mapper_4_EnableIRQ ? 1 : 0)); - State.Add((byte)(Mapper_4_ReloadIRQCounter ? 1 : 0)); - State.Add((byte)(Mapper_4_NametableMirroring ? 1 : 0)); - State.Add(Mapper_4_PRGRAMProtect); - State.Add(Mapper_4_M2Filter); - State.Add((byte)(Mapper_4_PPUA12 ? 1 : 0)); - return State; - } - public override 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++]; } - } - if (Cart.PRGVRAM != null) - { - for (int i = 0; i < Cart.PRGVRAM.Length; i++) { Cart.PRGVRAM[i] = State[p++]; } - } - Mapper_4_8000 = State[p++]; - Mapper_4_BankA = State[p++]; - Mapper_4_Bank8C = State[p++]; - Mapper_4_CHR_2K0 = State[p++]; - Mapper_4_CHR_2K8 = State[p++]; - Mapper_4_CHR_1K0 = State[p++]; - Mapper_4_CHR_1K4 = State[p++]; - Mapper_4_CHR_1K8 = State[p++]; - Mapper_4_CHR_1KC = State[p++]; - Mapper_4_IRQLatch = State[p++]; - Mapper_4_IRQCounter = State[p++]; - Mapper_4_EnableIRQ = (State[p++] & 1) == 1; - Mapper_4_ReloadIRQCounter = (State[p++] & 1) == 1; - Mapper_4_NametableMirroring = (State[p++] & 1) == 1; - Mapper_4_PRGRAMProtect = State[p++]; - Mapper_4_M2Filter = State[p++]; - Mapper_4_PPUA12 = (State[p++] & 1) == 1; - exitIndex = p; - } - - public override void PPUClock() - { - if (!TiltingCart) - { - Cart.Emu.SeventyTwoPinConnector[63] = (Cart.Emu.PPU_AddressBus & 0x1000) != 0; - } - else - { - Connector_SetUpPPUAddressPins(); - } - // if bit 12 of the ppu address bus (A12) changes: - if (!Mapper_4_PPUA12 && Cart.Emu.SeventyTwoPinConnector[63] && Mapper_4_M2Filter == 3) - { - if (Mapper_4_ReloadIRQCounter) - { - // If we're reloading the IRQ counter - Mapper_4_IRQCounter = Mapper_4_IRQLatch; // The latch is the reset value. - Mapper_4_ReloadIRQCounter = false; - if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ. - { - if (Mapper_4_EnableIRQ) // if setting the value to zero, run an IRQ - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - else - { - // decrement the counter - Mapper_4_IRQCounter--; - if (Mapper_4_IRQCounter == 0) // if decrementing the counter moved it to 0... - { - if (Mapper_4_EnableIRQ) // and the MMC3 IRQ is enabled... - { - Connector_IRQPin(true); // Run an IRQ! - } - } - else if (Mapper_4_IRQCounter == 255) // if the counter underflows... - { - Mapper_4_IRQCounter = Mapper_4_IRQLatch; // reset the irq counter - if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ... again - { - if (Mapper_4_EnableIRQ) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - - } - } - if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) != 0) - { - Mapper_4_M2Filter = 0; - } - Mapper_4_PPUA12 = Cart.Emu.SeventyTwoPinConnector[63]; - } - public override void CPUClockRise() - { - if (Cart.Emu.ConnectorPinFloating[37]) { return; } // If the M2 pin is disconnected, this step doesn't work. - if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) == 0) - { - if (Mapper_4_M2Filter < 3) - { - Mapper_4_M2Filter++; - } - } - } - - public override string AppendToDebugLog() - { - return "\tPPU_Coords (" + Cart.Emu.PPU_Scanline + ", " + Cart.Emu.PPU_Dot + ")\tIRQTimer:" + Mapper_4_IRQCounter + "\tIRQLatch: " + Mapper_4_IRQLatch + "\tIRQEnabled: " + Mapper_4_EnableIRQ + "\tDoIRQ: " + Cart.Emu.DoIRQ + "\tPPU_ADDR_Prev: " + (Mapper_4_PPUA12 ? "1" : "0"); - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NROM.cs deleted file mode 100644 index 3b2ba041..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NROM.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TriCNES.mappers -{ - public class Mapper_NROM : Mapper - { - // ines Mapper 0 - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NULL.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NULL.cs deleted file mode 100644 index 784555b8..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_NULL.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_NULL : Mapper - { - // There is not a cartridge inserted in the console. - - public override void FetchCPU() - { - // the data pins are always floating. There's no cartridge inserted! - return; - } - - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - return Cart.Emu.dataBus; // There is nothing inserted here. The CPU *must* see open bus. - } - - public override void AccessPPU() - { - // the data pins are always floating. There's no cartridge inserted! - return; - } - - public override 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. - { - return (byte)Cart.Emu.addressBus; // There is nothing inserted here. The PPU *must* see open bus. - } - - public override int FetchPatternAddress(ushort Address) - { - // there's no cartridge. TODO: Look into this. Supposedly this would likely be the lower 8 bits of the address bus, but CIRAM enable is also floating. - return 0; - } - public override void Connector_CheckCIRAM() - { - // CIRAM is left floating. There's no cartridge inserted! - } - public override List SaveMapperRegisters() - { - List State = new List(); - return State; - } - public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) - { - int p = startIndex; - exitIndex = p; - } - public override bool CheckCIC() - { - return false; // There's no CIC chip! - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_UxROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_UxROM.cs deleted file mode 100644 index 83adf1f1..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/mappers/Mapper_UxROM.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_UxROM : Mapper - { - // ines Mapper 2 - public byte Mapper_2_BankSelect; - public override 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 (CPU_AddressIn >= 0x8000) - { - if (CPU_AddressIn >= 0xC000) - { - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + (CPU_AddressIn & 0x3FFF)]; - } - else - { - CPU_DataOut = Cart.PRGROM[0x4000 * (Mapper_2_BankSelect & 0x0F) + (CPU_AddressIn & 0x3FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_2_BankSelect = (byte)(Input & 0xF); - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - if (CPU_AddressIn >= 0xC000) - { - return Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + (Address & 0x3FFF)]; - } - return Cart.PRGROM[0x4000 * (Mapper_2_BankSelect & 0x0F) + (Address & 0x3FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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); } - } - State.Add(Mapper_2_BankSelect); - return State; - } - public override 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++]; } - } - Mapper_2_BankSelect = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/packages.config b/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/packages.config deleted file mode 100644 index c1cbee0f..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/6502Documentation.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/6502Documentation.cs deleted file mode 100644 index 9fd7cb4b..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-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-harness-src/Emulator.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Emulator.cs deleted file mode 100644 index 0824c78f..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Emulator.cs +++ /dev/null @@ -1,11660 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -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 long HarnessCycle = 0; // harness: CPU-cycle counter for the Step-C anchor - // xdiff cross-diff: per-cycle bus-access window [XdiffLo, XdiffHi]. - // When XdiffLo < 0 the window is disabled. Set from env (Program.cs). - public long XdiffLo = -1; - public long XdiffHi = -1; - // xdiff cross-diff: anchor address. Every CPU Store to this address is - // marked "W54A" (regardless of window) so the harness can locate the - // anchor cycle in a first pass, then set the window in a second pass. - public ushort XdiffAnchorAddr = 0x054A; - void XdiffLog(string acc, ushort addr, byte data) - { - if (XdiffLo >= 0 && HarnessCycle >= XdiffLo && HarnessCycle <= XdiffHi) - { - System.Console.Error.WriteLine( - $"XC cyc={HarnessCycle} pc={programCounter:X4} acc={acc} addr={addr:X4} data={data:X2} dmc={(DoDMCDMA ? 1 : 0)} oam={(DoOAMDMA ? 1 : 0)} put={(APU_PutCycle ? 1 : 0)} dtimer={APU_ChannelTimer_DMC} dbits={APU_DMC_ShifterBitsRemaining} dsil={(APU_Silent ? 1 : 0)} dbytes={APU_DMC_BytesRemaining} d4015d={APU_DelayedDMC4015} ddmadelay={DMCDMADelay}"); - } - } - 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() - { - if (XdiffLo < 0 || (HarnessCycle >= XdiffLo && HarnessCycle <= XdiffHi)) - System.Console.Error.WriteLine($"GET cyc={HarnessCycle} addr={APU_DMC_AddressCounter:X4}"); - // 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() - { - - HarnessCycle++; - 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) - { - // xdiff: one log row per read cycle. `dataBus` here is the data bus - // at fetch entry = the open-bus value RETURNED for write-only regs - // ($4000 reads: exact, the $00-catch byte); for real RAM/ROM reads - // the value is filled below so the column lags by one (the ADDR is - // the alignment key; $4000-catch data is exact). - XdiffLog("R", Address, dataBus); - if (Address==0x4000 && HarnessCycle>=48808000 && HarnessCycle<=48822000) System.Console.Error.WriteLine($"R4000 cyc={HarnessCycle} bus={dataBus:X2} pc={programCounter:X4}"); - 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); - // xdiff: one log row per write cycle. `Input` is the exact write - // datum. The W54A marker fires for EVERY store to the anchor addr - // (window-independent) so pass 1 can locate the anchor cycle. - XdiffLog("W", Address, Input); - if (Address == XdiffAnchorAddr) - { - System.Console.Error.WriteLine( - $"W54A cyc={HarnessCycle} pc={programCounter:X4} data={Input:X2}"); - } - // 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: - System.Console.Error.WriteLine($"4010w cyc={HarnessCycle} data={Input:X2}"); - 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) - - System.Console.Error.WriteLine($"4015w cyc={HarnessCycle} data={Input:X2} dmctimer={APU_ChannelTimer_DMC} put={(APU_PutCycle?1:0)}"); - 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 - { - public Int32[] Bits { get; private set; } - public bool Disposed { get; private set; } - public int Height { get; private set; } - public int Width { get; private set; } - public DirectBitmap(int width, int height) - { - Width = width; Height = height; Bits = new Int32[width * height]; - } - public void SetPixel(int x, int y, Color color) { Bits[x + (y * Width)] = color.ToArgb(); } - public void SetPixel(int x, int y, int colorRGBA) { Bits[x + (y * Width)] = colorRGBA; } - public Color GetPixel(int x, int y) { return Color.FromArgb(Bits[x + (y * Width)]); } - public void Dispose() { Disposed = true; } - } - -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Program.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Program.cs deleted file mode 100644 index fb9b4192..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using TriCNES; -class Program { - static void Main(string[] args) { - var emu = new Emulator(); - // ROM path: env TRICNES_ROM overrides; otherwise resolve the canonical - // repo copy RELATIVE to the working directory. - // - // This used to hard-code an absolute path into a workspace layout that - // no longer exists (`Commercial_Private-Projects/RustyNES_v2/...`, - // pre-reorg). An absolute default silently rots the moment the repo - // moves, and it fails as "file not found" rather than as "your default - // is stale", so say so explicitly instead. - var romPath = System.Environment.GetEnvironmentVariable("TRICNES_ROM") - ?? System.IO.Path.Combine( - System.IO.Directory.GetCurrentDirectory(), - "tests", "roms", "accuracycoin", "AccuracyCoin.nes"); - if (!System.IO.File.Exists(romPath)) - { - System.Console.Error.WriteLine( - $"tricnes-harness: ROM not found at {romPath}\n" + - " Run from the repository root, or set TRICNES_ROM to the ROM path."); - System.Environment.Exit(2); - } - var cart = new Cartridge(romPath); - emu.Cart = cart; - cart.Emu = emu; - // xdiff cross-diff window (per-cycle bus log). Set XDIFF_LO/XDIFF_HI to - // a HarnessCycle range to emit "XC ..." rows there; leave unset (pass 1) - // to only emit the "W54A ..." anchor markers. XDIFF_ANCHOR (hex) - // overrides the anchor addr (default 054A = Implicit Loop3 X=10 store). - { - var lo = System.Environment.GetEnvironmentVariable("XDIFF_LO"); - var hi = System.Environment.GetEnvironmentVariable("XDIFF_HI"); - var an = System.Environment.GetEnvironmentVariable("XDIFF_ANCHOR"); - if (lo != null) emu.XdiffLo = long.Parse(lo); - if (hi != null) emu.XdiffHi = long.Parse(hi); - if (an != null) emu.XdiffAnchorAddr = System.Convert.ToUInt16(an, 16); - } - int frames = args.Length > 0 ? int.Parse(args[0]) : 2000; - for (int i = 0; i < 300; i++) emu._CoreFrameAdvance(); // boot/menu - emu.ControllerPort1 = 0x10; // press START - for (int i = 0; i < 6; i++) emu._CoreFrameAdvance(); - emu.ControllerPort1 = 0x00; // release - for (int i = 0; i < frames; i++) emu._CoreFrameAdvance(); // run battery - System.Console.Error.WriteLine($"# done {frames+306} frames, HarnessCycle={emu.HarnessCycle}"); - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_AOROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_AOROM.cs deleted file mode 100644 index 8b9b874c..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_AOROM.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_AOROM : Mapper - { - // ines Mapper 7 - public byte Mapper_7_BankSelect; - public override 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[(0x8000 * (Mapper_7_BankSelect & 0x07) + (CPU_AddressIn & 0x7FFF)) & (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 override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_7_BankSelect = Input; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - return Cart.PRGROM[(0x8000 * (Mapper_7_BankSelect & 0x07) + (Address & 0x7FFF)) & (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. - } - return Cart.Emu.dataBus; - } - public override 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] = (Mapper_7_BankSelect & 0x10) != 0; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = (Mapper_7_BankSelect & 0x10) != 0; - } - } - public override 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)(((Mapper_7_BankSelect & 0x10) == 0) ? 0 : 0x400); - return Cart.Emu.VRAM[Addr]; - } - } - public override 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); } - } - State.Add(Mapper_7_BankSelect); - return State; - } - public override 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++]; } - } - Mapper_7_BankSelect = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_CNROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_CNROM.cs deleted file mode 100644 index 7f4e276e..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_CNROM.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_CNROM : Mapper - { - // ines Mapper 3 - public byte Mapper_3_CHRBank; - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_3_CHRBank = (byte)(Input & 0x3); - } - } - public override int FetchPatternAddress(ushort Address) - { - return (Mapper_3_CHRBank * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - public override 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); } - } - State.Add(Mapper_3_CHRBank); - return State; - } - public override 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++]; } - } - Mapper_3_CHRBank = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FDS.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FDS.cs deleted file mode 100644 index e860deb4..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FDS.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_FDS : Mapper - { - // The Famicom Disk System - public byte[] FDS_BIOS; - - public byte FDS_4023_IOEnable; - public byte FDS_4025_Control; - - public Mapper_FDS(byte[] fds_bios) - { - FDS_BIOS = fds_bios; - } - - public override 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(); - ushort Address = CPU_AddressIn; - - if (CPU_AddressIn >= 0xE000) - { - // read from the FDS BIOS - CPU_DataOut = FDS_BIOS[Address & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (Address >= 0x6000) - { - // read from the FDS PRG RAM - CPU_DataOut = Cart.PRGRAM[Address-0x6000]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (Address >= 4030 && Address <= 0x403F) - { - // Read from the FDS Registers - Address &= 0xF; - switch (Address) - { - default: break; - case 0: - { - // FDS Status ($4030) - CPU_DataOut = 0; - CPU_DataOut |= (byte)((FDS_4025_Control >> 3) & 1); // 4030.3 = 4025.3 - CPU_DataOut |= (byte)((Cart.FDS.DiskAddress >= Cart.FDS.Disk.Length) ? 0x40 : 0); // 4030.6 = End of Disk - CPU_DataOut |= (byte)(Cart.FDS.Status_ByteTransferFlag ? 0x80 : 0); // 4030.7 = Byte Transfer Flag - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - case 1: - { - // Disk Data Input ($4031) - CPU_DataOut = Cart.FDS.ShiftRegisterLatch; - Connector_SetUpCPUDataPins(CPU_DataOut); - Cart.FDS.Status_ByteTransferFlag = false; - Connector_IRQPin(false); //acknowledge the IRQ - } - break; - case 2: - { - // Disk Drive Status ($4032) - CPU_DataOut = 0; - if(Cart.FDS.CurrentState == DiskDrive.RamAdapterState.INSERTING) - { - CPU_DataOut |= 1; - } - if (!(((FDS_4025_Control & 2) == 0) && (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.RUNNING || Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE))) - { - CPU_DataOut |= 2; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - case 3: - { - // External Connector Input ($4033) - CPU_DataOut = 0x80; // The battery is good. - Connector_SetUpCPUDataPins(CPU_DataOut); - } - break; - } - } - - return; - } - public override int FetchPatternAddress(ushort Address) - { - return Address; - } - - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x6000 && Address < 0xE000) - { - Cart.PRGRAM[Address-0x6000] = Input; - return; - } - else if (Address > 0x401F) - { - ushort tempo = (ushort)(Address & 0x40FF); - switch (tempo) - { - case 0x4023: - FDS_4023_IOEnable = Input; - if((FDS_4023_IOEnable & 1) == 0) - { - // Disable disk I/O registers - Connector_IRQPin(false); //acknowledge the IRQ - FDS_4025_Control &= 0xF3; - FDS_4025_Control |= 6; - } - return; - case 0x4024: - Cart.FDS.Status_ByteTransferFlag = false; - return; - case 0x4025: - if((FDS_4025_Control & 0x40) == 0 && (Input & 0x40) != 0) - { - Cart.FDS.lookingForEndOfGap = true; - } - FDS_4025_Control = Input; - if ((Input & 1) != 0) - { - if (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE) - { - Cart.FDS.CurrentState = DiskDrive.RamAdapterState.SPINUP; - } - } - if((FDS_4025_Control & 2) != 0) - { - // debugging: put breakpoint here - } - if ((FDS_4025_Control & 2) == 0) - { - // debugging: put breakpoint here - } - return; - } - } - } - - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xE000) - { - // read from the FDS BIOS - return FDS_BIOS[Address & 0x1FFF]; - } - else if (Address >= 0x6000) - { - // read from the FDS PRG RAM - return Cart.PRGRAM[Address - 0x6000]; - } - else if (Address >= 4030 && Address <= 0x403F) - { - // Read from the FDS Registers - Address &= 0xF; - switch (Address) - { - default: break; - case 0: - { - // FDS Status ($4030) - byte t = 0; - t |= (byte)((FDS_4025_Control >> 3) & 1); // 4030.3 = 4025.3 - t |= (byte)((Cart.FDS.DiskAddress >= Cart.FDS.Disk.Length) ? 0x40 : 0); // 4030.6 = End of Disk - t |= (byte)(Cart.FDS.Status_ByteTransferFlag ? 0x80 : 0); // 4030.7 = Byte Transfer Flag - return t; - } - break; - case 1: - { - // Disk Data Input ($4031) - return Cart.FDS.ShiftRegisterLatch; - } - break; - case 2: - { - // Disk Drive Status ($4032) - byte t = 0; - if (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.INSERTING) - { - t |= 1; - } - if (!(((FDS_4025_Control & 2) == 0) && (Cart.FDS.CurrentState == DiskDrive.RamAdapterState.RUNNING || Cart.FDS.CurrentState == DiskDrive.RamAdapterState.IDLE))) - { - t |= 2; - } - return t; - } - break; - case 3: - { - // External Connector Input ($4033) - return 0x80; // The battery is good. - } - break; - } - } - return Cart.Emu.dataBus; - } - - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - if (((FDS_4025_Control >> 3) & 1) == 1) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = (((FDS_4025_Control >> 3) & 1) == 1) ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - } - } - public override 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)(((((FDS_4025_Control >> 3) & 1) == 1) ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override void FDS_ByteTransferFlag() - { - if((FDS_4025_Control & 0x80) != 0) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - public override byte FDS_Get4025() - { - return FDS_4025_Control; - } - - public override 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); } - } - State.Add(FDS_4025_Control); - State.Add((byte)Cart.FDS.clock); - State.Add((byte)(Cart.FDS.clock >> 8)); - State.Add((byte)(Cart.FDS.clock >> 16)); - State.Add((byte)(Cart.FDS.clock >> 24)); - State.Add((byte)Cart.FDS.CurrentState); - State.Add(Cart.FDS.ShiftRegister); - State.Add(Cart.FDS.ShiftRegisterLatch); - State.Add((byte)Cart.FDS.DiskAddress); - State.Add((byte)(Cart.FDS.DiskAddress >> 8)); - State.Add((byte)(Cart.FDS.DiskAddress >> 16)); - State.Add((byte)(Cart.FDS.DiskAddress >> 24)); - State.Add(Cart.FDS.DiskAddressFine); - State.Add((byte)(Cart.FDS.Status_ByteTransferFlag ? 1 : 0)); - State.Add((byte)(Cart.FDS.lookingForEndOfGap ? 1 : 0)); - - return State; - } - public override 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++]; } - } - FDS_4025_Control = State[p++]; - Cart.FDS.clock = State[p++]; - Cart.FDS.clock |= (ushort)(State[p++] << 8); - Cart.FDS.clock |= (ushort)(State[p++] << 16); - Cart.FDS.clock |= (ushort)(State[p++] << 24); - Cart.FDS.CurrentState = (DiskDrive.RamAdapterState)State[p++]; - Cart.FDS.ShiftRegister = State[p++]; - Cart.FDS.ShiftRegisterLatch = State[p++]; - Cart.FDS.DiskAddress = State[p++]; - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 8); - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 16); - Cart.FDS.DiskAddress |= (ushort)(State[p++] << 24); - Cart.FDS.DiskAddressFine = State[p++]; - Cart.FDS.Status_ByteTransferFlag = State[p++] == 1; - Cart.FDS.lookingForEndOfGap = State[p++] == 1; - - exitIndex = p; - } - - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FME7.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FME7.cs deleted file mode 100644 index 008e01da..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_FME7.cs +++ /dev/null @@ -1,321 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_FME7 : Mapper - { - // ines Mapper 69 - public byte Mapper_69_CMD; - public byte Mapper_69_CHR_1K0; - public byte Mapper_69_CHR_1K1; - public byte Mapper_69_CHR_1K2; - public byte Mapper_69_CHR_1K3; - public byte Mapper_69_CHR_1K4; - public byte Mapper_69_CHR_1K5; - public byte Mapper_69_CHR_1K6; - public byte Mapper_69_CHR_1K7; - public byte Mapper_69_Bank_6; - public bool Mapper_69_Bank_6_isRAM; - public bool Mapper_69_Bank_6_isRAMEnabled; - public byte Mapper_69_Bank_8; - public byte Mapper_69_Bank_A; - public byte Mapper_69_Bank_C; - public byte Mapper_69_NametableMirroring; // 0 = Vertical 1 = Horizontal 2 = One Screen Mirroring from $2000 ("1ScA") 3 = One Screen Mirroring from $2400 ("1ScB") - public bool Mapper_69_EnableIRQ; - public bool Mapper_69_EnableIRQCounterDecrement; - public ushort Mapper_69_IRQCounter; // When enabled the 16-bit IRQ counter is decremented once per CPU cycle. When the IRQ counter is decremented from $0000 to $FFFF an IRQ is generated. - public override 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 (CPU_AddressIn >= 0x6000) - { - ushort tempo = (ushort)(CPU_AddressIn % 0x2000); - if (CPU_AddressIn >= 0x6000) - { - //actions - if (CPU_AddressIn < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - - } - } - else - { //read from ROM - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_6 * 0x2000 + tempo) % Cart.PRGROM.Length]; - - } - } - else if (CPU_AddressIn < 0xA000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_8 * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn < 0xC000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_A * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn < 0xE000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_69_Bank_C * 0x2000 + tempo) % Cart.PRGROM.Length]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else - { - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x2000 + tempo]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x6000) - { - //actions - if (Address < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - //writing to RAM - Cart.PRGRAM[Address & 0x1FFF] = Input; - } //else, writing to open bus - } //else it's ROM. writing here does nothing. - } - else if (Address < 0xA000) - { - Mapper_69_CMD = (byte)(Input & 0x0F); - } - else if (Address < 0xC000) - { - switch (Mapper_69_CMD) - { - case 0: Mapper_69_CHR_1K0 = Input; break; - case 1: Mapper_69_CHR_1K1 = Input; break; - case 2: Mapper_69_CHR_1K2 = Input; break; - case 3: Mapper_69_CHR_1K3 = Input; break; - case 4: Mapper_69_CHR_1K4 = Input; break; - case 5: Mapper_69_CHR_1K5 = Input; break; - case 6: Mapper_69_CHR_1K6 = Input; break; - case 7: Mapper_69_CHR_1K7 = Input; break; - case 8: Mapper_69_Bank_6 = (byte)(Input & 0x3F); Mapper_69_Bank_6_isRAM = (Input & 0x40) != 0; Mapper_69_Bank_6_isRAMEnabled = (Input & 0x80) != 0; break; - case 9: Mapper_69_Bank_8 = (byte)(Input & 0x3F); break; - case 10: Mapper_69_Bank_A = (byte)(Input & 0x3F); break; - case 11: Mapper_69_Bank_C = (byte)(Input & 0x3F); break; - case 12: Mapper_69_NametableMirroring = (byte)(Input & 0x3); break; - case 13: Mapper_69_EnableIRQ = (Input & 0x1) != 0; Mapper_69_EnableIRQCounterDecrement = (Input & 0x80) != 0; Connector_IRQPin(false); break; - case 14: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF00) | Input); break; - case 15: Mapper_69_IRQCounter = (ushort)((Mapper_69_IRQCounter & 0xFF) | (Input << 8)); break; - } - } // else do nothing - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x6000) - { - ushort tempo = (ushort)(Address % 0x2000); - if (Address >= 0x6000) - { - //actions - if (Address < 0x8000) - { - if (Mapper_69_Bank_6_isRAM) - { - if (Mapper_69_Bank_6_isRAMEnabled) - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - } - else - { //read from ROM - return Cart.PRGROM[(Mapper_69_Bank_6 * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - } - else if (Address < 0xA000) - { - return Cart.PRGROM[(Mapper_69_Bank_8 * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else if (Address < 0xC000) - { - return Cart.PRGROM[(Mapper_69_Bank_A * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else if (Address < 0xE000) - { - return Cart.PRGROM[(Mapper_69_Bank_C * 0x2000 + tempo) % Cart.PRGROM.Length]; - } - else - { - return Cart.PRGROM[Cart.PRGROM.Length - 0x2000 + tempo]; - } - } - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - if (Address < 0x400) { return (Mapper_69_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x800) { Address &= 0x3FF; return (Mapper_69_CHR_1K1 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0xC00) { Address &= 0x3FF; return (Mapper_69_CHR_1K2 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x3FF; return (Mapper_69_CHR_1K3 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1400) { Address &= 0x3FF; return (Mapper_69_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x3FF; return (Mapper_69_CHR_1K5 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1C00) { Address &= 0x3FF; return (Mapper_69_CHR_1K6 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x3FF; return (Mapper_69_CHR_1K7 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - break; - case 1: //horizontal - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - break; - case 2: //one-screen A - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = false; } - break; - case 3: //one-screen B - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = true; } - break; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x400) != 0; - break; - case 1: //horizontal - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x800) != 0; - break; - case 2: //one-screen A - Cart.Emu.SeventyTwoPinConnector[21] = false; - break; - case 3: //one-screen B - Cart.Emu.SeventyTwoPinConnector[21] = true; - break; - } - } - } - public override 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); - switch (Mapper_69_NametableMirroring) - { - case 0: //vertical - Addr |= (ushort)(((Address & 0x400) != 0) ? 0x400 : 0); - break; - case 1: //horizontal - Addr |= (ushort)(((Address & 0x800) != 0) ? 0x400 : 0); - break; - case 2: //one-screen A - break; - case 3: //one-screen B - Addr |= 0x400; - break; - } - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_69_CMD); - State.Add(Mapper_69_CHR_1K0); - State.Add(Mapper_69_CHR_1K1); - State.Add(Mapper_69_CHR_1K2); - State.Add(Mapper_69_CHR_1K3); - State.Add(Mapper_69_CHR_1K4); - State.Add(Mapper_69_CHR_1K5); - State.Add(Mapper_69_CHR_1K6); - State.Add(Mapper_69_CHR_1K7); - State.Add(Mapper_69_Bank_6); - State.Add((byte)(Mapper_69_Bank_6_isRAM ? 1 : 0)); - State.Add((byte)(Mapper_69_Bank_6_isRAMEnabled ? 1 : 0)); - State.Add(Mapper_69_Bank_8); - State.Add(Mapper_69_Bank_A); - State.Add(Mapper_69_Bank_C); - State.Add(Mapper_69_NametableMirroring); - State.Add((byte)(Mapper_69_EnableIRQ ? 1 : 0)); - State.Add((byte)(Mapper_69_EnableIRQCounterDecrement ? 1 : 0)); - State.Add((byte)Mapper_69_IRQCounter); - State.Add((byte)(Mapper_69_IRQCounter >> 8)); - return State; - } - public override 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++]; } - } - Mapper_69_CMD = State[p++]; - Mapper_69_CHR_1K0 = State[p++]; - Mapper_69_CHR_1K1 = State[p++]; - Mapper_69_CHR_1K2 = State[p++]; - Mapper_69_CHR_1K3 = State[p++]; - Mapper_69_CHR_1K4 = State[p++]; - Mapper_69_CHR_1K5 = State[p++]; - Mapper_69_CHR_1K6 = State[p++]; - Mapper_69_CHR_1K7 = State[p++]; - Mapper_69_Bank_6 = State[p++]; - Mapper_69_Bank_6_isRAM = (State[p++] & 1) == 1; - Mapper_69_Bank_6_isRAMEnabled = (State[p++] & 1) == 1; - Mapper_69_Bank_8 = State[p++]; - Mapper_69_Bank_A = State[p++]; - Mapper_69_Bank_C = State[p++]; - Mapper_69_NametableMirroring = State[p++]; - Mapper_69_EnableIRQ = (State[p++] & 1) == 1; - Mapper_69_EnableIRQCounterDecrement = (State[p++] & 1) == 1; - Mapper_69_IRQCounter = State[p++]; - Mapper_69_IRQCounter |= (ushort)(State[p++] << 8); - exitIndex = p; - } - public override void CPUClock() - { - // The sunsoft FME-7 mapper chip has an IRQ counter that ticks down once per CPU cycle. - if (Mapper_69_EnableIRQCounterDecrement) - { - ushort temp = Mapper_69_IRQCounter; - Mapper_69_IRQCounter--; - if (Mapper_69_EnableIRQ && temp < Mapper_69_IRQCounter) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_GxROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_GxROM.cs deleted file mode 100644 index a42dbd3a..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_GxROM.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_GxROM : Mapper - { - // ines Mapper 66 - public byte Mapper_66_BankSelect_CHR; - public byte Mapper_66_BankSelect_PRG; - public override int FetchPatternAddress(ushort Address) - { - return (Mapper_66_BankSelect_CHR * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - public override 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 (CPU_AddressIn >= 0x8000) - { - CPU_DataOut = Cart.PRGROM[0x8000 * (Mapper_66_BankSelect_PRG) + (CPU_AddressIn & 0x7FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_66_BankSelect_CHR = (byte)(Input & 0x3); - Mapper_66_BankSelect_PRG = (byte)((Input & 0x30) >> 4); - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - return Cart.PRGROM[0x8000 * (Mapper_66_BankSelect_PRG) + (Address & 0x7FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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); } - } - State.Add(Mapper_66_BankSelect_CHR); - State.Add(Mapper_66_BankSelect_PRG); - return State; - } - public override 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++]; } - } - Mapper_66_BankSelect_CHR = State[p++]; - Mapper_66_BankSelect_PRG = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC1.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC1.cs deleted file mode 100644 index 96afee4a..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC1.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC1 : Mapper - { - // ines Mapper 1 - public byte Mapper_1_ShiftRegister; - public byte Mapper_1_Control = 0x0C; //0x8000 - public byte Mapper_1_CHR0; //0xA000 - public byte Mapper_1_CHR1; //0xC000 - public byte Mapper_1_PRG; //0xE000 - public bool Mapper_1_PB; - public override 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 (CPU_AddressIn >= 0x8000) - { - // The bank mode for MMC1: - byte MMC1PRGROMBankMode = (byte)((Mapper_1_Control & 0b01100) >> 2); - switch (MMC1PRGROMBankMode) - { - case 0: - case 1: - { - // switch 32 KB at $8000, ignoring low bit of bank number - ushort tempo = (ushort)(CPU_AddressIn & 0x7FFF); - CPU_DataOut = Cart.PRGROM[(0x8000 * (Mapper_1_PRG & 0x0E) + tempo) % Cart.PRGROM.Length]; - } - break; - case 2: - // fix first bank at $8000 and switch 16 KB bank at $C000 - if (CPU_AddressIn >= 0xC000) - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[0x4000 * (Mapper_1_PRG) + tempo]; - } - else - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[tempo]; - } - break; - case 3: - // fix last bank at $C000 and switch 16 KB bank at $8000 - if (CPU_AddressIn >= 0xC000) - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; - } - else - { - ushort tempo = (ushort)(CPU_AddressIn & 0x3FFF); - CPU_DataOut = Cart.PRGROM[(0x4000 * (Mapper_1_PRG & 0x0F) + tempo) & (Cart.PRGROM.Length - 1)]; - } - break; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else // if the address is < $8000 - { - if (((Mapper_1_PRG & 0x10) == 0) && CPU_AddressIn >= 0x6000) // if Work RAM is enabled - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - // else, open bus. - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0x8000) //WRAM not available on MMC1A - { - if (((Mapper_1_PRG & 0x10) == 0) /*&& Mapper != 1*/) - { - //Battery backed RAM - Cart.PRGRAM[Address & 0x1FFF] = Input; - return; - } - else - { - return; //do nothing - } - } - else - { // shift the shirftRegister and add the new bit - Mapper_1_PB = (Mapper_1_ShiftRegister & 1) == 1; - Mapper_1_ShiftRegister >>= 1; - Mapper_1_ShiftRegister |= (byte)((Input & 1) << 4); - } - if (Mapper_1_PB) // if the '1' that was initialized in bit 4 is shifted into the bus - { - // copy shift register to the desired internal register. - switch (Address & 0xE000) - { - case 0x8000: //control - Mapper_1_Control = Mapper_1_ShiftRegister; - break; - case 0xA000: //CHR0 - Mapper_1_CHR0 = Mapper_1_ShiftRegister; - break; - case 0xC000: //CHR1 - Mapper_1_CHR1 = Mapper_1_ShiftRegister; - break; - case 0xE000: //PRG - Mapper_1_PRG = Mapper_1_ShiftRegister; - break; - } - Mapper_1_ShiftRegister = 0b10000; - } - if ((Input & 0b10000000) != 0) - { - Mapper_1_ShiftRegister = 0b10000; - Mapper_1_Control |= 0b01100; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - // The bank mode for MMC1: - byte MMC1PRGROMBankMode = (byte)((Mapper_1_Control & 0b01100) >> 2); - switch (MMC1PRGROMBankMode) - { - case 0: - case 1: - { - // switch 32 KB at $8000, ignoring low bit of bank number - ushort tempo = (ushort)(Address & 0x7FFF); - return Cart.PRGROM[(0x8000 * (Mapper_1_PRG & 0x0E) + tempo) % Cart.PRGROM.Length]; - } - break; - case 2: - // fix first bank at $8000 and switch 16 KB bank at $C000 - if (Address >= 0xC000) - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[0x4000 * (Mapper_1_PRG) + tempo]; - } - else - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[tempo]; - } - break; - case 3: - // fix last bank at $C000 and switch 16 KB bank at $8000 - if (Address >= 0xC000) - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + tempo]; - } - else - { - ushort tempo = (ushort)(Address & 0x3FFF); - return Cart.PRGROM[(0x4000 * (Mapper_1_PRG & 0x0F) + tempo) & (Cart.PRGROM.Length - 1)]; - } - break; - } - } - else // if the address is < $8000 - { - if (((Mapper_1_PRG & 0x10) == 0) && Address >= 0x6000) // if Work RAM is enabled - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - // else, open bus. - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - // bit 4 of Mapper_1_Control controls how the pattern tables are swapped. if set, 2 banks of 4Kib. Otherwise, 1 8Kib bank - if ((Mapper_1_Control & 0x10) != 0) - { - // with the MMC1 chip, you can swap out the pattern tables. - // address < 0x1000 is the first pattern table, else, the second pattern table. - // if the final write for the MMC1 shift register was in the $A000 - $BFFF, this updates Mapper_1_CHR0 - // if the final write for the MMC1 shift register was in the $B000 - $CFFF, this updates Mapper_1_CHR1 - if (Address < 0x1000) { return ((Mapper_1_CHR0 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0xFFF; return ((Mapper_1_CHR1 & 0x1F) * 0x1000 + Address) & (Cart.CHRROM.Length - 1); } - } - else // one swappable bank that changes both pattern tables. - { - // this uses the value written to Mapper_1_CHR0 - return ((Mapper_1_CHR0 & 0b11111110) * 0x2000 + Address) & (Cart.CHRROM.Length - 1); - } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = false; } - break; - case 1: //one screen, high - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = true; } - break; - case 2: //vertical - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - break; - case 3: //horizontal - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - break; - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - Cart.Emu.SeventyTwoPinConnector[21] = false; - break; - case 1: //one screen, high - Cart.Emu.SeventyTwoPinConnector[21] = true; - break; - case 2: //vertical - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x400) != 0; - break; - case 3: //horizontal - Cart.Emu.SeventyTwoPinConnector[21] = (Cart.Emu.PPU_AddressBus & 0x800) != 0; - break; - } - } - } - public override 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); - switch (Mapper_1_Control & 3) - { - case 0: //one screen, low - break; - case 1: //one screen, high - Addr |= 0x400; - break; - case 2: //vertical - Addr |= (ushort)(((Address & 0x400) != 0) ? 0x400 : 0); - break; - case 3: //horizontal - Addr |= (ushort)(((Address & 0x800) != 0) ? 0x400 : 0); - break; - } - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_1_ShiftRegister); - State.Add(Mapper_1_Control); - State.Add(Mapper_1_CHR0); - State.Add(Mapper_1_CHR1); - State.Add(Mapper_1_ShiftRegister); - State.Add((byte)(Mapper_1_PB ? 1 : 0)); - return State; - } - public override 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++]; } - } - Mapper_1_ShiftRegister = State[p++]; - Mapper_1_Control = State[p++]; - Mapper_1_CHR0 = State[p++]; - Mapper_1_CHR1 = State[p++]; - Mapper_1_ShiftRegister = State[p++]; - Mapper_1_PB = (State[p++] & 1) == 1; - exitIndex = p; - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC2.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC2.cs deleted file mode 100644 index 92d0c976..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC2.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC2 : Mapper - { - // ines Mapper 9 - public byte Mapper_9_BankSelect; - public byte Mapper_9_CHR0_FD; - public byte Mapper_9_CHR0_FE; - public byte Mapper_9_CHR1_FD; - public byte Mapper_9_CHR1_FE; - public bool Mapper_9_NametableMirroring; - public bool Mapper_9_Latch0_FE; - public bool Mapper_9_Latch1_FE; - public override 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 (CPU_AddressIn >= 0xA000) - { - CPU_DataOut = Cart.PRGROM[((Cart.PRG_Size - 2) << 14) | (CPU_AddressIn & 0x7FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if(CPU_AddressIn >= 0x8000) - { - CPU_DataOut = Cart.PRGROM[(Mapper_9_BankSelect << 13) | (CPU_AddressIn & 0x1FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0xA000) - { - // nothing - } - else if (Address < 0xB000) // PRG Bank select - { - Mapper_9_BankSelect = (byte)(Input & 0x0F); - } - else if (Address < 0xC000) // CHR0 Bank select - { - Mapper_9_CHR0_FD = (byte)(Input & 0x1F); - } - else if (Address < 0xD000) // CHR0 Bank select - { - Mapper_9_CHR0_FE = (byte)(Input & 0x1F); - } - else if (Address < 0xE000) // CHR1 Bank select - { - Mapper_9_CHR1_FD = (byte)(Input & 0x1F); - } - else if (Address < 0xF000) // CHR1 Bank select - { - Mapper_9_CHR1_FE = (byte)(Input & 0x1F); - } - else // Nametable mirroring - { - Mapper_9_NametableMirroring = (Input & 0x1) == 1; - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xA000) - { - return Cart.PRGROM[((Cart.PRG_Size - 2) << 14) | (Address & 0x7FFF)]; - } - else if (Address >= 0x8000) - { - return Cart.PRGROM[(Mapper_9_BankSelect << 13) | (Address & 0x1FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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]) { - PPU_DataOut = Cart.CHRROM[CHR_Address]; - Connector_SetUpPPUDataPins(PPU_DataOut); - // MMC2 has registers that do things based on the address being read. - if (Address == 0x0FD8) - { - Mapper_9_Latch0_FE = false; - } - else if (Address == 0x0FE8) - { - Mapper_9_Latch0_FE = true; - } - else if (Address >= 0x1FD8 && Address <= 0x1FDF) - { - Mapper_9_Latch1_FE = false; - } - else if (Address >= 0x1FE8 && Address <= 0x1FEF) - { - Mapper_9_Latch1_FE = true; - } - } // Reads - if (!Cart.Emu.SeventyTwoPinConnector[55] && Cart.UsingCHRRAM) { Cart.CHRROM[CHR_Address] = PPU_DataIn; } // Writes - } - } - - public override int FetchPatternAddress(ushort Address) - { - ushort Addr = Address; - if (Address < 0x1000) { return (Mapper_9_Latch0_FE ? Mapper_9_CHR0_FE : Mapper_9_CHR0_FD) * 0x1000 + Addr; } - else { Addr &= 0xFFF; return (Mapper_9_Latch1_FE ? Mapper_9_CHR1_FE : Mapper_9_CHR1_FD) * 0x1000 + Addr; } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - if (Mapper_9_NametableMirroring) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - Cart.Emu.SeventyTwoPinConnector[21] = Mapper_9_NametableMirroring ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - } - } - public override 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)((Mapper_9_NametableMirroring ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - State.Add(Mapper_9_BankSelect); - State.Add(Mapper_9_CHR0_FD); - State.Add(Mapper_9_CHR0_FE); - State.Add(Mapper_9_CHR1_FD); - State.Add(Mapper_9_CHR1_FE); - State.Add((byte)(Mapper_9_NametableMirroring ? 1 : 0)); - State.Add((byte)(Mapper_9_Latch0_FE ? 1 : 0)); - State.Add((byte)(Mapper_9_Latch1_FE ? 1 : 0)); return State; - } - public override 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++]; } - } - Mapper_9_BankSelect = State[p++]; - Mapper_9_CHR0_FD = State[p++]; - Mapper_9_CHR0_FE = State[p++]; - Mapper_9_CHR1_FD = State[p++]; - Mapper_9_CHR1_FE = State[p++]; - Mapper_9_NametableMirroring = (State[p++] & 1) == 1; - Mapper_9_Latch0_FE = (State[p++] & 1) == 1; - Mapper_9_Latch1_FE = (State[p++] & 1) == 1; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC3.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC3.cs deleted file mode 100644 index e35b6d3c..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_MMC3.cs +++ /dev/null @@ -1,487 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_MMC3 : Mapper - { - // ines Mapper 4 - public byte Mapper_4_8000; // The value written to $8000 (or any even address between $8000 and $9FFE) - public byte Mapper_4_BankA; // The PRG bank between $A000 and $BFFF - public byte Mapper_4_Bank8C; // The PRG bank that could either be at $8000 through 9FFF, or $C000 through $DFFF - public byte Mapper_4_CHR_2K0; - public byte Mapper_4_CHR_2K8; - public byte Mapper_4_CHR_1K0; - public byte Mapper_4_CHR_1K4; - public byte Mapper_4_CHR_1K8; - public byte Mapper_4_CHR_1KC; - public byte Mapper_4_IRQLatch; - public byte Mapper_4_IRQCounter; - public bool Mapper_4_EnableIRQ; - public bool Mapper_4_ReloadIRQCounter; - public bool Mapper_4_NametableMirroring; // MMC3 has it's own way of controlling how the nametables are mirrored. - public byte Mapper_4_PRGRAMProtect; - public byte Mapper_4_M2Filter; - public bool Mapper_4_PPUA12; - public override 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 (CPU_AddressIn >= 0xE000) // This bank is fixed the the final PRG bank of the ROM - { - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x3FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0xC000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$C000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (CPU_AddressIn & 0x1FFF)]; - } - else - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x1FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0xA000) - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_BankA << 13) | (CPU_AddressIn & 0x1FFF)]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0x8000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$8000 swappable - CPU_DataOut = Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (CPU_AddressIn & 0x1FFF)]; - } - else - { - //$C000 swappable - CPU_DataOut = Cart.PRGROM[(Mapper_4_Bank8C << 13) | (CPU_AddressIn & 0x1FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - else if (CPU_AddressIn >= 0x6000) - { - if (Cart.SubMapper == 1) // MMC6 - { - if ((Mapper_4_8000 & 0x20) != 0) - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if (CPU_AddressIn >= 0x7000 && CPU_AddressIn <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x20) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x3FF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - else if (CPU_AddressIn >= 0x7200 && CPU_AddressIn <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x3FF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - } - else - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - CPU_DataOut = Cart.PRGRAM[CPU_AddressIn & 0x1FFF]; - Connector_SetUpCPUDataPins(CPU_DataOut); - } - } - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address < 0x8000) - { //Battery backed RAM - - if (Cart.SubMapper == 1) // MMC6 - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if ((Mapper_4_8000 & 0x20) != 0) - { - if (Address >= 0x7000 && Address <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x10) != 0) - { - Cart.PRGRAM[Address & 0x3FF] = Input; - - } - } - else if (Address >= 0x7200 && Address <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x40) != 0) - { - Cart.PRGRAM[Address & 0x3FF] = Input; - } - } - } - } - else if ((Mapper_4_PRGRAMProtect & 0xC0) == 0x80) // bit 7 enables PRG RAM, bit 6 disables writing there. - { - Cart.PRGRAM[Address & 0x1FFF] = Input; - } - return; - } - else - { - ushort tempo = (ushort)(Address & 0xE001); - switch (tempo) - { - case 0x8000: - Mapper_4_8000 = Input; - return; - case 0x8001: - byte mode = (byte)(Mapper_4_8000 & 7); - switch (mode) - { - case 0: //PPU ($0000 - $07FF) ?+ $1000 - Mapper_4_CHR_2K0 = (byte)(Input & 0xFE); - return; - case 1: //PPU ($0800 - $0FFF) ?+ $1000 - Mapper_4_CHR_2K8 = (byte)(Input & 0xFE); - return; - case 2: //PPU ($1000 - $13FF) ?- $1000 - Mapper_4_CHR_1K0 = Input; - return; - case 3: //PPU ($1400 - $17FF) ?- $1000 - Mapper_4_CHR_1K4 = Input; - return; - case 4: //PPU ($1800 - $1BFF) ?- $1000 - Mapper_4_CHR_1K8 = Input; - return; - case 5: //PPU ($1C00 - $1FFF) ?- $1000 - Mapper_4_CHR_1KC = Input; - return; - case 6: //PRG ($8000 - $9FFF) ?+ 0x4000 - Mapper_4_Bank8C = (byte)(Input & (Cart.PRG_Size * 2 - 1)); - return; - case 7: //PRG ($A000 - $BFFF) - Mapper_4_BankA = (byte)(Input & (Cart.PRG_Size * 2 - 1)); - return; - } - return; - case 0xA000: - Mapper_4_NametableMirroring = (Input & 1) == 1; - return; - case 0xA001: - Mapper_4_PRGRAMProtect = Input; - return; - case 0xC000: - Mapper_4_IRQLatch = Input; - return; - case 0xC001: - Mapper_4_IRQCounter = 0xFF; - Mapper_4_ReloadIRQCounter = true; - return; - case 0xE000: - Mapper_4_EnableIRQ = false; - Connector_IRQPin(false); // Acknowledge the IRQ - return; - case 0xE001: - Mapper_4_EnableIRQ = true; - return; - } - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0xE000) // This bank is fixed the the final PRG bank of the ROM - { - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x3FFF)]; - } - else if (Address >= 0xC000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$C000 swappable - return Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; - } - else - { - //$8000 swappable - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; - } - } - else if (Address >= 0xA000) - { - //$8000 swappable - return Cart.PRGROM[(Mapper_4_BankA << 13) | (Address & 0x1FFF)]; - } - else if (Address >= 0x8000) - { - if ((Mapper_4_8000 & 0x40) == 0x40) - { - //$8000 swappable - return Cart.PRGROM[(Cart.PRG_SizeMinus1 << 14) | (Address & 0x1FFF)]; - } - else - { - //$C000 swappable - return Cart.PRGROM[(Mapper_4_Bank8C << 13) | (Address & 0x1FFF)]; - } - } - else if (Address >= 0x6000) - { - if (Cart.SubMapper == 1) // MMC6 - { - if ((Mapper_4_8000 & 0x20) != 0) - { - // MMC6 differs from MMC3 since there's only 1Kib of PRG RAM - if (Address >= 0x7000 && Address <= 0x71FF) - { - if ((Mapper_4_PRGRAMProtect & 0x20) != 0) - { - return Cart.PRGRAM[Address & 0x3FF]; - } - } - else if (Address >= 0x7200 && Address <= 0x73FF) - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - return Cart.PRGRAM[Address & 0x3FF]; - } - } - } - } - else - { - if ((Mapper_4_PRGRAMProtect & 0x80) != 0) - { - return Cart.PRGRAM[Address & 0x1FFF]; - } - } - } - return Cart.Emu.dataBus; - } - public override int FetchPatternAddress(ushort Address) - { - //Writes to $8000 determine the mode, writes to $8001 determine the banks - if ((Mapper_4_8000 & 0x80) == 0) // bit 7 of the previous write to $8000 determines which pattern table is 2 2kb banks, and which is 4 1kb banks. - { - if (Address < 0x800) { return (Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x7FF; return (Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1400) { Address &= 0x3FF; return (Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x3FF; return (Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1C00) { Address &= 0x3FF; return (Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x3FF; return (Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - else - { - if (Address < 0x400) { return (Mapper_4_CHR_1K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x800) { Address &= 0x3FF; return (Mapper_4_CHR_1K4 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0xC00) { Address &= 0x3FF; return (Mapper_4_CHR_1K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1000) { Address &= 0x3FF; return (Mapper_4_CHR_1KC * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else if (Address < 0x1800) { Address &= 0x7FF; return (Mapper_4_CHR_2K0 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - else { Address &= 0x7FF; return (Mapper_4_CHR_2K8 * 0x400 + Address) & (Cart.CHRROM.Length - 1); } - } - } - public override void Connector_CheckCIRAM() - { - if (TiltingCart) - { - if (Cart.AlternativeNametableArrangement) - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57] || Cart.Emu.SeventyTwoPinConnector[61]; } - } - else - { - if (!Cart.Emu.ConnectorPinFloating[56]) { Cart.Emu.SeventyTwoPinConnector[56] = Cart.Emu.SeventyTwoPinConnector[57]; } - } - - if (Mapper_4_NametableMirroring) //horizontal - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[61]; } - } - else //vertical - { - if (!Cart.Emu.ConnectorPinFloating[21]) { Cart.Emu.SeventyTwoPinConnector[21] = Cart.Emu.SeventyTwoPinConnector[62]; } - } - } - else - { - if (Cart.AlternativeNametableArrangement) - { - Cart.Emu.SeventyTwoPinConnector[61] = (Cart.Emu.PPU_AddressBus & 0x0800) != 0; - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0 || Cart.Emu.SeventyTwoPinConnector[61]; - } - else - { - Cart.Emu.SeventyTwoPinConnector[56] = (Cart.Emu.PPU_AddressBus & 0x2000) == 0; - } - Cart.Emu.SeventyTwoPinConnector[21] = Mapper_4_NametableMirroring ? ((Cart.Emu.PPU_AddressBus & 0x800) != 0) : ((Cart.Emu.PPU_AddressBus & 0x400) != 0); - - } - } - public override 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 if (Cart.AlternativeNametableArrangement && Address >= 0x2800) // 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. - return Cart.PRGVRAM[Address]; - } - else - { - ushort Addr = (ushort)(Address & 0x3FF); - Addr |= (ushort)((Mapper_4_NametableMirroring ? ((Address & 0x800) != 0) : ((Address & 0x400) != 0)) ? 0x400 : 0); - return Cart.Emu.VRAM[Addr]; - } - } - - public override 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); } - } - if (Cart.PRGVRAM != null) - { - foreach (Byte b in Cart.PRGVRAM) { State.Add(b); } - } - State.Add(Mapper_4_8000); - State.Add(Mapper_4_BankA); - State.Add(Mapper_4_Bank8C); - State.Add(Mapper_4_CHR_2K0); - State.Add(Mapper_4_CHR_2K8); - State.Add(Mapper_4_CHR_1K0); - State.Add(Mapper_4_CHR_1K4); - State.Add(Mapper_4_CHR_1K8); - State.Add(Mapper_4_CHR_1KC); - State.Add(Mapper_4_IRQLatch); - State.Add(Mapper_4_IRQCounter); - State.Add((byte)(Mapper_4_EnableIRQ ? 1 : 0)); - State.Add((byte)(Mapper_4_ReloadIRQCounter ? 1 : 0)); - State.Add((byte)(Mapper_4_NametableMirroring ? 1 : 0)); - State.Add(Mapper_4_PRGRAMProtect); - State.Add(Mapper_4_M2Filter); - State.Add((byte)(Mapper_4_PPUA12 ? 1 : 0)); - return State; - } - public override 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++]; } - } - if (Cart.PRGVRAM != null) - { - for (int i = 0; i < Cart.PRGVRAM.Length; i++) { Cart.PRGVRAM[i] = State[p++]; } - } - Mapper_4_8000 = State[p++]; - Mapper_4_BankA = State[p++]; - Mapper_4_Bank8C = State[p++]; - Mapper_4_CHR_2K0 = State[p++]; - Mapper_4_CHR_2K8 = State[p++]; - Mapper_4_CHR_1K0 = State[p++]; - Mapper_4_CHR_1K4 = State[p++]; - Mapper_4_CHR_1K8 = State[p++]; - Mapper_4_CHR_1KC = State[p++]; - Mapper_4_IRQLatch = State[p++]; - Mapper_4_IRQCounter = State[p++]; - Mapper_4_EnableIRQ = (State[p++] & 1) == 1; - Mapper_4_ReloadIRQCounter = (State[p++] & 1) == 1; - Mapper_4_NametableMirroring = (State[p++] & 1) == 1; - Mapper_4_PRGRAMProtect = State[p++]; - Mapper_4_M2Filter = State[p++]; - Mapper_4_PPUA12 = (State[p++] & 1) == 1; - exitIndex = p; - } - - public override void PPUClock() - { - if (!TiltingCart) - { - Cart.Emu.SeventyTwoPinConnector[63] = (Cart.Emu.PPU_AddressBus & 0x1000) != 0; - } - else - { - Connector_SetUpPPUAddressPins(); - } - // if bit 12 of the ppu address bus (A12) changes: - if (!Mapper_4_PPUA12 && Cart.Emu.SeventyTwoPinConnector[63] && Mapper_4_M2Filter == 3) - { - if (Mapper_4_ReloadIRQCounter) - { - // If we're reloading the IRQ counter - Mapper_4_IRQCounter = Mapper_4_IRQLatch; // The latch is the reset value. - Mapper_4_ReloadIRQCounter = false; - if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ. - { - if (Mapper_4_EnableIRQ) // if setting the value to zero, run an IRQ - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - else - { - // decrement the counter - Mapper_4_IRQCounter--; - if (Mapper_4_IRQCounter == 0) // if decrementing the counter moved it to 0... - { - if (Mapper_4_EnableIRQ) // and the MMC3 IRQ is enabled... - { - Connector_IRQPin(true); // Run an IRQ! - } - } - else if (Mapper_4_IRQCounter == 255) // if the counter underflows... - { - Mapper_4_IRQCounter = Mapper_4_IRQLatch; // reset the irq counter - if (Mapper_4_IRQCounter == 0) // if the latch is set to 0, you need to enable the IRQ... again - { - if (Mapper_4_EnableIRQ) - { - Connector_IRQPin(true); // Run an IRQ! - } - } - } - - } - } - if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) != 0) - { - Mapper_4_M2Filter = 0; - } - Mapper_4_PPUA12 = Cart.Emu.SeventyTwoPinConnector[63]; - } - public override void CPUClockRise() - { - if (Cart.Emu.ConnectorPinFloating[37]) { return; } // If the M2 pin is disconnected, this step doesn't work. - if ((Cart.Emu.PPU_AddressBus & 0b0001000000000000) == 0) - { - if (Mapper_4_M2Filter < 3) - { - Mapper_4_M2Filter++; - } - } - } - - public override string AppendToDebugLog() - { - return "\tPPU_Coords (" + Cart.Emu.PPU_Scanline + ", " + Cart.Emu.PPU_Dot + ")\tIRQTimer:" + Mapper_4_IRQCounter + "\tIRQLatch: " + Mapper_4_IRQLatch + "\tIRQEnabled: " + Mapper_4_EnableIRQ + "\tDoIRQ: " + Cart.Emu.DoIRQ + "\tPPU_ADDR_Prev: " + (Mapper_4_PPUA12 ? "1" : "0"); - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NROM.cs deleted file mode 100644 index 3b2ba041..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NROM.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TriCNES.mappers -{ - public class Mapper_NROM : Mapper - { - // ines Mapper 0 - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NULL.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NULL.cs deleted file mode 100644 index 784555b8..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_NULL.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_NULL : Mapper - { - // There is not a cartridge inserted in the console. - - public override void FetchCPU() - { - // the data pins are always floating. There's no cartridge inserted! - return; - } - - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - return Cart.Emu.dataBus; // There is nothing inserted here. The CPU *must* see open bus. - } - - public override void AccessPPU() - { - // the data pins are always floating. There's no cartridge inserted! - return; - } - - public override 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. - { - return (byte)Cart.Emu.addressBus; // There is nothing inserted here. The PPU *must* see open bus. - } - - public override int FetchPatternAddress(ushort Address) - { - // there's no cartridge. TODO: Look into this. Supposedly this would likely be the lower 8 bits of the address bus, but CIRAM enable is also floating. - return 0; - } - public override void Connector_CheckCIRAM() - { - // CIRAM is left floating. There's no cartridge inserted! - } - public override List SaveMapperRegisters() - { - List State = new List(); - return State; - } - public override void LoadMapperRegisters(List State, int startIndex, out int exitIndex) - { - int p = startIndex; - exitIndex = p; - } - public override bool CheckCIC() - { - return false; // There's no CIC chip! - } - } -} diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_UxROM.cs b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_UxROM.cs deleted file mode 100644 index 83adf1f1..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/mappers/Mapper_UxROM.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TriCNES.mappers -{ - public class Mapper_UxROM : Mapper - { - // ines Mapper 2 - public byte Mapper_2_BankSelect; - public override 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 (CPU_AddressIn >= 0x8000) - { - if (CPU_AddressIn >= 0xC000) - { - CPU_DataOut = Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + (CPU_AddressIn & 0x3FFF)]; - } - else - { - CPU_DataOut = Cart.PRGROM[0x4000 * (Mapper_2_BankSelect & 0x0F) + (CPU_AddressIn & 0x3FFF)]; - } - Connector_SetUpCPUDataPins(CPU_DataOut); - } - - return; - } - public override void StoreCPU(ushort Address, byte Input) - { - if (Address >= 0x8000) - { - Mapper_2_BankSelect = (byte)(Input & 0xF); - } - } - public override byte SnoopCPU(ushort Address) // For debug purposes. It's a bit clunky. - { - if (Address >= 0x8000) - { - if (CPU_AddressIn >= 0xC000) - { - return Cart.PRGROM[Cart.PRGROM.Length - 0x4000 + (Address & 0x3FFF)]; - } - return Cart.PRGROM[0x4000 * (Mapper_2_BankSelect & 0x0F) + (Address & 0x3FFF)]; - } - return Cart.Emu.dataBus; - } - public override 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); } - } - State.Add(Mapper_2_BankSelect); - return State; - } - public override 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++]; } - } - Mapper_2_BankSelect = State[p++]; - exitIndex = p; - } - } -} \ No newline at end of file diff --git a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/tricnes-harness.csproj b/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/tricnes-harness.csproj deleted file mode 100644 index 329058e3..00000000 --- a/crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/tricnes-harness.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - Exe - net10.0 - disable - true - false - CS0162;CS0168;CS0219;CS0414;CS0649 - disable - - - - - diff --git a/docs/agents/accuracy-oracle.md b/docs/agents/accuracy-oracle.md index e5d5ba44..1e290716 100644 --- a/docs/agents/accuracy-oracle.md +++ b/docs/agents/accuracy-oracle.md @@ -11,7 +11,7 @@ > written to be re-checkable, not to be taken on trust. - **`pre-commit run --all-files` REWRITES vendored/immutable trees — use `--files ` or a single named hook instead.** `trailing-whitespace` / `end-of-file-fixer` / `mixed-line-ending` *modify* files and, before PR #320, had no `exclude` at all: one `--all-files` run silently reformatted **41 files** across the vendored TriCNES C#, vendored rcheevos C, `ref-docs/`, an upstream font licence, and upstream test-ROM READMEs — destroying exactly the byte-identical-to-upstream property those trees exist for. `.markdownlintignore` covered them for markdownlint only. #320 added a shared `exclude` anchor across the three rewriting hooks, scoped deliberately **narrower** than `.markdownlintignore`: only content we did not author. Frozen-but-ours trees (`docs/archive/`, `to-dos/plans/`) stay in scope, since the invariant is "don't rewrite what we didn't write". If it happens anyway, revert **only** the unintended paths (never a blanket `git checkout`, and never including your own edits). -- **THE VENDORED TriCNES AND AccuracyCoin TREES ARE NOT OURS TO PATCH, and a reviewer will suggest patching them (2026-09-19).** `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` is `100thCoin/TriCNES` **byte-identical to upstream** (`94f1b117`; verify by recursive diff against a fresh clone, expect `0 differing files`), and `tricnes-harness-src/` is that same source plus **exactly 88 instrumentation lines** — an invariant worth asserting after any sync, because it is what proves the instrumentation survived and nothing else moved. TriCNES is the **oracle** the core is diffed against; patching emulator *behaviour* into it would mean the thing we call TriCNES is no longer TriCNES, silently, and in exactly the direction that flatters us. CodeRabbit raised a real observation on this tree (`Reset()` assigns `CPU_SYNC` and `DoReset` and does **not** clear `DoNMI`) and the correct answer was to **decline the fix and record it as an upstream candidate**, not to apply it. The route for a genuine defect is an upstream report — which works: `46199ae4` in AccuracyCoin is upstream's fix for a defect reported from here. The same applies to `tests/roms/accuracycoin/` — the ROM is upstream's artifact, and our only local products are `SOURCE_CATALOG.tsv` (regenerated by script, never hand-edited) and the `sub-tests/` ROMs (built by `build_sub_test_rom.py`, which needs `--wine /usr/bin/wine`). +- **THE VENDORED TriCNES AND AccuracyCoin TREES ARE NOT OURS TO PATCH, and a reviewer will suggest patching them (2026-09-19).** The TriCNES source was vendored until v3.0.1 and now lives outside the repository (`~/reference-oracles/TriCNES` at `94f1b117`, `~/reference-oracles/TriCNES-rustynes-harness`; guardrails section 3a), and the rule moved with it. The upstream copy is `100thCoin/TriCNES` **byte-identical to upstream** (checked file-for-file on removal, 2026-10-07), and the harness is that same source plus **exactly 88 instrumentation lines** — an invariant worth asserting after any sync, because it is what proves the instrumentation survived and nothing else moved. TriCNES is the **oracle** the core is diffed against; patching emulator *behaviour* into it would mean the thing we call TriCNES is no longer TriCNES, silently, and in exactly the direction that flatters us. CodeRabbit raised a real observation on this tree (`Reset()` assigns `CPU_SYNC` and `DoReset` and does **not** clear `DoNMI`) and the correct answer was to **decline the fix and record it as an upstream candidate**, not to apply it. The route for a genuine defect is an upstream report — which works: `46199ae4` in AccuracyCoin is upstream's fix for a defect reported from here. The same applies to `tests/roms/accuracycoin/` — the ROM is upstream's artifact, and our only local products are `SOURCE_CATALOG.tsv` (regenerated by script, never hand-edited) and the `sub-tests/` ROMs (built by `build_sub_test_rom.py`, which needs `--wine /usr/bin/wine`). - **A MISSING ACCURACYCOIN SUB-TEST ROM CAN BE ONE BYTE AWAY (v2.6.20).** `scripts/accuracycoin-build/build_sub_test_rom.py` injects the suite and test indices as plain `LDY #suite` / `LDX #test` immediates, and they **survive into the assembled binary**. In `advanced-sprite-eval-frozen-oam2-increment.nes` they sit at file offset `0x10AB` (`A0 15`) and `0x10B5` (`A2 02`), so a sibling sub-test is that ROM with one byte changed — no `.asm`, no build. Confirm the index from TWO sources before patching (`BUILD-PROVENANCE.tsv` records the base ROM's suite/test, and `SOURCE_CATALOG.tsv`'s row ORDER within the suite gives the sibling's index), then validate with `validate_sub_test_rom ` before trusting it. For `Misaligned OAM2 Address` — the LAST catalog row, so 134 M cycles and ~35 minutes per battery attempt — this turned the loop into **30 seconds**, which is the only reason two experiments were affordable and the second is what found the real defect. **Wire the instrument before debugging the entry**, and note the corollary: `$0050`-style scratch bytes survive a battery run only because the test is last, so truncating the run to save time gives you whichever test wrote that byte last. - **A test ROM's own source is the SPECIFICATION, and checking whether it is reachable costs one command.** Three AccuracyCoin entries resisted a full working session of tracing and hypothesis in v2.6.4. AccuracyCoin is **MIT-licensed and its assembly source is one `curl` away**; it carries a prose explanation of every assertion, written by the author who chose the stimulus, and it settled all three in minutes — naming a rule the nesdev pages do not state at all (`$4015` reads are internal to the 2A03, so the data bus is not driven), giving the exact failing stimulus (`LDX #$16 / LDA $40FF,X`, which matched a trace divergence found independently), and distinguishing the neighbouring assertions a broad "fix" would break. The v2.6.4 plan's own note that the source "is not vendored" is true of this repository and had been read as *unavailable*; they are not the same statement. **A test ROM is stimulus, not a reference implementation, so reading it raises no firewall question** — check its licence, then read it before theorising about its verdict. The same holds for blargg's `readme.txt`, which states two APU rules the wiki does not. **And decode its failure codes from the macro, never by inference**: `TEST_Fail` reports `(ErrorCode << 2) | 2` and the runner sets `ErrorCode` to **1** before every routine, so `Fail(N)` names test N one-based — read as a zero-based index it is off by one, and in v2.6.4 that made a **regression** (test 7 -> test 5) read as *progress*, a description that reached a code comment before the macro was read. It also retires v2.6.3's reading that six entries "sharing one failure code" implied one shared cause: the code indexes within one routine, so two entries sharing it share nothing. diff --git a/docs/ai-emulator-provenance-guardrails.md b/docs/ai-emulator-provenance-guardrails.md index 9d1518a4..b66904ab 100644 --- a/docs/ai-emulator-provenance-guardrails.md +++ b/docs/ai-emulator-provenance-guardrails.md @@ -119,6 +119,26 @@ sandbox mounts, etc.), express the firewall there. A rule the runtime enforces b agent is merely asked to follow — because the failure mode is precisely an agent that *doesn't* follow the asked rule. +### 3a. The one named exception: TriCNES (maintainer decision, 2026-10-07) + +TriCNES (Chris "100th_Coin" Siebert, the AccuracyCoin author; MIT, verified against +`100thCoin/TriCNES` on 2026-10-07) is the single reference whose **source** may be consulted, +and only on these terms: + +- **For AccuracyCoin work only**, when a test its author has already passed in TriCNES is being + troubleshot, and only after documentation, the Internet and black-box comparison (rungs 1-3 of + the AGENTS.md escalation ladder) have not settled it. +- **From outside the repository.** Since v3.0.1 the upstream clone lives at + `~/reference-oracles/TriCNES` (commit `94f1b117`) and the instrumented harness at + `~/reference-oracles/TriCNES-rustynes-harness`. Rules 1 and 2 above still hold: nothing of it + goes back into the working tree. +- **Anything written after consulting it is a port, and is attributed as one:** a `// Provenance:` + header, a section 1 row in `docs/originality-and-provenance.md`, and `NOTICE`, in the same + change. MIT requires the copyright notice; this project requires the rest. +- **It is not a precedent.** It rests on TriCNES being permissive and written by the author of the + test suite. No GPL reference and no third-party HDL gains anything from it, and for the MiSTer + sibling the AGENTS.md per-region rung-4 declaration still applies. + --- ## 4. Attribution: four surfaces, always consistent diff --git a/docs/tooling/oracle-tooling-setup.md b/docs/tooling/oracle-tooling-setup.md index bae396e4..38d686cb 100644 --- a/docs/tooling/oracle-tooling-setup.md +++ b/docs/tooling/oracle-tooling-setup.md @@ -10,14 +10,14 @@ > allowed paths** (a sibling directory the tool sandbox does not expose). The removed-clone paths that > appear below are historical and no longer resolve. > -> **TriCNES is the deliberate exception, and it is not a firewall violation.** TriCNES is **MIT**, so -> its full upstream source is *intentionally vendored in-repo* at -> `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` (with its `LICENSE`, attributed in -> `NOTICE` + `docs/originality-and-provenance.md` §1) as a genuinely-incorporated permissive component — -> which is exactly what makes the cross-diff harness self-contained. The committed golden vectors under -> `crates/rustynes-test-harness/golden/` (plus the AccuracyCoin sub-test ROMs) remain the preferred -> path because they need no live emulator at all; the vendored MIT TriCNES source is the permissible -> in-repo fallback. Neither requires the copyleft references to be in reach. +> **TriCNES is the deliberate exception, and it is not a firewall violation.** TriCNES is **MIT** +> and written by the AccuracyCoin author; its ported models are attributed in `NOTICE` + +> `docs/originality-and-provenance.md` section 1. Its source was vendored in-repo until v3.0.1 and +> now lives **outside** the repository (maintainer decision, 2026-10-07): `~/reference-oracles/TriCNES` +> (upstream at `94f1b117`) and `~/reference-oracles/TriCNES-rustynes-harness`. It may be consulted +> for AccuracyCoin troubleshooting on the terms in `docs/ai-emulator-provenance-guardrails.md` +> section 3a. The committed golden vectors under `crates/rustynes-test-harness/golden/` (plus the +> AccuracyCoin sub-test ROMs) remain the preferred path because they need no live emulator at all. The v2.0 accuracy push (toward a full pass) cross-diffs RustyNES's per-cycle bus stream against two reference emulators. `/tmp` is wiped on reboot (CachyOS) — this is the recipe to regenerate. @@ -75,16 +75,16 @@ stood here. A grep target survives a re-vendor; a line number does not. ### 2a. In-repo buildable instrumented harness (the per-cycle cross-diff oracle) The cross-diff oracle used for the DMA-tail / Program-M work is a **trimmed, instrumented TriCNES -built from source**, vendored self-contained in this repo (TriCNES is MIT — Chris Siebert 2025): - -- `crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src/` — the buildable harness: - `Emulator.cs` (instrumented with the per-cycle window logger), `Program.cs`, `6502Documentation.cs`, - `mappers/` (all 10 `Mapper_*.cs` — **required to build**; salvaged 2026-06-08, the earlier salvage - had omitted them so the harness did not build), `tricnes-harness.csproj`. Build with - `dotnet build -c Release` (needs the .NET 10 SDK); rebuild into `/tmp/tricnes-harness` if you prefer - an out-of-tree build dir. -- `crates/rustynes-test-harness/golden/tricnes/tricnes-full-src/` — the **complete** upstream TriCNES - source (`.cs`/`.csproj`/`.resx` + `LICENSE`, no build artifacts; ~1 MB), for reference / re-trimming. +built from source** (TriCNES is MIT — Chris Siebert 2025). It was vendored in this repo until +v3.0.1 and now lives outside it: + +- `~/reference-oracles/TriCNES-rustynes-harness/` — the buildable harness: `Emulator.cs` + (instrumented with the per-cycle window logger; 88 lines against upstream), `Program.cs`, + `6502Documentation.cs`, `mappers/` (all 11 `Mapper_*.cs`, **required to build**), + `tricnes-harness.csproj`, `LICENSE`. Build with `dotnet build -c Release` (.NET 10 SDK). On a + machine without it, restore the tree from this repository's history before the v3.0.1 removal. +- `~/reference-oracles/TriCNES/` — a clone of `github.com/100thCoin/TriCNES` at `94f1b117`, for + reference and re-trimming. - Cross-diff driver: `scripts/tricnes_xdiff.py` (+ the ad-hoc helpers under `scripts/diag/`, salvaged session diagnostics — see `scripts/diag/README.md`). RustyNES side: `scan_dma_abort`, `trace_dma_4015`. - Individual AccuracyCoin sub-test ROMs (MIT, distinct builds) under @@ -96,9 +96,9 @@ built from source**, vendored self-contained in this repo (TriCNES is MIT — Ch > gitignored). The out-of-tree rule is for the **copyleft** references: if a **Mesen2** build (or > puNES / FCEUX / GeraNES) is genuinely needed to *regenerate* an oracle trace, keep it **out of tree, > outside the agent's allowed paths** — build and run it there, capture only its **output**, and diff. -> **TriCNES is MIT and is the deliberate exception:** its harness + full source are vendored in-repo -> (§2a) under their own permissive license, so a TriCNES trace can be regenerated from the in-tree -> `tricnes-harness-src` with no out-of-tree source at all. The committed golden vectors above make the +> **TriCNES is MIT and is the deliberate exception:** a TriCNES trace is regenerated from the +> harness at `~/reference-oracles/TriCNES-rustynes-harness` (§2a), out of the repository since +> v3.0.1. The committed golden vectors above make the > cross-diff oracle self-contained without *any* live emulator, which is the preferred path. ## 3. PPU sub-dot oracles (Phase 6) From 1740bf88cb64ab5197a0f38493e59a0f377a4831 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Tue, 6 Oct 2026 23:38:18 -0400 Subject: [PATCH 32/44] docs(changelog): the NTSC and TriCNES provenance changes in [Unreleased] Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60978e4c..066146ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,21 @@ 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. +- **Provenance: the shared Bisqwit NTSC pass recorded as derived + (T-NTSC-PROVENANCE).** `rustynes-gfx-shaders` called `BISQWIT_WGSL` "an + independent implementation ... no third-party emulator code is incorporated", + but it is a generated verbatim copy of `ntsc_bisqwit.rs`, whose tables have + long been recorded as ported from Bisqwit's C via Mesen2. It now carries a + `Provenance` header and its own row in `docs/originality-and-provenance.md`, + `NOTICE` names it, and the provenance audit lost the exception that had hidden + it (maintainer, 2026-10-07). +- **Provenance: the TriCNES source moved out of the repository.** The vendored + MIT TriCNES trees under `crates/rustynes-test-harness/golden/tricnes/` were + removed so no repository search reaches reference-emulator source; the + committed cross-diff outputs stay. TriCNES remains the one reference whose + source may be consulted, for AccuracyCoin work and always attributed + (`docs/ai-emulator-provenance-guardrails.md` section 3a; maintainer, + 2026-10-07). `NOTICE` also corrects the last vendored commit to `94f1b117`. - **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 From 8cd0781388264738308cf230d98b19b001cc7416 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Tue, 6 Oct 2026 23:45:00 -0400 Subject: [PATCH 33/44] docs(roadmap): plan v3.1.0 to v4.0.0, from the 28 decisions of 2026-10-07 There was no plan past v3.0.0 except to-dos/plans/v3.x-hardware-verification-plan.md, and the v2.9.4-to-v3.0.0 line plan's "After v3.0.0" list named work without releases. Three read-only surveys on 2026-10-06 covered: - the oracle backlog, about 110 items in 15 themes; - the MiSTer sibling's backlog, about 60 items; - the outside ecosystem: Rust, wgpu/egui, libretro, RetroAchievements, the app stores, UniFFI, AccuracyCoin, Cloudflare, MiSTer. The maintainer then took 28 decisions (D1-D28) on 2026-10-07. This commit turns all of it into a line plan, one plan per release, refreshed programme plans, and dated ADR amendments. The line (to-dos/plans/v3.1-to-v4.0-line-plan.md): - v3.1.0: the AccuracyCoin re-sync first. Upstream added two tests on 2026-10-05, so "144/144" now refers to an older catalogue. Then: - the CPU overclock and the sprite-limit toggle, both carried in movies and netplay with one format bump (D22); - PAL emphasis, opt-in composite artifacts, a NEC MMC3 override (D20); - rewind and run-ahead in Vs. dual mode (D27); - an epoch fingerprint gate, so ADR 0045's rule stops being enforced by hand. - HW: the hardware release. Its number is chosen after the board session, no later than v4.0.0 (D1), and it comes before any feature RTL. A Strand C correction reopens the seed sweep at that day's build date, so it must not share a release with feature RTL. - v3.2.0 to v3.8.0: an oracle theme beside each MiSTer phase F1-F4. Off-die becomes the headline at v3.3.0, with save states (D4). - v3.9.x: mobile signing and the store listings (D18); v4.0 preparation. - v4.0.0: the remaining public enums #[non_exhaustive] plus MiSTer parity (D3). The survey found the enum change to be the only item that must be a MAJOR. The SemVer rule changes (D2), and VERSION-PLAN.md is rewritten to match. Until now it said save-state breaks need a MAJOR, while practice did otherwise: v2.9.5 raised PPU_SNAPSHOT_VERSION in a minor, v2.9.8 refused every older state in a minor, v2.9.9 moved .rnm to format 4 in a minor, and v3.0.1 raised EMULATION_EPOCH in a patch. Format breaks are now allowed in any release with notes. MAJOR is a public Rust API break or a new deliverable class. The old sentence is quoted where it changed, not silently replaced. Other files changed: - to-dos/mister/IMPLEMENTATION_PLAN.md and SPRINT_PLAN.md: a current "where the core is" table and phases S/H/F1-F4/parity (sprints M21-M30). The old v2.5.1 to v3.0.0 content is kept below a History heading, not deleted. Its "where the core actually is" table had gone stale in exactly the way its own comment warned. - to-dos/libretro: marked historical (every TASKS.md box was ticked). The forward sprints L1-L5 are added: the 1.96 pin test (D5), Core Options v2, float audio, console targets (D28). - to-dos/plans/v3.x-hardware-verification-plan.md: D1, D11 (SuperStation One only; the two-board box split, not ticked on one board), D13 and D16, plus an explicit strand order. - ADRs, as dated amendments with nothing rewritten in place: - 0043 (D1-D3); - 0035 (D18: when the free listings happen; no monetisation, unchanged); - 0032 (D27); - 0037 (D13: the board wins only where it agrees with documentation; D15: rung 4 on a Provenance-headered family needs a per-family amendment naming the maintainer). No rung-4 authorisation is given. - to-dos/ROADMAP.md: a Status pointer, 27 minted tickets with their slots (existing T-MISTER-* and T-PS-dual-runahead reused, not re-minted), and the "None block Phase 1" placeholder replaced. - DEFERRED-AND-CARRYOVER-FEATURES.md, checked against the tree: - R1 marked closed. It shipped as T-MMC3-BG-A12 in v3.0.0; tests/mmc3.rs:119-120 and :201-202 are plain #[test]s. - The six header/size mismatches marked adjudicated. v2.9.6's UNSUPPORTED list refuses each by name (external_coverage.rs:141-182). - The open entries now point at their slots. Deliberately not edited here, because the main session owns them this cycle: the T-NTSC-PROVENANCE ticket (D6), the TriCNES tree and its guardrail wording (D7), NOTICE, docs/originality-and-provenance.md and AGENTS.md. Inferences are labelled as inferences in the plans, for example the wgpu 31 date, Play's API 37 deadline of about 2027-08-31, and the Xcode 27 floor around April 2027. Version numbers after v3.1.0 are slots, not promises. Verification: - markdownlint (pinned hook) passes on every changed file. The new to-dos/plans files sit under .markdownlintignore, so they were linted as copies outside the ignored tree. - release_anchor_audit 14/0, release_state_prose_audit 16/0, contribution_checklist_audit 9/0. - Every relative .md link in the changed files resolves. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- VERSION-PLAN.md | 39 +- ...0032-vs-dualsystem-desktop-presentation.md | 20 + ...-rustynes-is-permanently-non-commercial.md | 19 + ...pga-core-independent-hdl-implementation.md | 29 ++ ...-api-major-and-a-release-candidate-core.md | 27 + to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md | 100 +++- to-dos/ROADMAP.md | 55 ++- to-dos/libretro/IMPLEMENTATION_PLAN.md | 6 + to-dos/libretro/SPRINT_PLAN.md | 20 + to-dos/mister/IMPLEMENTATION_PLAN.md | 70 ++- to-dos/mister/SPRINT_PLAN.md | 38 ++ to-dos/plans/v3.1-to-v4.0-line-plan.md | 461 ++++++++++++++++++ to-dos/plans/v3.1.0-plan.md | 89 ++++ to-dos/plans/v3.2.0-plan.md | 45 ++ to-dos/plans/v3.3.0-plan.md | 44 ++ to-dos/plans/v3.4.0-plan.md | 37 ++ to-dos/plans/v3.5.0-plan.md | 38 ++ to-dos/plans/v3.6.0-plan.md | 31 ++ to-dos/plans/v3.7.0-plan.md | 33 ++ to-dos/plans/v3.8.0-plan.md | 34 ++ to-dos/plans/v3.9.0-plan.md | 36 ++ .../plans/v3.x-hardware-verification-plan.md | 63 ++- to-dos/plans/v4.0.0-plan.md | 50 ++ 23 files changed, 1367 insertions(+), 17 deletions(-) create mode 100644 to-dos/plans/v3.1-to-v4.0-line-plan.md create mode 100644 to-dos/plans/v3.1.0-plan.md create mode 100644 to-dos/plans/v3.2.0-plan.md create mode 100644 to-dos/plans/v3.3.0-plan.md create mode 100644 to-dos/plans/v3.4.0-plan.md create mode 100644 to-dos/plans/v3.5.0-plan.md create mode 100644 to-dos/plans/v3.6.0-plan.md create mode 100644 to-dos/plans/v3.7.0-plan.md create mode 100644 to-dos/plans/v3.8.0-plan.md create mode 100644 to-dos/plans/v3.9.0-plan.md create mode 100644 to-dos/plans/v4.0.0-plan.md diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index 34b20f89..9dcf9de1 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -19,9 +19,9 @@ v1.0.0 is the **production cut that integrates the cycle-accurate emulation engi MAJOR.MINOR.PATCH[-PRERELEASE] ``` -- **MAJOR** — incompatible public-API or save-state-format breaks (now at `2`, since **v2.0.0 "Timebase"** broke the `.rns` save-state / `.rnm` movie epochs per ADR 0028), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). **v3.0.0 is MAJOR by the first trigger** (ADR 0042's API and save-state breaks) and ships an unverified release-candidate core; the hardware-verified core is a later v3.x release ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). -- **MINOR** — backwards-compatible features (new mappers, new frontend features, new platforms). -- **PATCH** — backwards-compatible bug fixes and accuracy refinements. +- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below). +- **MINOR** — features (new mappers, new frontend features, new platforms). May carry a documented format break. +- **PATCH** — bug fixes and accuracy refinements. May carry a documented format break when the fix needs one (v3.0.1 raised `EMULATION_EPOCH`). - **PRERELEASE** — `-alpha.N` / `-beta.N` / `-rc.N` when stabilizing a future minor/major. ## Version history @@ -62,9 +62,26 @@ The cycle-accurate engine was integrated as the core in a sequence of documentar Scoped but unreleased. The `(current)` row in the table below stays on the released version; these are plans, and `to-dos/plans/` holds the detail. +The line from v3.1.0 to v4.0.0 is indexed in +[`v3.1-to-v4.0-line-plan.md`](to-dos/plans/v3.1-to-v4.0-line-plan.md), drafted +2026-10-07 with the maintainer's decisions D1-D28. Version numbers after v3.1.0 +are slots: the hardware release takes a number only after its board session +(D1), and the slots after it move up by one if it takes a minor. + | Version | Scope | Plan | |---------|-------|------| -| v3.x | **Hardware verification**: the SuperStation One board session (Strands A-F), the mobile device runs, and the fixes each produces | [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` | +| v3.0.1 "Mortar" | The open items (the palette-offset A/B, T-GA23C-CHRRAM, the MiSTer odd-frame A12 stimulus), every dependency and the Rust 1.99 toolchain, the libretro 1.96 pin test, every unanswered review comment back to #1 | [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md) | +| v3.1.0 | The AccuracyCoin re-sync; the CPU overclock and sprite-limit options in movies and netplay; PAL emphasis; opt-in composite artifacts; the NEC MMC3 option; rewind and run-ahead in Vs. dual mode; an epoch fingerprint gate. MiSTer: small RTL items, the self-hosted runner, submission documents | [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md) | +| HW (numbered after the session) | **Hardware verification**: the SuperStation One board session (Strands A-F), the mobile device run, and the fixes each produces. No feature RTL | [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` | +| v3.2.0 | Mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions. MiSTer F1: options and about ten cheap families, paddle, Four Score, cheats | [`v3.2.0-plan.md`](to-dos/plans/v3.2.0-plan.md) | +| v3.3.0 | Phi2 write placement and the sprite-0 stale shifter; wgpu 31 / egui 0.37. MiSTer F2: the SDRAM arbiter, DDR3, save states, rewind; the off-die build becomes the headline | [`v3.3.0-plan.md`](to-dos/plans/v3.3.0-plan.md) | +| v3.4.0 | Hosted netplay and the browser RA proxy on Cloudflare, RA hardcore compliance, native 3-4 player netplay. MiSTer F3a: MMC2/4, FME-7/5B, VRC2/4, the Zapper | [`v3.4.0-plan.md`](to-dos/plans/v3.4.0-plan.md) | +| v3.5.0 | The full movie input stream, Lua sockets and shared memory, desktop polish. MiSTer F3b: MMC5, N163, VRC6/7, Bandai FCG, expansion audio | [`v3.5.0-plan.md`](to-dos/plans/v3.5.0-plan.md) | +| v3.6.0 | Libretro Core Options v2, float audio, console targets; Android API 37. MiSTer F4a: PAL and Dendy | [`v3.6.0-plan.md`](to-dos/plans/v3.6.0-plan.md) | +| v3.7.0 | The mobile extras (iOS box art, widget and external display; EQ and cheat DB; zero-copy frames after UniFFI 0.33). MiSTer F4b: FDS | [`v3.7.0-plan.md`](to-dos/plans/v3.7.0-plan.md) | +| v3.8.0 | The remaining accuracy residuals, Vs. cabinets, the long-tail mappers. MiSTer F4c: NSF, Vs. System, band-limited audio | [`v3.8.0-plan.md`](to-dos/plans/v3.8.0-plan.md) | +| v3.9.x | Mobile signing and store listings; the enum-break trial; the audits re-run; the MiSTer RC pair | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md) | +| v4.0.0 | **The API major**: the remaining public enums `#[non_exhaustive]`; MiSTer feature parity | [`v4.0.0-plan.md`](to-dos/plans/v4.0.0-plan.md) | ### Post-1.0 release line (v1.1.0 → current) @@ -167,15 +184,17 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide ## Versioning guidelines -- **Bump MINOR** (the middle digit — e.g. `vMAJOR.MINOR.0`) for: new mapper families, new frontend features, new platforms (e.g. mobile), new input devices — anything backwards-compatible that adds capability. -- **Bump PATCH** (the last digit — e.g. `vMAJOR.MINOR.PATCH`) for: bug fixes, accuracy refinements, dependency bumps, and documentation that does not change behavior. -- **Bump MAJOR** (`vMAJOR.0.0`) for either of two things. (1) An incompatible public-API break or a save-state-format break that cannot migrate — exactly what **v2.0.0 "Timebase"** did (ADR 0028 bumped the `.rns`/`.rnm` epochs). (2) The first release of a **new deliverable class**, verified to the standard that class requires — the first hardware-verified FPGA core ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)), which [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) moved from v3.0.0 to a later v3.x. v3.0.0 itself is MAJOR under (1), for ADR 0042's API and save-state breaks. A new *host* for the same emulator (mobile, libretro) stays MINOR. +- **Bump MINOR** (the middle digit — e.g. `vMAJOR.MINOR.0`) for: new mapper families, new frontend features, new platforms (e.g. mobile), new input devices — anything that adds capability without breaking the public Rust API. It may carry a documented format break. +- **Bump PATCH** (the last digit — e.g. `vMAJOR.MINOR.PATCH`) for: bug fixes, accuracy refinements, dependency bumps, and documentation that does not change behavior. It may carry a documented format break when the fix needs one. +- **Bump MAJOR** (`vMAJOR.0.0`) for either of two things. (1) An incompatible break of the **public Rust API** — the surface defined at the end of this section. (2) The first release of a **new deliverable class**, verified to the standard that class requires — the first hardware-verified FPGA core ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)), which [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) moved from v3.0.0 to a later v3.x, numbered after the board session and no later than v4.0.0. v2.0.0 "Timebase" and v3.0.0 "Cornerstone" were MAJOR under the rule in force at the time, which counted save-state breaks too. A new *host* for the same emulator (mobile, libretro) stays MINOR. **v4.0.0** is planned as MAJOR under (1): the remaining public enums become `#[non_exhaustive]` ([`v4.0.0-plan.md`](to-dos/plans/v4.0.0-plan.md)). ### Breaking-change policy -- Public-API and save-state-format breaks are MAJOR bumps and must be documented in `CHANGELOG.md` with a migration note. A MAJOR bump for a new deliverable class carries no break by itself; any break it also carries still needs its migration note. -- Save-state cross-version compatibility is best-effort (tagged per-chip sections with a version byte); the on-disk `.rnm` movie format and the public `rustynes-core` API are the stable surfaces. -- **What "public API" means here** (written down at v2.9.0's review, which found it unwritten). The SemVer surface is the public API of `rustynes-core`, including the chip-crate types it re-exports, together with the `.rns` and `.rnm` formats. Every other crate's public items are internal to this repository and may change in any release: `rustynes-frontend`, `rustynes-hdpack`, `rustynes-script`, `rustynes-ra`, `rustynes-mobile`, `rustynes-libretro` and the rest. That holds because no crate here is published to crates.io, so nothing outside this workspace resolves against them. Their consumers are this repository's own binaries and apps. What the libretro core exposes is the libretro C ABI, which is libretro's contract, not ours. Such changes are still recorded in `CHANGELOG.md` when a user could notice them. This states the existing practice: v2.3.3, a PATCH, changed a frontend `pub fn` signature and deleted a public frontend field, and v2.9.0 changed `HdAudioTrack::pcm` and `ScriptHost::submit`. +- **Changed 2026-10-07 (maintainer decision D2, [`v3.1-to-v4.0-line-plan.md`](to-dos/plans/v3.1-to-v4.0-line-plan.md)).** Until then this said public-API **and save-state-format** breaks were MAJOR bumps. Practice had already left it: v2.9.5 raised `PPU_SNAPSHOT_VERSION` in a minor, v2.9.8 refused every older state and movie in a minor, v2.9.9 moved `.rnm` to format 4 in a minor, and v3.0.1 raised `EMULATION_EPOCH` in a patch. The rule now matches the practice, by the maintainer's standing preference for the enduring design over compatibility. +- **Format breaks are allowed in any release** — save states (`.rns` sections), `.rnm` movies, the netplay protocol and `EMULATION_EPOCH` — provided the CHANGELOG entry and the release notes say what breaks and what the user sees (a clear refusal with a reason, never a silent misread; ADR 0028, ADR 0045). A MAJOR's release notes restate every break since the previous MAJOR. +- **Public Rust API breaks are MAJOR bumps** and need a migration note in `CHANGELOG.md`. A MAJOR bump for a new deliverable class carries no break by itself; any break it also carries still needs its migration note. +- Save-state cross-version compatibility is best-effort (tagged per-chip sections with a version byte); the public `rustynes-core` API is the stable surface. +- **What "public API" means here** (written down at v2.9.0's review, which found it unwritten). The SemVer surface is the public API of `rustynes-core`, including the chip-crate types it re-exports. The `.rns` and `.rnm` formats are versioned surfaces whose breaks are documented rather than MAJOR triggers (since 2026-10-07; they were part of the SemVer surface before). Every other crate's public items are internal to this repository and may change in any release: `rustynes-frontend`, `rustynes-hdpack`, `rustynes-script`, `rustynes-ra`, `rustynes-mobile`, `rustynes-libretro` and the rest. That holds because no crate here is published to crates.io, so nothing outside this workspace resolves against them. Their consumers are this repository's own binaries and apps. What the libretro core exposes is the libretro C ABI, which is libretro's contract, not ours. Such changes are still recorded in `CHANGELOG.md` when a user could notice them. This states the existing practice: v2.3.3, a PATCH, changed a frontend `pub fn` signature and deleted a public frontend field, and v2.9.0 changed `HdAudioTrack::pcm` and `ScriptHost::submit`. ## Accuracy milestones (met) diff --git a/docs/adr/0032-vs-dualsystem-desktop-presentation.md b/docs/adr/0032-vs-dualsystem-desktop-presentation.md index 6a0c1912..dbea0d5f 100644 --- a/docs/adr/0032-vs-dualsystem-desktop-presentation.md +++ b/docs/adr/0032-vs-dualsystem-desktop-presentation.md @@ -58,3 +58,23 @@ misread. The slot grid shows a cabinet slot without a thumbnail, because Run-ahead, rewind, netplay and TAS stay out of dual mode (`T-PS-dual-runahead`, `T-PS-dual-netplay`), and so do the debugger and HD packs. The overclock is not applied to a cabinet either (v2.9.7). + +## Amendment (2026-10-07): rewind and run-ahead in dual mode + +The maintainer decided to lift two of Decision 4's exclusions: **rewind and +run-ahead** (decision D27 in +[`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md), +scheduled for v3.1.0 as `T-PS-dual-runahead`). + +The 2026-09-30 amendment supplies the means. The "RVSD" container already +snapshots both consoles and the latch that wires them, and restores them +atomically. Rewind and run-ahead are built on that container rather than on a +single `Nes`. + +The gate: + +- a rewind across a cabinet frame restores both framebuffers byte-identically; +- run-ahead gives the same output as a run without it. + +Netplay and TAS (`T-PS-dual-netplay`), the debugger and HD packs stay out of +dual mode. diff --git a/docs/adr/0035-rustynes-is-permanently-non-commercial.md b/docs/adr/0035-rustynes-is-permanently-non-commercial.md index 72af2e35..3c8262ed 100644 --- a/docs/adr/0035-rustynes-is-permanently-non-commercial.md +++ b/docs/adr/0035-rustynes-is-permanently-non-commercial.md @@ -68,3 +68,22 @@ Concretely, in v2.2.6 "Almanac": reversing this ADR — which is the intended bar. - **Follow-up:** `docs/originality-and-provenance.md` and `NOTICE` disclose the TriCNES behavioral-calibration caveat (see ADR 0030) as part of the same honesty pass. + +## Amendment (2026-10-07): when the free store listings happen + +The Decision allowed a free store listing as an unversioned later step, and the +v2.9.4-to-v3.0.0 line plan placed it "after the v3.x hardware release". The +maintainer has now placed it (decision D18 in +[`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md)): +**mobile signing and the store listings come at about v3.9, right before +v4.0.0's final development, test and release activities.** + +That covers: + +- Android developer verification, which also applies to sideloaded apps and is + global from 2027; +- iOS signing, so that TestFlight uploads run; +- the Google Play, F-Droid or IzzyOnDroid, and App Store listings. + +Nothing in the Decision changes. Every listing is free, with no ads, no +tracking and no paid unlock, and the `foss`/`play` flavour split stays. diff --git a/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md b/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md index d6688df4..87ad30aa 100644 --- a/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md +++ b/docs/adr/0037-mister-fpga-core-independent-hdl-implementation.md @@ -135,3 +135,32 @@ GPL-2.0-**only**, the combined bitstream is undistributable and the RTL must be GPL-2.0-or-later instead. Tabulating every licence header in `sys/` is an hour of work and must happen **before any RTL is written** -- relicensing after 10k lines exist is precisely the failure `docs/originality-and-provenance.md` documents. + +## Amendment (2026-10-07): when the board and the oracle disagree, and the Provenance-headered families + +Two maintainer decisions taken while the v3.1 to v4.0 line was planned +([`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md), +D13 and D15). Both extend "Accepted risk: the oracle can be wrong" now that a +board will run the core. + +**D13: the board against the oracle.** + +- When the SuperStation One contradicts the oracle (palette, video timing, + OAM corruption, APU power-on phase, or anything else), the board wins **only + where it agrees with documented hardware behaviour**. A board result with no + documentary support is recorded, not adopted. +- When it wins, the RTL and the oracle are fixed **in the same release**, red + first, with an `EMULATION_EPOCH` rise (ADR 0045). +- Each case gets its own dated amendment to this ADR, naming the measurement. + +**D15: families whose oracle source carries a `// Provenance:` header.** + +- These are N163, FME-7/5B, VRC7 with its OPLL, Bandai FCG, FDS, Vs. System, + and any other family the command finds: `grep -rln "^// Provenance:" crates`. +- Their RTL is written from rungs 1-3 of the escalation ladder: documentation, + the Internet, and black-box comparison of outputs. +- **Rung 4, reading the derived oracle region, needs a dated amendment to this + ADR naming the maintainer as the authoriser, one per family, before anything + is read.** The derivation is then declared in the sibling: a site comment, + its provenance document, and `NOTICE`. +- No rung-4 authorisation is given by this amendment. diff --git a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md index 22589fce..9736a31e 100644 --- a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md +++ b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md @@ -114,3 +114,30 @@ release notes restate every break since v2.x. Decision 1 (an API major plus a release-candidate core, both bitstreams labelled not hardware-verified) and Decision 4 (only the libretro sync goes upstream) are unchanged. + +## Amendment (2026-10-07, after v3.0.0): the hardware release's number, and v4.0.0 + +The maintainer settled the question Decision 2 left open, and defined the next +MAJOR, while the line after v3.0.0 was planned +([`v3.1-to-v4.0-line-plan.md`](../../to-dos/plans/v3.1-to-v4.0-line-plan.md), +decisions D1-D3). + +- **D1: the hardware-verification release is numbered after the board session, + and no later than v4.0.0.** It is planned as the slot "HW", after v3.1.0 and + before any feature RTL. The number is chosen with the session's evidence in + hand: the next free minor if the fixes are small, or as the maintainer then + decides if the session finds an L-sized re-target (the FPGA device question, + HW-A8). ADR 0041's "new deliverable class" trigger stays available to it. +- **D2: a format break alone no longer makes a MAJOR.** Save-state, movie, + netplay-protocol and `EMULATION_EPOCH` breaks may land in any release with + notes; MAJOR is a public Rust API break or a new deliverable class. + `VERSION-PLAN.md` was rewritten to match. This states the practice of v2.9.5, + v2.9.8, v2.9.9 and v3.0.1. +- **D3: v4.0.0 is the remaining public enums made `#[non_exhaustive]`, plus + MiSTer feature parity.** The enum change is the half of v3.0.0's + `T-API-EXTENSIBLE` that v3.0.0 did not take. Parity covers save states, + cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and + mapper families covering the incumbent core's licensed-library list. + +Decisions 1, 3 and 4 above stand as history. This amendment changes no shipped +artefact. diff --git a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md index cac15697..c677f86b 100644 --- a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md +++ b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md @@ -19,6 +19,19 @@ > **and cited**, and absence means it was never catalogued rather than that it was > finished. > +> **Re-targeted 2026-10-07 for the v3.1 → v4.0 line.** +> [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) now +> schedules what was still open, under maintainer decisions D1-D28. Each entry +> that changed carries a dated `(2026-10-07 ...)` annotation naming the release +> slot and the ticket. Two entries were closed against the tree, not the prose: +> +> - R1 (§6b) shipped as `T-MMC3-BG-A12` in v3.0.0; +> - §7's six header/size mismatches were adjudicated, as refusals, in v2.9.6. +> +> The rest of the 2026-10-06 backlog survey's stale-entry list (the ROADMAP, +> `docs/compatibility.md`, `docs/mappers.md` and others) is v3.1.0's records +> item. +> > **Reconciled against `main` at v2.9.3 (2026-09-29) for v2.9.4.** Every open > box and `[~]` below was re-checked against the tree, not against the prose; > each change carries a dated `(2026-09-29 ...)` annotation naming the file and @@ -147,6 +160,12 @@ > apps still ship as GitHub sideload + TestFlight; app stores and F-Droid are now > targeted **after the v3.x hardware release** per the > [line plan](plans/v2.9.4-to-v3.0.0-line-plan.md) "After v3.0.0 — External".)* +> +> *(2026-10-07: placed by the maintainer (D18, and ADR 0035's amendment of that +> date). Mobile signing and the free store listings come at about **v3.9.x**, +> right before v4.0.0's final activities. That covers Android developer +> verification, which is global from 2027 and covers sideloads too, and iOS +> signing so that TestFlight uploads run.)* --- @@ -286,6 +305,11 @@ remains are the optional/SQLite tails and any beta.5 polish. unverifiable in CI. Source: `docs/netplay-webrtc.md` §4. Target: **maintainer-manual**. *(2026-09-29: still open; target **after v3.0.0** with the hosted signalling, as above.)* + *(2026-10-07: both `[M]` entries above are scheduled in **v3.4.0** as + `T-HOSTED-NETPLAY`. The hosting is Cloudflare (maintainer decision D19): a + Worker with Durable Objects for the lobby, and Cloudflare TURN with + short-lived credentials. Native 3-4 player netplay over `mesh_net.rs` + (`T-NETPLAY-MESH-NATIVE`) lands in the same release.)* - `[x]` **Spectator netplay (H8)** — *(shipped; v1.8.9 reconcile: complete in `crates/rustynes-netplay/src/spectator.rs` — `SpectatorSession`, receive-only, determinism-safe, `MAX_SPECTATOR_FRAME_LOOKAHEAD` DoS bound, unit-tested; @@ -340,6 +364,15 @@ the v1.7.0 **H1/H2** workstream + a maintainer-manual deploy/verify. code change. Source: [ROADMAP](ROADMAP.md) "Beyond v2.0.0". Target: **maintainer-manual**. *(2026-09-29: still open; the line plan lists "RetroAchievements allowlisting" under **after v3.0.0 — External**.)* + *(2026-10-07: the three entries above are scheduled in **v3.4.0**: the proxy + on Cloudflare as `T-RA-PROXY` (D19), and RA hardcore compliance as + `T-RA-HARDCORE` (D25). + - RA's current requirement page asks for a privacy policy, offline unlock + queueing, and hardcore blocks on Lua and TAS playback as well as the usual + ones. + - It also specifies a User-Agent format that `RustyNES/ rcheevos/` + may not match. + - `docs/ra-integration-request.md` still says v1.8.8 and MIT/Apache.)* --- @@ -439,7 +472,13 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and ### 6b. The residuals it unlocks (R1–R5) -- `[ ]` **R1 — `mmc3_test_2/4` #3 (1-CPU-cycle "IRQ sooner" bracket)** — the +- `[x]` **R1 — `mmc3_test_2/4` #3 (1-CPU-cycle "IRQ sooner" bracket)** — + *(CLOSED v3.0.0 "Cornerstone" as `T-MMC3-BG-A12`, `to-dos/ROADMAP.md`: both + 4-scanline ROMs pass all 13 sub-tests. + `crates/rustynes-test-harness/tests/mmc3.rs:119-120` + `mmc3_test_2_4_scanline_timing_strict` and `:201-202` + `mmc3_test_v1_4_scanline_timing_strict` are plain `#[test]`s, with no + `#[ignore]`. Verified 2026-10-07. The annotations below are history.)* the CPU `T_last-1` IRQ-sample M2 sub-cycle phase; the integer 3-dots-per-cycle timebase **cannot represent** it. **The 17-rollback graveyard / hard target with a bounded-effort escape hatch** (fall back to by-design `#[ignore]` rather than @@ -516,6 +555,10 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and `docs/nesdev-hardware-emulation-checklist.md` "residual stale-shifter cases tracked". Falls in the **v2.9.5** accuracy release by theme, but is not a named item there; the attempt-only-if-a-game-demands-it condition stands.)* + *(2026-10-07: the maintainer lifted that condition (D20): the item is to be + attempted without a motivating game, in **v3.3.0** as `T-SPRITE0-STALE`. It is + kept only if AccuracyCoin stays all-pass; otherwise it is recorded as refuted. + [Line plan](plans/v3.1-to-v4.0-line-plan.md).)* ### 6c. Other v2.0-axis items @@ -538,6 +581,11 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and `Nes::set_extra_scanlines`, held at 0 under a movie or netplay; see `docs/frontend.md`. The CPU-multiplier overclock and the sprite-limit toggle stay open.)* + *(2026-10-07: both are scheduled for **v3.1.0** (maintainer decision D22): + `T-CPU-OVERCLOCK` and `T-SPRITE-LIMIT`. Both go in `HardwareOptions`, so + movies and netplay carry them, with one `.rnm` format and protocol bump. The + sprite limit is render-only, so the overflow flag and evaluation timing stay + exact.)* - `[x]` **Full Vs. DualSystem dual-core (C)** — *(shipped v2.0.0 "Timebase" beta.5, commit `9fe44a19`: `crates/rustynes-core/src/vs_dualsystem.rs`, `pub enum Emu` (Single / Dual); desktop presentation v2.1.2 (`render_dual` in @@ -582,7 +630,10 @@ take this on. See [v2.0.0 plan](plans/v2.0.0-master-clock-plan.md) and ### 6d. By-design non-targets (recorded for completeness — do NOT implement) - `mmc3_test_2/6` (NEC rev B) — RustyNES defaults to Sharp rev A; mutually - exclusive (R6). + exclusive (R6). *(2026-10-07: the default stays rev A. An opt-in per-game or + config override that runs the ROM under NEC is scheduled for **v3.1.0** as + `T-MMC3-NEC-OVERRIDE` (D20). The NEC variant has been selectable by NES 2.0 + submapper since v2.9.6.)* - `cpu_reset` full-protocol ×2 (interactive) — needs an externally-timed reset the headless handler can't supply (R7). @@ -612,6 +663,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac continuation stays partial; the next batch — 37/47, 12, 44/45/49, 74/191/192/194/195, 105, 115/121, 163, 228, 83/91/153 over the existing MMC3 and FCG/MMC1 cores — is **v2.9.6** in the line plan.)* + *(2026-10-07: v2.9.6 shipped 17 families (174 → 191). The next batch is + ranked by real titles (maintainer decision D26): 8 (FFE), 158, 207, 116, + 114/182/215, 197, 165, 172/173, 230, 235, 252, in **v3.2.0** + (`T-MAPPER-BREADTH-V3`). The long tail continues in **v3.8.0**. Each family is + first checked against the maintainer's library in `~/Dropbox/ROMs/` (D21). + [Line plan](plans/v3.1-to-v4.0-line-plan.md).)* - `[~]` **Zero-library mappers (no freely-available ROM)** — families 28, 29, 31, 39, 81, 174, 179 have no freely-available dump, so they have no committed screenshots (register-decode unit-tested only). Source: the standing @@ -624,6 +681,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac `screenshots/besteffort/mapper-031-INL-NSF/`. Still none for **29, 39, 81, 174, 179** (`tests/roms/external/mapper-029-RET-CUFROM/` exists but is empty). Target: **v2.9.6** tier promotions, where a legal homebrew ROM exists.)* + *(2026-10-07: v3.2.0's dump-corpus sweep (`T-DUMP-CORPUS`, D21) searches + `~/Dropbox/ROMs/` for 29, 39, 81, 104, 174, 179, 238, 261, 194, 195 and the 16 + high-id boards. A local dump now counts as evidence for promotion when the + committed record names the title, the hash, the frames checked and a + screenshot (D23, `T-CURATED-EVIDENCE`). The dumps themselves are never + committed.)* - `[x]` **`m176` Waixing FS005 detection follow-up** — three `.WXN` Chinese dumps are misdetected as m30 (UNROM-512). Not an m30 bug. Source: the blank-boot-fixes memory note. Files: `crates/rustynes-mappers`, frontend `game_db`. @@ -803,6 +866,14 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac *(2026-09-29: still open. `Chu Liu Xiang` still renders no tiles (`docs/mappers.md` "remains open"), and the six header/size mismatches are still unadjudicated. Target: **v2.9.6**, the mapper release.)* + *(2026-10-07: the header/size mismatches **are** adjudicated, by v2.9.6: + rejected. `UNSUPPORTED` in + `crates/rustynes-test-harness/tests/external_coverage.rs:141-182` refuses + each by name with a typed reason: three CPROM, m58 *Study and Game*, two m146 + *Lucky 777*, and a non-iNES Vs. hack. *Chu Liu Xiang* is still blank and sits + in the KNOWN_BLANK list. It is scheduled for **v3.2.0** as `T-KNOWN-BLANK`, + with a CPU trace first and the mirroring-register power-on hypothesis to + test.)* - `[x]` **`m301` / `m348` UNIF board-map entries** — *(done; verified against the tree 2026-08-14, v2.3.4 Workstream D: `unif.rs:246` maps board `"8157"` -> 301 and `unif.rs:270` maps `"830118C"` -> 348, both covered by the board-map tests @@ -828,6 +899,12 @@ are ROM-availability/coverage and a detection follow-up — none affect the orac [v1.0.0 synthesis](plans/v1.0.0-synthesis-plan.md); [ROADMAP](ROADMAP.md). Target: **long-tail / no fixed version**. *(2026-09-29: unchanged — 174 families; **v2.9.6** adds the next batch, the rest stays long-tail.)* + *(2026-10-07: 191 families at v3.0.0. Breadth now follows real titles (D26): + **v3.2.0**, then **v3.8.0** for the long tail. "100% TASVideos" stays a + direction, not a gate. The 2026-10-06 ecosystem survey found no fixed + TASVideos NES compatibility set to measure against: its NESAccuracyTests page + is a results table, and which emulators it currently accepts was not + verified.)* --- @@ -869,6 +946,17 @@ bridge, Vs. DualSystem on mobile and the SOCD switch (**v2.9.7**), the device run and the iOS first compile (**after v3.0.0**, line plan "Mobile"), and the app stores (**after the v3.x hardware release**).)* +*(2026-10-07: FDS/NSF loading, Vs. DualSystem and the SOCD switch shipped on +the bridge at v2.9.7; their device rows are T1-T12 of the run sheet. In the +[v3.1 → v4.0 line plan](plans/v3.1-to-v4.0-line-plan.md): + +- the device run belongs to the hardware release (ADR 0043 Decision 2); +- the mobile extras are **v3.7.0** (D24): iOS box art, widget and external + display, and the EQ and cheat database on both apps; +- the zero-copy framebuffer (MOB-06) follows UniFFI 0.33; +- Android API 37 is **v3.6.0**; +- signing and the store listings are **v3.9.x** (D18).)* + - `[x]` **v1.8.0 "Android" MVP** — *(shipped v1.8.0, commit `e83f8d35`: `crates/rustynes-mobile` (UniFFI bridge) + `crates/rustynes-android` (JNI/NDK host) + the Compose app under `android/`; ADR 0024.)* a hybrid + focused-MVP Android frontend. @@ -907,7 +995,9 @@ app stores (**after the v3.x hardware release**).)* (CPU registers, disassembly, RAM hex, step-frame), v1.9.9 "Workshop", commit `70a971bf`. Android has no debugger surface; the egui spike in `crates/rustynes-android/src/lib.rs` was replaced. Remaining: an Android - surface, if wanted. Target: **unscheduled**.)* kept only as an optional sideload + surface, if wanted. Target: **unscheduled**.)* *(2026-10-07: wanted (D24). The + Android debugger is scheduled with the zero-copy bridge (MOB-06), in **v3.7.0** + or the first release after UniFFI 0.33 releases `&mut [u8]`.)* kept only as an optional sideload power-user overlay (Android) / a future hybrid embed (iOS), not a first-class mobile surface. Source: [v1.8.0](plans/v1.8.0-android-plan.md), [v1.9.0](plans/v1.9.0-ios-plan.md). Target: **v1.8.x / v1.9.x**. @@ -1025,6 +1115,10 @@ broken-boot fix re-blesses its own snapshots, which next applies in **v2.9.6**.) `gh api repos/doublegate/RustyNES/rulesets` on 2026-09-29). So the remaining work is one repository setting, still a maintainer decision. Target: **unscheduled**.)* + *(2026-10-07: offered to the maintainer with the v3.1 → v4.0 planning + decisions, and not chosen (D25 took the AccuracyCoin re-sync, the v1.8.x + checklist and RA hardcore approval, and left the merge queue out). It stays + open and unscheduled; turning it on is still one repository setting.)* - `[x]` **`cargo-nextest` adoption** — *(REJECTED, WONT-FIX in v1.8.9-beta.1, commit `a4b494cd`. The NOTE on the `test` job in `.github/workflows/ci.yml` records why: nextest runs `cargo metadata --all-features` internally, which diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md index 52af5ab7..29a6a532 100644 --- a/to-dos/ROADMAP.md +++ b/to-dos/ROADMAP.md @@ -58,6 +58,15 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**. ## Status - **Current release:** **RustyNES v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +- **The line after v3.0.0 (drafted 2026-10-07):** [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) indexes v3.1.0 → v4.0.0, with one plan file per release and the maintainer's 28 decisions (D1-D28) tabled there. In order: + - v3.0.1 "Mortar" (in progress); + - v3.1.0: options, emphasis, the AccuracyCoin re-sync; + - the hardware release, numbered after its board session and no later than v4.0.0, with no feature RTL; + - v3.2.0 to v3.8.0: oracle themes alongside MiSTer phases F1-F4; + - v3.9.x: mobile signing and store listings, and v4.0 preparation; + - **v4.0.0**: the remaining public enums `#[non_exhaustive]`, plus MiSTer feature parity. + + Format breaks no longer need a MAJOR (D2; `VERSION-PLAN.md`). The tickets the line minted are under "Tickets for the v3.1 → v4.0 line" below. - **Re-plan (maintainer, 2026-09-29):** the mobile device runs (`docs/mobile-v2.9.3-run-sheet.md`) and the SuperStation One board session, with the fixes each produces, move after v3.0.0 to a v3.x hardware-verification release. [ADR 0043](../docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes **v3.0.0 the API major with a release-candidate core** (not hardware-verified), superseding ADR 0041's definition. The line from v2.9.4 is [`plans/v2.9.4-to-v3.0.0-line-plan.md`](plans/v2.9.4-to-v3.0.0-line-plan.md): records and CI, accuracy, mappers, targeted platform features, performance and the SDRAM arbiter, the release candidate, then v3.0.0. - **Programme after v2.6.23 — the line to v3.0.0, the SuperStation One core** ([ADR 0041](../docs/adr/0041-hardware-release-is-v3.0.0.md), 2026-09-22). *(Superseded on 2026-09-29 by ADR 0043 and the re-plan bullet above: v3.0.0 is now the API major with a release-candidate core, and the board session and the hardware-verified core move to a later v3.x. What follows is the plan as it stood; the v2.7.x–v2.9.3 releases it describes have shipped.)* The first hardware-verified FPGA core is a new deliverable class, so it ships as a MAJOR version, and the board session moves from v2.7.0 to **v2.9.2** so that it measures the bitstream the audit lines produce rather than one they are about to replace. Three minor lines come first, each acting on AI-written audits in [`docs/audits/`](../docs/audits/README.md), every finding triaged against the code, pinned by a test that fails first, and gated: **v2.7.x** the core and frontend audits ([plan](plans/v2.7.x-core-frontend-audit-plan.md)); **v2.8.x** the libretro and RTL audits, and the off-die SDRAM build ([plan](plans/v2.8.x-libretro-rtl-audit-plan.md)); **v2.9.x** the re-audit, final seed sweeps, the release-candidate `.rbf` pair and the bring-up on the board ([plan](plans/v2.9.x-final-audit-and-hardware-plan.md)); **v3.0.0** the on-die `.rbf` as the headline, with the off-die `.rbf` as a labelled secondary ([plan](plans/v3.x-hardware-verification-plan.md), renamed 2026-10-05 from `v3.0.0-superstation-core-plan.md`). Features the older plans deferred "to v2.8+" are now deferred **past v3.0.0**, so each version number means one thing. - **Shipped, inside v2.4.1 — v2.4.0 "Concordance".** It merged to `main` and was never tagged, because the workspace version never sat at 2.4.0 on any commit; v2.4.1 carries it. There is deliberately no `v2.4.0` tag. Its scope was: A concordance is an index of where every term actually occurs, and the release is scoped as one: reconcile what the project says about itself with what is true outside it. Four items, each traceable to a recorded deferral rather than newly invented — **(A)** the **owed upstream libretro sync** (`libretro-super` + `libretro/docs`), the one carried obligation with an outside deadline; **(B)** a core-side **timeline generation counter** replacing the last-seen-`cycle()` heuristic for stale telemetry (it covers a restore to a *later* state, which the heuristic cannot), deliberately **not** serialized, so it must land with its consumers and be AccuracyCoin-**verified**; **(C)** a **shared atomic-write helper**, lifting v2.3.9's seven properties out of `config.rs` and giving the Windows tail a real implementation rather than a portable spine; and **(D)** `skip_serializing_if` on `hd_packs` / `shader_presets`, which carry the same false byte-identity claim v2.3.9 corrected in prose only. Explicitly out of scope, and recorded as decisions rather than oversights: the remaining RAM Atlas exports (a cheat is a **write**, so it needs a locked-session predicate the watch export correctly does without), RAM Atlas per-game persistence (a restored verdict without its evidence is a claim that cannot be checked — this panel's whole argument in reverse), APU workstreams **D2 and D4** (unmeasured on purpose; their prior is a null, not an unknown), a CHANGELOG gate (**measured and rejected** — 62% false positives against the project's own history), and any store launch. See [`plans/v2.4.0-concordance-plan.md`](plans/v2.4.0-concordance-plan.md). @@ -1420,6 +1429,50 @@ and fixed from the mapper's NESdev page, or recorded in `docs/accuracy-ledger.md` with the cause if it proves to be the game or the dump. +## Tickets for the v3.1 → v4.0 line (minted 2026-10-07) + +Minted from the line plan, +[`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md). Each row +names the release slot that owns it and the backlog ID from the 2026-10-06 +surveys. The release plans hold the gates. An existing ticket is reused, not +re-minted: `T-PS-dual-runahead` (ADR 0032), `T-MISTER-SAVESTATE`, +`T-MISTER-CHEATS`, `T-MISTER-ZAPPER`, `T-MISTER-4PLAYER`, `T-MISTER-PADDLE`, +`T-MISTER-KEYBOARD`, `T-MISTER-VMODE`, `T-MISTER-OSD` and +`T-MISTER-DIRECTVIDEO` keep their sections above. + +| Ticket | What | Backlog | Slot | +| --- | --- | --- | --- | +| `T-ECOSYSTEM-WATCH` | Every minor: the wgpu/egui pair, the AccuracyCoin upstream diff, rcheevos, the libretro build image, the Android/iOS policy calendar, the Rust pin | ecosystem survey | every minor | +| `T-LIBRETRO-TOOLCHAIN` | Drop the libretro build's Rust 1.96 pin if a branch pipeline on 1.99 passes all 15 jobs (D5) | LR-02 | v3.0.1, else v3.1.0 | +| `T-ACCURACYCOIN-RESYNC-2610` | Re-sync AccuracyCoin to upstream HEAD (two new tests and a "Misaligned OAM Behavior" fix since 2026-09-19) and triage red first (D25) | ecosystem 7 | v3.1.0 | +| `T-EPOCH-FINGERPRINT` | A committed panel of output hashes that fails CI when it moves without an `EMULATION_EPOCH` rise | CI-02 | v3.1.0 | +| `T-CPU-OVERCLOCK` | The CPU-multiplier overclock, in `HardwareOptions`, movies and netplay (D22) | FE-01 | v3.1.0 | +| `T-SPRITE-LIMIT` | "Disable sprite limit", render-only, carried like the overclock (D22) | FE-02 | v3.1.0 | +| `T-PAL-EMPHASIS` | The PAL/Dendy emphasis red/green swap | ACC-01 | v3.1.0 | +| `T-COMPOSITE-ARTIFACTS` | Differential phase distortion and inter-pixel artifacts, an opt-in video option (D20) | ACC-02 | v3.1.0 | +| `T-MMC3-NEC-OVERRIDE` | The NEC rev B MMC3 as a per-game or config override (D20) | ACC-13 | v3.1.0 | +| `T-MAPPER-BREADTH-V3` | The missing families with real titles, both cores (D26) | MAP-03, MAP-05, FB-1 | v3.2.0, v3.8.0 | +| `T-KNOWN-BLANK` | Triage the 52 KNOWN_BLANK dumps | ACC-08, ACC-09 | v3.2.0 | +| `T-CURATED-EVIDENCE` | BestEffort → Curated on local-dump evidence under D23's record | MAP-01 | v3.2.0 | +| `T-DUMP-CORPUS` | Every implemented family with a dump in `~/Dropbox/ROMs/` gets a recorded boot; nothing committed but snapshots (D21) | MAP-02 | v3.2.0 | +| `T-PHI2-WRITE` | Reopen PPU register write placement against the v2.6.17 conditions (D20) | ACC-03 | v3.3.0 | +| `T-SPRITE0-STALE` | The sprite-0 stale shifter and the internal/external bus split (D20) | ACC-04 | v3.3.0 | +| `T-HOSTED-NETPLAY` | A Cloudflare Worker lobby (Durable Objects) and Cloudflare TURN (D19) | NET-04, NET-02, NET-03 | v3.4.0 | +| `T-RA-PROXY` | The browser RA proxy, on the same account (D19) | RA-01, RA-02 | v3.4.0 | +| `T-RA-HARDCORE` | RetroAchievements hardcore compliance, then the application (D25) | RA-03 | v3.4.0 | +| `T-NETPLAY-MESH-NATIVE` | Native 3-4 player netplay over `mesh_net.rs`, then mobile | NET-01, MOB-09 | v3.4.0 | +| `T-MOVIE-INPUT-STREAM` | The Zapper, microphone, Vs. coins and FDS events in `.rnm` | CR-03 | v3.5.0 | +| `T-LIBRETRO-OPTIONS-V2` | Core Options v2 and float-audio negotiation, behind capability checks | ecosystem 3 | v3.6.0 | +| `T-LIBRETRO-CONSOLES` | Switch, PS Vita and 3DS investigated for the libretro core (D28) | LR-04 | v3.6.0 | +| `T-ANDROID-API37` | `ACCESS_LOCAL_NETWORK` for LAN netplay, lifecycle-safe background audio, `targetSdk` 37 | ecosystem 5 | v3.6.0 | +| `T-MOBILE-EXTRAS` | iOS box art, widget and external display; EQ and cheat DB on both apps; the live Kotlin TODOs (D24) | MOB-07, MOB-08 | v3.7.0 | +| `T-MOB-06-ZEROCOPY` | The zero-copy framebuffer, once UniFFI 0.33 releases `&mut [u8]` (D24) | MOB-04 | v3.7.0 or the first release after UniFFI 0.33 | +| `T-MOBILE-DISTRIBUTION` | Android developer verification, iOS signing, and the free store listings (D18) | MOB-02, MOB-03 | v3.9.x | +| `T-API-ENUMS` | The remaining public enums `#[non_exhaustive]`, the v4.0.0 API break (D3) | the backlog's MAJOR section | v3.9.x trial, v4.0.0 | + ## Open questions blocking planning -None block Phase 1. Open questions in the docs (esp. `architecture.md`, `mappers.md`) will be revisited at the start of the phase that needs them resolved. +None block the v3.1.0 start. The open maintainer decisions are tabled under +"Still open" in [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md), +each asked at the release that needs it. *(The sentence this replaced, "None +block Phase 1", dated from the original phase plan.)* diff --git a/to-dos/libretro/IMPLEMENTATION_PLAN.md b/to-dos/libretro/IMPLEMENTATION_PLAN.md index ba254a63..13e7ed6e 100644 --- a/to-dos/libretro/IMPLEMENTATION_PLAN.md +++ b/to-dos/libretro/IMPLEMENTATION_PLAN.md @@ -1,5 +1,11 @@ # RustyNES Libretro Core Implementation Plan +> **Historical (2026-10-07).** Every phase below shipped, at v1.10.0 "Arcade" +> and in the releases after it; `TASKS.md` has every box ticked. The forward +> libretro work, v3.0.1 to v4.0.0, is under "The v3.x line" in +> [`SPRINT_PLAN.md`](SPRINT_PLAN.md) and in +> [`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). + This document outlines the exact execution sequence to build out the `rustynes-libretro` core integration, strictly adhering to the architectural constraints established in `to-dos/libretro/SPRINT_PLAN.md` and `docs/libretro/*`. ## Proposed Changes diff --git a/to-dos/libretro/SPRINT_PLAN.md b/to-dos/libretro/SPRINT_PLAN.md index a85a19de..2718119b 100644 --- a/to-dos/libretro/SPRINT_PLAN.md +++ b/to-dos/libretro/SPRINT_PLAN.md @@ -1,5 +1,25 @@ # RustyNES Libretro Core Exhaustive Implementation & Integration Sprint Plan +## The v3.x line (current, 2026-10-07) + +Everything from "Phase 1" down shipped (v1.10.0 "Arcade" onward) and is kept as +the record of how the core was built. The forward work is below. The decisions +it cites (D5, D28) were taken by the maintainer on 2026-10-07 and are tabled in +[`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). + +| Sprint | Release | Deliverable | Gate | Status | +| --- | --- | --- | --- | --- | +| L1 | v3.0.1 | **The Rust 1.96 pin** (D5). libretro's build image stopped passing `-C ar` on 2026-09-03 (`libretro-build-rust` `841f3619`; the rebuilt image passed 2026-09-23). Push a short-lived branch with `RUSTUP_TOOLCHAIN` removed and run its pipeline on 1.99. The mirror builds every pushed branch | All 15 jobs green, Apple included: drop the pin, the seven crates' `rust-version = "1.96"`, and the `libretro-cross` 1.96 leg. Any red job: keep the pin and record the job and error in `docs/agents/libretro.md`. Watch the Apple link lines either way, because the image's `-C link-arg` flags now reach the linker | — | +| L2 | v3.1.0 | `libretro/docs#1215` (the v3.0.0 page) answered and merged; `docs/libretro/UPSTREAM_SYNC.md` brought up to date (it still says no PR is open) | The upstream state recorded | — | +| L3 | v3.6.0 | Core Options v2 (`SET_CORE_OPTIONS_V2`, v0 fallback), float-audio negotiation (`GET_AUDIO_SAMPLE_BATCH_FLOAT`, behind a capability check), LR-03, the Vs. DIP option | `abi_tests.rs` covers each path; every existing option keeps its key | — | +| L4 | v3.6.0 | **Console targets** (D28): Switch, PS Vita and 3DS, investigated for a Rust libretro core. `libnx` was dropped at v2.9.8; re-check why rather than assume | A recorded go or no-go per target; any target that goes ahead gets a green buildbot job | — | +| L5 | each release | Re-sync the forks and the upstream `.info` when the version, mapper count or capabilities change | `libretro_info` audit green; the upstream PR opened after the tag | — | + +Out of scope, as before: turbo and SOCD in the core (RetroArch provides both), +HDR output, and the VFS v5 API. + +--- + This document is the absolute source of truth for the Antigravity IDE (and any other developers or autonomous agents) when implementing the `rustynes-libretro` core integration for RetroArch. It expands upon the original architectural blueprint, defining strict code-level constraints, integration mechanisms, mathematics, and file structures. This must be followed sequentially. diff --git a/to-dos/mister/IMPLEMENTATION_PLAN.md b/to-dos/mister/IMPLEMENTATION_PLAN.md index 0c2cb4d2..914ece9e 100644 --- a/to-dos/mister/IMPLEMENTATION_PLAN.md +++ b/to-dos/mister/IMPLEMENTATION_PLAN.md @@ -1,4 +1,70 @@ -# RustyNES MiSTer core — implementation plan, v2.5.1 → v2.7.0 +# RustyNES MiSTer core — implementation plan + +## v3.1 → v4.0: the hardware release, then feature parity (current, 2026-10-07) + +The execution view of the MiSTer half of +[`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). The +decisions it cites (D1-D28) were taken by the maintainer on 2026-10-07 and are +tabled there. The narrative for the hardware release is +[`v3.x-hardware-verification-plan.md`](../plans/v3.x-hardware-verification-plan.md). + +### Where the core actually is (after v3.0.1) + + + +| Component | State | +|---|---| +| Rungs 1-5, 7 (banking) | Closed. The CPU, PPU, APU, the six mappers and the full system are cycle-exact against the oracle on every gate. Ladder **199 passed / 0 failed / 1 expected failure** on-die and **200 / 0 / 1** off-die at v3.0.0; v3.0.1 adds `mapper4mmc3oddskip080` and the dot-0 A12 fix (ledger 3.49) | +| Rung 6 (hardware) | **Open.** The SuperStation One is in hand. **No hardware has run any bitstream** | +| Mappers | 0 NROM, 1 MMC1 (up to 256 KiB PRG; SUROM/SXROM refused), 2 UxROM, 3 CNROM, 4 MMC3 (rev A; NES 2.0 board-variant submappers refused), 7 AxROM (`rtl/ines_header.sv`) | +| Cartridge size | on-die 256 K PRG / 128 K CHR; off-die 512 K / 256 K | +| Saves, OSD, input | Battery saves through the HPS (v2.6.21, not yet seen on hardware); aspect, scandoubler, scale, crop and palette options (v2.9.8, not yet seen); two pads | +| Region, audio | NTSC only; the 2A03 only, with no band-limiting and no expansion audio | +| Absent | Save states, cheats, FDS, NSF, Vs. System, the Zapper, Four Score, paddle, keyboard, PAL | +| Fit | On-die 22,922 / 41,910 ALMs (55%), **468 / 553 M10K (85%)**, 33 / 112 DSP; off-die 24,570 ALMs, 84 M10K (15%) | +| CI | Nine rung-1 gates against a pinned oracle, plus the module gates. The full ladder runs by hand from a frozen worktree, and from v3.1.0 on a self-hosted runner (D16) | + +### The phases + +| Phase | Release slot | Content | Decisions | +|---|---|---|---| +| **S** (submission prep, docs only) | v3.1.0, then alongside H | SUB-1 (a dated `ref-docs/` record of the live contribution page; it changed on 2026-09-26), SUB-2 (re-scope the checklist), SUB-5 (refresh `submission-case.md`); TL-5 (this file, done) | D10, D14 (the RTL keeps its long comments) | +| **H** (hardware) | the hardware release, numbered after the session, no later than v4.0.0 | HW-0, Strands A-F on the SuperStation One, HW-O6, fixes as gates, the re-sweep, the anchors flipped. No feature RTL | D1, D11, D13 | +| **F1** (cheap breadth) | v3.2.0 | FB-16 options first (custom palette, +8 sprites), FB-2 SUROM/SXROM, the 206 family, 66, 11, 79, 9/10, 118/119, 71/232, 34, the trivial discretes, FB-10 paddle, FB-8 Four Score, FB-6 cheats | D12, D26 | +| **F2** (the memory platform) | v3.3.0 | FB-20 arbiter (RTL-9 closes), DDR3, FB-4 save states, FB-5 rewind, the real `hps_io` under Verilator; **the off-die build becomes the headline** and on-die a "lite" build | D4, D12, D16 | +| **F3** (big boards, audio) | v3.4.0-v3.5.0 | MMC2/4, FME-7/5B, VRC2/4, the Zapper (v3.4.0); MMC5, N163, VRC6, VRC7, Bandai FCG with expansion audio, Famicom peripherals (v3.5.0) | D15 | +| **F4** (region and media) | v3.6.0-v3.8.0 | PAL/Dendy with VMODE (v3.6.0); FDS (v3.7.0); NSF, Vs. System, band-limited audio (v3.8.0) | D15 | +| **Parity** | v4.0.0 | save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, the licensed-library mapper list, the re-measured incumbent | D3 | +| **After** | v4.x | the SuperStation One distribution channel, then an openFPGA (Analogue Pocket) port | D17 | + +Open RTL items slot into the start of any release: RTL-1, RTL-5 and RTL-10 at +v3.1.0. RTL-2 and RTL-4 are investigations. RTL-3 (OAM corruption) and RTL-6 +(APU power-on phase) wait on the board. + +### Scope, re-decided 2026-10-07 + +- **Mappers: ranked by real titles** (D26). This replaces the 2026-08-23 "top + six" scope, under which `TASKS.md` recorded the remaining families as out of + scope. +- **Hardware: the SuperStation One** (D11). The DE10-Nano is optional. +- **Provenance-headered families** use rungs 1-3. Rung 4 needs an ADR 0037 + amendment naming the maintainer, per family (D15). Those families are N163, + FME-7/5B, VRC7, Bandai FCG, FDS and Vs., and anything else the command finds: + `grep -rln "^// Provenance:" crates`. +- **Not planned:** run-ahead, rollback netplay, HD packs, HD audio, TAS, Lua, + the debugger. + +### Standing rule 1, restated + +"A rung may not start until the one below is green" now means green on a +recorded ladder run. That is the frozen worktree until the self-hosted runner +exists, and both afterwards. CI's nine rung-1 gates are a subset and never +stand in for the ladder. Rules 2-7 below are unchanged. + +--- + +## History: the v2.5.1 → v3.0.0 plan > **Re-targeted twice.** [ADR 0041](../../docs/adr/0041-hardware-release-is-v3.0.0.md) > (2026-09-22) made the hardware-verified core and the contribution package v3.0.0; @@ -18,7 +84,7 @@ what is next, and what each release owes. until ADR 0043), suitable for contributing per `ref-docs/2026-08-23-mister-core-contribution-requirements.md`. -## Where the core actually is +## Where the core was (as of v2.6.x; superseded by the table at the top) +# v3.1.0 → v4.0.0: the hardware release, MiSTer parity, and the last API major + +This is the line after v3.0.0 "Cornerstone" and the v3.0.1 "Mortar" patch. It +was drafted on 2026-10-07 from three read-only surveys of both repositories and +the outside ecosystem, about 110 oracle items and 60 sibling items, and shaped +by 28 maintainer decisions taken the same day (D1-D28, the table at the end). +It continues +[`v2.9.4-to-v3.0.0-line-plan.md`](v2.9.4-to-v3.0.0-line-plan.md), whose +"After v3.0.0" list it schedules. + +Each release gets its own plan file. Each file states its gate before work +starts, and its outcome table is filled in as the release lands. This file is +the line's index. Version numbers here are slots, not promises. A release that +grows splits into patches, and the order of the oracle themes can change +without a new decision. Two things are fixed: the hardware release sits +before any feature RTL, and v4.0.0 is the API major (D3). + +**Where v3.0.1 leaves things** (verified 2026-10-06/07; `docs/STATUS.md` is the +authority): + +- The emulator is at 191 mapper families (51 Core, 109 Curated, 31 BestEffort). + AccuracyCoin is 144/144 against the catalogue as re-synced on 2026-09-19; + upstream has since added two tests (`9fc47e06`, 2026-10-05). nestest is + 0-diff. +- The MiSTer core has six mapper families (0, 1, 2, 3, 4, 7). Its ladder is + 199/0/1 on-die and 200/0/1 off-die at v3.0.0; v3.0.1 adds + `mapper4mmc3oddskip080`. On-die it uses 85% of M10K. +- **No hardware has run any bitstream.** + +## Rules every release follows + +Unchanged from the v2.7 to v3.0 lines, except where a decision is cited. + +- **Triage → pin red → fix → gate**, through the `next-version` skill. A fix + counts only if a test failed first and reverting the fix is caught. +- **Provenance firewall.** Reference emulators and third-party NES HDL are + black boxes. Run `grep -n "Provenance:"` before reading oracle source for RTL + work. Work up the ladder: documentation, then the Internet, then black-box + comparison, and derived source last. + - **Rung 4 on a Provenance-headered family needs an ADR 0037 amendment naming + the maintainer, one per family** (D15). The affected families are N163, + FME-7/5B, VRC7, Bandai FCG, FDS and Vs. The default is rungs 1-3. + - **TriCNES is the one consultable reference source** (D7). It is MIT and by + AccuracyCoin's author. It may be read, with attribution, to troubleshoot + AccuracyCoin tests its author has already solved. It lives outside the + repository, and the guardrails record the authorisation. Every other + reference emulator stays a black box. +- **SemVer, as decided (D2).** + - Format breaks are allowed in any release, provided the release notes and + the CHANGELOG say so: save states, `.rnm` movies, the netplay protocol and + `EMULATION_EPOCH`. + - MAJOR is for a public Rust API break (`rustynes-core` and what it + re-exports) or a new deliverable class. + - `VERSION-PLAN.md` was rewritten to say so on 2026-10-07. +- **The epoch rises with any change to a frame, a sample or a bus cycle** + (ADR 0045). A re-blessed golden or a moved snapshot is the trigger. +- **The test-ROM corpus includes the maintainer's library** (D21). + - Dumps from `~/Dropbox/ROMs/` (nested) are copied into the gitignored + `tests/roms/external/` as each release needs them. + - They verify new work, and anything existing that was never verified on a + real dump. + - Never commit them. Commit only snapshots, screenshots and hashes. +- **A local dump can promote a mapper tier** (D23). The committed record must + name the title, the dump hash, the frames checked and a committed screenshot. +- **Off by default, still.** A new option that changes emulation is off by + default, and is carried in `HardwareOptions` so movies and netplay record it + (ADR 0044). +- **Bitstreams.** Any release that changes RTL sweeps seeds 1-8 for both builds + on one build date. Two clean compiles of each must be byte-identical. A + release with unchanged RTL ships the previous pair and says so. +- **Never claim hardware before the hardware release.** Its PR flips the + anchors, found by grep at the time. +- **Ecosystem watch, every minor** (`T-ECOSYSTEM-WATCH`): + - the wgpu/egui pair (a breaking wgpu every three months; v31 is due about + now, an inference); + - the AccuracyCoin upstream diff; + - rcheevos; + - the libretro build image's commit log; + - the Android and iOS policy calendar; + - the Rust pin, moved in a minor about every second stable. + - Rust 1.100 ships 2026-11-12 and 1.101 2026-12-24. Algebraic float + operations (Rust 1.98) stay out of the chip crates, because they are not + deterministic. +- **Gates.** + - fmt; + - clippy for every feature combination, including `retroachievements` and + both wasm builds; + - rustdoc `-D warnings`; + - the no_std build; + - the full `--features test-roms` suite and the commercial suites; + - the release audits; + - for the sibling, one frozen-worktree ladder run per build. + +## The releases + +| Slot | Oracle theme | Sibling theme (MiSTer phase) | Plan | +| --- | --- | --- | --- | +| v3.0.1 | (in progress) the open items, the toolchain, the review sweep, and the libretro 1.96 pin test (D5) | odd-frame A12 fix | [`v3.0.1-mortar-plan.md`](v3.0.1-mortar-plan.md) | +| **v3.1.0** | AccuracyCoin re-sync, the overclock and sprite-limit options, emphasis, the NEC option, rewind and run-ahead in dual mode, records | small RTL items, the self-hosted runner, submission docs (Phase S) | [`v3.1.0-plan.md`](v3.1.0-plan.md) | +| **HW** | the mobile device run | the board session, Strands A-F (Phase H) | [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md) | +| v3.2.0 | mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions | options and cheap mappers (Phase F1) | [`v3.2.0-plan.md`](v3.2.0-plan.md) | +| v3.3.0 | phi2 write placement and the sprite-0 stale shifter, the graphics stack | the memory platform: arbiter, DDR3, save states, rewind, headline switch (Phase F2) | [`v3.3.0-plan.md`](v3.3.0-plan.md) | +| v3.4.0 | hosted netplay and RetroAchievements: Cloudflare, the RA proxy, hardcore compliance, native 3-4 players | the first big boards: MMC2/4, FME-7/5B, VRC2/4, the Zapper (Phase F3a) | [`v3.4.0-plan.md`](v3.4.0-plan.md) | +| v3.5.0 | creator tools: the full movie input stream, Lua sockets and shared memory, desktop polish | MMC5, N163, VRC6/7, Bandai FCG, Famicom peripherals (Phase F3b) | [`v3.5.0-plan.md`](v3.5.0-plan.md) | +| v3.6.0 | libretro: Core Options v2, float audio, the DIP option, console targets; Android API 37 | PAL and Dendy (Phase F4a) | [`v3.6.0-plan.md`](v3.6.0-plan.md) | +| v3.7.0 | the mobile extras | FDS with its audio (Phase F4b) | [`v3.7.0-plan.md`](v3.7.0-plan.md) | +| v3.8.0 | the remaining accuracy residuals, Vs. cabinets, the long-tail mappers | NSF, Vs. System, band-limited audio (Phase F4c) | [`v3.8.0-plan.md`](v3.8.0-plan.md) | +| v3.9.x | mobile signing and store listings (D18); the v4.0 trial and re-audit | the parity re-measure and the RC pair | [`v3.9.0-plan.md`](v3.9.0-plan.md) | +| **v4.0.0** | the public enums `#[non_exhaustive]` (the API major) | MiSTer feature parity | [`v4.0.0-plan.md`](v4.0.0-plan.md) | + +**The hardware release has no number yet (D1).** It is chosen after the board +session, and it is no later than v4.0.0. + +- If the session is clean, or its fixes are small, it takes the next free minor + after v3.1.0, and every later slot moves up by one. +- If it finds the wrong FPGA device, the cost is an L-sized re-target (HW-A8). + The maintainer then picks the number with the evidence in hand. +- Either way, the hardware release comes **before any feature RTL** (v3.2.0's + sibling half and later). A Strand C correction is an RTL change, which reopens + the seed sweep at that day's build date, and mixing it with feature RTL would + hide which change cost the timing margin. +- Oracle work does not wait for the board: v3.1.0's oracle half and v3.2.0's + can land while it is pending. + +### v3.1.0: options, emphasis, and the re-sync + +Oracle-heavy. The sibling side is small and has no feature RTL. Detail: +[`v3.1.0-plan.md`](v3.1.0-plan.md). + +- **AccuracyCoin re-sync first** (D25, `T-ACCURACYCOIN-RESYNC-2610`). Re-sync + to upstream HEAD (`9fc47e06` or later), then triage the two new tests and the + "Misaligned OAM Behavior" fix red-first. The sibling picks up any changed + goldens at its pin move. +- **FE-01 CPU-multiplier overclock and FE-02 "disable sprite limit"** (D22, + `T-CPU-OVERCLOCK`, `T-SPRITE-LIMIT`). Both are in `HardwareOptions`, carried + in `.rnm` and the netplay `config_digest`, with one format and protocol bump + for both. FE-02 renders extra sprites only: the overflow flag and evaluation + timing stay exact. +- **ACC-01, the PAL/Dendy emphasis red/green swap** (`T-PAL-EMPHASIS`). A + documented hardware behaviour, with an epoch rise. +- **ACC-02, composite artifacts as an opt-in video option** (D20, + `T-COMPOSITE-ARTIFACTS`): differential phase distortion and inter-pixel + artifacts. Video only, so emulation output does not change. +- **ACC-13, the NEC rev B MMC3 as a config override** (D20, + `T-MMC3-NEC-OVERRIDE`). It lets `mmc3_test_2/6` run under NEC without + changing the default. +- **FE-09, rewind and run-ahead in Vs. DualSystem mode** (D27, + `T-PS-dual-runahead`). The two consoles are snapshotted together. ADR 0032 + has a 2026-10-07 amendment. +- **CI-02, an epoch fingerprint gate** (`T-EPOCH-FINGERPRINT`). A fixed panel + of output hashes fails CI when it moves without an epoch rise. Every behaviour + change in this line leans on it. +- **Records:** + - DOC-01..DOC-09; + - retire `to-dos/v1.8.x-on-device-verification.md` into the v2.9.3 run sheet + (D25); + - `docs/mappers.md` and `docs/compatibility.md` open questions; + - CI-04's search exclusion for anything vendored that stays. +- **The 1.96 pin.** v3.0.1 tests it (D5). If the test is green, v3.0.1 drops + it. If it is red, v3.1.0 records the failing job and keeps the pin, and + `T-LIBRETRO-TOOLCHAIN` stays open. +- **Sibling:** + - RTL-1 (`$2006` on a `v_pipeline` load dot), RTL-5 (the decay stimulus), + RTL-10 (register the 15 AccuracyCoin sub-test ROMs); + - TL-1 (the Verilator skew); + - the self-hosted ladder runner (D16); + - Phase S documentation: SUB-1 (a dated `ref-docs/` record of the 2026-09-26 + wiki change), SUB-2 (re-scope the checklist to the live page), SUB-5 + (refresh `submission-case.md`); + - TL-5 (these planning docs, done 2026-10-07). + +**Gate:** + +- AccuracyCoin all-pass against the re-synced catalogue, whatever its new count + is, stated with the upstream commit; +- each new option's red-first test, and a movie and a netplay session refused + across a mismatched option; +- the epoch fingerprint gate shown to fail on a seeded mutation; +- the sibling ladder 0 failed, with no seed sweep unless the RTL changed. + +### HW: the hardware-verification release (number chosen after the session, D1) + +Plan: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md). +Strands A-F come from +[`v2.9.x-final-audit-and-hardware-plan.md`](v2.9.x-final-audit-and-hardware-plan.md), +and the bring-up worksheet is the sibling's `docs/bringup-log.md`. + +- **The SuperStation One only** (D11). The DE10-Nano becomes an optional later + row (HW-X3). The two-board checklist box is split, never ticked on one board + (HW-X2). +- **Order:** + 1. HW-0, stage the kit; + 2. Strand A, including HW-A8 (the FPGA marking: 5CSEBA6U23I7 against the + published 5CSXFC6D6F31) and HW-A9 (the SDRAM part); + 3. Strand B on-die; + 4. Strand C (palette, H/V timing, resting audio level, band-limiting); + 5. Strand D (battery, AccuracyCoin as bytes, the incumbent A/B, a soak of 4 + hours or more, the save across a power cycle); + 6. the off-die strand, plus HW-O6, the real SDRAM constraints; + 7. E1/E2; + 8. F1, re-measuring the incumbent on the same silicon. +- **No feature RTL in this release.** Every fix the board finds becomes a gate + first where simulation can reach it. Then re-sweep at the release date, + re-run the failed strand and every later one, and flip the anchors (HW-X1). +- **Board against oracle (D13).** The board wins only when it agrees with + documented hardware behaviour. The RTL and the oracle are fixed in the same + release, red first, with an epoch rise and an ADR 0037 amendment per case. +- **The mobile device run** (MOB-01, the 60 rows of + `docs/mobile-v2.9.3-run-sheet.md`) belongs to this release under ADR 0043 + Decision 2. It can split into its own patch if the phone session and the board + session fall on different days. +- **After it (D10).** Write the feature delta and the re-measured incumbent + number into `submission-case.md`. Then the maintainer decides whether the + sibling goes public and whether to email MiSTer-devel. + +**Gate:** + +- every `bringup-log.md` row PASS against the md5 read off the console; +- the ladder green on the tagged commit; +- AccuracyCoin read back as bytes and diffed; +- every hardware anchor flipped in the same PR. + +### v3.2.0: mappers, both cores + +Plan: [`v3.2.0-plan.md`](v3.2.0-plan.md). + +- **Oracle (D26, `T-MAPPER-BREADTH-V3`):** the missing families that have real + titles, checked against the Dropbox library first. + - 8 (FFE; staged dumps already refused), 158 (Tengen 800037), 207 (Taito + X1-005 variant), 116, 114/182/215, 197, 165, 172/173, 230, 235, 252. + - 157 (Datach) only if the barcode reader is in scope. + - The demand for each is unverified until a dump is found. +- **ACC-08/ACC-09, KNOWN_BLANK triage** (`T-KNOWN-BLANK`): 52 dumps, starting + with *Chu Liu Xiang*, the Sansuu 1-3 Nen titles and *Gradius II (VC)*. The + ratchet holds in both directions. +- **MAP-01 tier promotions under D23** (`T-CURATED-EVIDENCE`): candidates 47, + 121, 191, 290, 154 and 243. +- **The dump-corpus sweep (D21, `T-DUMP-CORPUS`):** every family with a + Dropbox dump and no recorded boot gets one. +- **Sibling, Phase F1 (D12, first part):** + - FB-16 core options at the start of the release (custom palette, +8 sprites, + the region placeholder off); + - FB-2 SUROM/SXROM (off-die); + - the next ~10 by coverage per effort: the 206 family, 66, 11, 79, 9/10, + 118/119, 71/232, 34; + - the trivial discretes; + - FB-10 paddle, FB-8 Four Score, FB-6 cheats (an oracle-gated rung). + +**Gate:** + +- each family pinned by a register-decode test and a boot snapshot; +- the honesty gate's tier counts updated; +- every sibling family with bus and checkpoint gates and a caught mutant; +- one seed sweep for the release. + +### v3.3.0: the memory platform + +Plan: [`v3.3.0-plan.md`](v3.3.0-plan.md). + +- **Sibling, Phase F2 (D12, second part):** + - FB-20, the slot-scheduled SDRAM arbiter, with RTL-9 closing; + - DDR3 bring-up; + - FB-4 save states (the framework's four slots, oracle-gated by a + serialise/restore round trip), then FB-5 rewind; + - the real `hps_io` under Verilator for every HPS-facing feature (D16, TL-8). +- **The headline switch (D4).** + - From this release the off-die build is the headline. + - The on-die build continues as a reduced "lite" build. + - `releases/` carries both from here on. +- **Oracle (D20):** ACC-03 phi2 write placement, reopened against the v2.6.17 + conditions, and ACC-04 the sprite-0 stale shifter. They take one + state-and-epoch break together. Both are measured against AccuracyCoin, and + each is kept only if the whole battery holds. +- **The graphics stack:** wgpu 31 and egui 0.37 together, with the vendored + `egui-winit` re-diffed, or removed if upstream has caught up. + +### v3.4.0: hosted services, and the first big boards + +Plan: [`v3.4.0-plan.md`](v3.4.0-plan.md). + +- **Oracle (D19, D25):** + - NET-04 / RA-01: a Cloudflare Worker with Durable Objects for the lobby, and + Cloudflare TURN with short-lived credentials minted by the Worker + (`T-HOSTED-NETPLAY`); + - the browser RA proxy (`T-RA-PROXY`); + - a privacy policy (both RA and F-Droid's `NonFreeNet` need one); + - RA hardcore compliance (`T-RA-HARDCORE`): block Lua and TAS playback in + hardcore, queue offline unlocks, check the User-Agent format, refresh + `docs/ra-integration-request.md`, then apply; + - NET-01, native 3-4 player netplay over the existing `mesh_net.rs` + (`T-NETPLAY-MESH-NATIVE`). +- **Sibling, Phase F3a:** + - MMC2/MMC4 (9/10, *Punch-Out!!*); + - FME-7 with 5B audio (69, Provenance-headered, so rungs 1-3 only); + - VRC2/VRC4; + - FB-9 Zapper. + +### v3.5.0: creator tools, and the expansion-audio boards + +Plan: [`v3.5.0-plan.md`](v3.5.0-plan.md). + +- **Oracle:** + - CR-03, the full movie input stream (Zapper, microphone, Vs. coins and + service, FDS disk events; an `.rnm` format bump, `T-MOVIE-INPUT-STREAM`); + - CR-01 Lua WebSockets and CR-02 shared memory; + - desktop polish FE-03..FE-08 and FE-10. +- **Sibling, Phase F3b:** + - MMC5 (needs v3.3.0's arbiter); + - N163, VRC6, VRC7/OPLL, Bandai FCG (all Provenance-headered except VRC6; + D15); + - FB-12 expansion audio with each; + - FB-11 Famicom peripherals. + +### v3.6.0: libretro, Android API 37, and PAL on the FPGA + +Plan: [`v3.6.0-plan.md`](v3.6.0-plan.md). + +- **Oracle:** + - libretro Core Options v2 and float-audio negotiation, both behind a + capability check; + - LR-03 the Vs. DIP option; + - D28, an investigation of console targets (Switch, PS Vita, 3DS) for a Rust + libretro core, recorded with a go or no-go per target + (`T-LIBRETRO-CONSOLES`); + - Android API 37 (`T-ANDROID-API37`): the `ACCESS_LOCAL_NETWORK` runtime + permission for LAN netplay, and background audio under a valid lifecycle. + Play's API 37 deadline is about 2027-08-31 (an inference from the yearly + pattern). +- **Sibling, Phase F4a:** FB-7 PAL and Dendy with `T-MISTER-VMODE`. The oracle's + PAL step table is Provenance-headered, so the RTL is written from nesdev. + +### v3.7.0: the mobile extras, and FDS on the FPGA + +Plan: [`v3.7.0-plan.md`](v3.7.0-plan.md). + +- **Oracle (D24, `T-MOBILE-EXTRAS`):** + - iOS box art and a home-screen widget; + - iOS external-display output; + - the 20-band EQ and a cheat database on both apps; + - MOB-08, the live Kotlin TODOs. + - MOB-06, the zero-copy framebuffer (`T-MOB-06-ZEROCOPY`), lands here if + UniFFI 0.33 has released (it is merged upstream as PR #2940). Otherwise it + moves to the first release after. +- **Sibling, Phase F4b:** FB-13 FDS (disk image in SDRAM or DDR3, the BIOS on + die, write persistence through the HPS, a disk-swap OSD) with FDS audio. Both + are Provenance-headered, so the RTL is written from documentation. + +### v3.8.0: residuals and the rest of the console + +Plan: [`v3.8.0-plan.md`](v3.8.0-plan.md). + +- **Oracle:** + - ACC-05 (FDS `$4030.D1`), ACC-06 (a measured-RC filter, opt-in) and ACC-10 + (2A03H), each only where a source exists; + - ACC-07, the Vs. DualSystem real cabinets (the combined *Wrecking Crew* dump + first); + - MAP-05, the long-tail mappers. +- **Sibling, Phase F4c:** FB-14 NSF, FB-15 Vs. System, and FB-17 band-limited + audio informed by HW-C4. + +### v3.9.x: distribution, and v4.0 preparation + +Plan: [`v3.9.0-plan.md`](v3.9.0-plan.md). + +- **Mobile signing and store listings (D18).** + - Register for Android developer verification, which is global from 2027. + Wider distribution than the 20-device limited account needs a government + ID. + - Set up iOS signing so TestFlight uploads actually run (MOB-02). TestFlight + builds expire after 90 days, and uploads need Xcode 27 from about April + 2027, an inference. + - Then the Play, F-Droid (or IzzyOnDroid) and App Store listings, free and + without monetisation (ADR 0035). + - This is the release right before v4.0.0's final development, test and + release activities. +- **v4.0 preparation:** + - trial the enum break on a branch; + - re-run the four audit scopes; + - MiSTer: the parity re-measure, then the RC bitstream pair at the RC date. + +### v4.0.0: the API major and MiSTer parity (D3) + +Plan: [`v4.0.0-plan.md`](v4.0.0-plan.md). + +- **The API break** (`T-API-ENUMS`). The remaining public enums become + `#[non_exhaustive]`: about 13 of 20 in `rustynes-core`, 21 of 27 in + `rustynes-mappers`, and others in the chip crates, counted at planning time. + It is the one change found that must be a MAJOR. Release notes restate every + break since v3.0.0. +- **MiSTer parity:** + - save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four + Score; + - the mapper families covering the incumbent's licensed-library list; + - a re-measured incumbent comparison. +- **The hardware release lands by v4.0.0 at the latest** (D1). If it has not + been numbered earlier, it is this release. +- **MOB-04 is not a MAJOR on its own.** `rustynes-mobile` is internal under + `VERSION-PLAN.md`'s definition of public API, so the bridge API change can + land in any minor. + +## After v4.0.0 (recorded, not planned here) + +- **The SuperStation One distribution channel, then an openFPGA (Analogue + Pocket) port** (D17), as v4.x line items. `nes_top.sv` is kept + framework-free for the port. +- **The MiSTer-devel submission**, if D10's post-hardware decision says go. + Allow a month or more for review. The repository transfer is one-way, and + whether it is still part of the process must be confirmed. +- **winit 0.31**, once egui adopts it (not before Q1 2027, an inference). +- **A Rust 2027 edition migration**, if rustc gains `Edition2027` (re-check + about Q2 2027). It would be a post-v4.0 minor. +- **The DE10-Nano run** (HW-X3), if a board appears (D11). + +## Maintainer decisions + +Taken 2026-10-07 unless marked. Each binds every plan in this line. + +| ID | Decision | +| --- | --- | +| D1 | The hardware-verified release is numbered after the board session, no later than v4.0.0 | +| D2 | Format breaks (state, movie, netplay, epoch) are allowed in any release with notes; MAJOR = a public Rust API break or a new deliverable class | +| D3 | v4.0.0 = the remaining public enums `#[non_exhaustive]` plus MiSTer feature parity | +| D4 | On-die stays the headline until save states (v3.3.0); off-die is the headline from then, on-die a "lite" build; `releases/` carries both | +| D5 | The libretro 1.96 pin is tested in v3.0.1 with a pushed branch pipeline on 1.99; dropped if all 15 jobs pass, kept and recorded otherwise | +| D6 | T-NTSC-PROVENANCE: the Bisqwit-style NTSC pass is treated as derived from Bisqwit's code on the NESdev "NTSC video" page (header, section 1 row, `NOTICE`; the self-certification removed) | +| D7 | The vendored TriCNES source moves out of the repository after a licence check (MIT, verified 2026-10-07 upstream and locally); it stays available, attributed, as reference for AccuracyCoin troubleshooting | +| D8 | v3.0.1's bitstreams ship at build date 261007 even if the merge is later | +| D9 | Cleanup authorised: the v3.0.1 sweep branches, PR #584 closed after the merge, the release, review and pin-test branches deleted after the merges | +| D10 | MiSTer submission: prepare the documents alongside the board work; decide the public repository and the submission after the hardware release and the incumbent re-measure | +| D11 | Verification on the SuperStation One only; the DE10-Nano is an optional later row | +| D12 | After the board: options and cheap mappers (v3.2.0), then save states (v3.3.0), then PAL/Dendy and FDS | +| D13 | When the board and the oracle disagree, the board wins where it agrees with documented hardware; RTL and oracle are fixed in the same release, red first, with an epoch rise and an ADR 0037 amendment | +| D14 | The RTL keeps its long comments | +| D15 | Provenance-headered families: rungs 1-3 by default; rung 4 needs an ADR 0037 amendment naming the maintainer, per family | +| D16 | Sibling CI: a self-hosted runner now; the real `hps_io` under Verilator at v3.3.0; no hosted-CI goldens, no Quartus container | +| D17 | Other homes: the SuperStation One channel first, then an openFPGA port, both v4.x | +| D18 | Mobile signing and store listings at about v3.9, right before v4.0.0's final activities | +| D19 | Hosting on Cloudflare: Durable Objects lobby, Cloudflare TURN, the RA proxy | +| D20 | Reopen ACC-02 (opt-in), ACC-03, ACC-04 and ACC-13 | +| D21 | `~/Dropbox/ROMs/` is a test source; needed dumps copied into the gitignored `tests/roms/external/`; never committed | +| D22 | FE-01 overclock and FE-02 sprite limit both in v3.1.0, carried in movies, netplay and `config_digest` | +| D23 | A local dump counts as Curated evidence when the record names the title, hash, frames and a committed screenshot (`docs/compatibility.md`) | +| D24 | All four mobile extras: MOB-06 after UniFFI 0.33, iOS box art and widget, iOS external display, EQ and cheat DB on both | +| D25 | AccuracyCoin re-sync first in v3.1.0; the v1.8.x checklist retired into the v2.9.3 run sheet; RA hardcore approval worked toward. The merge queue (CI-01) is not adopted | +| D26 | Mapper scope ranked by real titles, in both repositories | +| D27 | Lift ADR 0032's rewind and run-ahead exclusions in dual mode | +| D28 | Investigate console targets (Switch, PS Vita, 3DS) for the libretro core | + +**Still open** (asked at the release that needs them): + +| When | Decision | +| --- | --- | +| each release | the codename, and merge authorisation | +| HW | the hardware release's number (D1), with the session's evidence in hand | +| HW | whether the sibling repository goes public, and whether to submit to MiSTer-devel (D10) | +| v3.2.0 | whether 157 (Datach) is in scope: it needs a barcode-reader input device | +| v3.4.0 | the netplay and RA proxy domain, and who operates it (D19 sets the provider, not the name) | +| v3.6.0 | which console targets go forward, after D28's investigation | +| v3.9.x | Android identity: full developer verification, or the 20-device account plus IzzyOnDroid/F-Droid | +| v3.9.x | whether upstream `releases/` ever carries the off-die build (SUB-12) | diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md new file mode 100644 index 00000000..41ac7704 --- /dev/null +++ b/to-dos/plans/v3.1.0-plan.md @@ -0,0 +1,89 @@ + +# v3.1.0: options, emphasis, and the AccuracyCoin re-sync + +The first minor of the line in +[`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md). It is drafted on +2026-10-07, before v3.0.1 "Mortar" has merged. The codename is the +maintainer's, asked at the cut, and the file is renamed +`v3.1.0--plan.md` then. Re-read every "Where it starts" fact against +the tree when the release begins, because v3.0.1 may still move some of them. + +**The release is mostly oracle work.** The sibling side carries small RTL items +and documents, and no feature RTL, because feature RTL waits for the hardware +release (D1, D12). If the board session happens during this release, it is its +own release, not part of this one. + +## Where it starts + +| Fact | Value | Source | +| --- | --- | --- | +| AccuracyCoin | 144/144 against the catalogue as re-synced 2026-09-19 (#528) | `docs/STATUS.md` | +| AccuracyCoin upstream | 8 commits since, through `9fc47e06` (2026-10-05): "Added 2 new tests", a "Misaligned OAM Behavior" fix, a PPU "Data Bus" → "IO Bus" rename | GitHub API, 2026-10-06 | +| `EMULATION_EPOCH` | 2 (v3.0.1) | `crates/rustynes-core/src/hardware_options.rs` | +| `.rnm` / netplay | format 5 / protocol 6 (`"RNE6"`) | ADR 0045 | +| CPU-multiplier overclock | absent (`rg cpu_multiplier` finds nothing); the scanline overclock shipped v2.9.7 | backlog FE-01 | +| "Disable sprite limit" | the UI persists the flag and nothing reads it; `rustynes-ppu` has no hook | `config.rs:1797`, `settings_panel.rs:1937-1942` | +| PAL emphasis | the red/green swap is "not modelled, by choice" | `rustynes-ppu/src/emphasis.rs:62-63` | +| Dual-mode rewind and run-ahead | excluded by ADR 0032 | ADR 0032 and its 2026-09-30 amendment | +| Epoch rule | enforced by hand only | ADR 0045 Consequences; `docs/agents/ci-and-release.md` | + +## Scope and gates + +Every gate is stated before work starts. "Red first" means a test that fails +on the v3.0.1 tree, and a mutation that reverts the fix and is caught. + +| # | Item | Ticket | Gate | +| --- | --- | --- | --- | +| 1 | **AccuracyCoin re-sync** (D25): upstream HEAD into `tests/roms/accuracycoin/` and its catalogue; each new or changed test triaged | `T-ACCURACYCOIN-RESYNC-2610` | All-pass on the RAM decoder against the new catalogue, with its count and the upstream commit recorded in `docs/STATUS.md`. Any test that fails gets a red-first fix or a recorded cause. An emulation change raises the epoch | +| 2 | **Epoch fingerprint gate** (CI-02) | `T-EPOCH-FINGERPRINT` | A fixed panel of output hashes (framebuffer, audio, bus trace on a few ROMs) compared against a committed table that names the epoch. A changed hash with an unchanged epoch fails. Shown to fail on a seeded behaviour mutation, and to pass once the epoch is raised with the table | +| 3 | **FE-01, the CPU-multiplier overclock** (D22) | `T-CPU-OVERCLOCK` | Off by default; a field in `HardwareOptions` (already `#[non_exhaustive]`, so not an API break); stock timing under every accuracy path that does not opt in. Red-first: a test that the multiplier reaches the core, and that a movie or netplay peer with a different value is refused with the option named | +| 4 | **FE-02, disable the sprite limit** (D22) | `T-SPRITE-LIMIT` | Render-only. Red first: a ROM with more than 8 sprites on a line shows them with the option on. The overflow flag and evaluation timing are unchanged, pinned by the existing sprite-overflow suites passing with the option on. Carried like item 3 | +| 5 | **One format bump for items 3 and 4**: `.rnm` 5 → 6, netplay protocol 6 → 7, `config_digest` covering both | — | An older movie and an older peer are refused with a reason; the foreign movie imports still import at the current options | +| 6 | **ACC-01, the PAL/Dendy emphasis red/green swap** | `T-PAL-EMPHASIS` | From the NESdev NTSC/PAL pages. Red first: an emphasis frame on a 2C07 with the swapped tint; NTSC unchanged; the palette goldens re-blessed in the same commit; the epoch raised | +| 7 | **ACC-02, composite artifacts as an opt-in video option** (D20) | `T-COMPOSITE-ARTIFACTS` | Differential phase distortion and inter-pixel artifacts, from the documented composite model. A post-process that changes no emulation output, so no epoch rise. The default picture is byte-identical (the visual regression suite) | +| 8 | **ACC-13, the NEC rev B MMC3 as a per-game or config override** (D20) | `T-MMC3-NEC-OVERRIDE` | `mmc3_test_2/6` passes under the override; the default (rev A) results unchanged; the option recorded in `HardwareOptions` | +| 9 | **FE-09, rewind and run-ahead in Vs. DualSystem mode** (D27) | `T-PS-dual-runahead` | Both consoles snapshot and restore as one (the existing "RVSD" container). Red first: a rewind across a cabinet frame restores both framebuffers byte-identically. Run-ahead gives the same output as without it. ADR 0032 amended | +| 10 | **Records** (DOC-01..DOC-09) | — | Each stale statement corrected against the code with a citation; `to-dos/v1.8.x-on-device-verification.md` folded into `docs/mobile-v2.9.3-run-sheet.md` and deleted (D25); markdownlint and the release audits green | +| 11 | **The libretro toolchain** (D5) | `T-LIBRETRO-TOOLCHAIN` | If v3.0.1 dropped the pin: nothing to do but confirm the next buildbot pipeline. If not: the failing job's name and error recorded in `docs/agents/libretro.md`, and the pin kept | +| 12 | **Sibling RTL-1**: `$2006`'s `v <- t` copy on a `v_pipeline` load dot | — | A stimulus that lands the copy on dot 257 or an increment dot, compared per cycle; RTL changes only if it diverges | +| 13 | **Sibling RTL-5**: a PPU open-bus decay stimulus that reaches the three inert mutations | — | Each mutation CAUGHT, or classified as stimulus-blind with its reach counted | +| 14 | **Sibling RTL-10**: register the 15 unregistered AccuracyCoin sub-test ROMs | — | Each resolves to a suite and test address and runs in the ladder | +| 15 | **Sibling TL-1 and the self-hosted runner** (D16) | — | CI's Verilator version recorded; the runner provisioned with the existing scripts, and one PR's ladder run on it end to end | +| 16 | **Sibling Phase S documents**: SUB-1, SUB-2, SUB-5 | — | A dated `ref-docs/` record of the live MiSTer contribution page read by hand; the checklist re-scoped with `contribution_checklist_audit.rs` passing; `submission-case.md` refreshed | + +**Not in this release:** + +- feature RTL (it waits for the hardware release); +- ACC-03 and ACC-04 (v3.3.0, one state break together); +- mapper breadth (v3.2.0); +- hosting (v3.4.0). + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.1-S1 | Items 1 and 2: the re-sync, and the fingerprint gate first, so every later behaviour change is checked by it | items 1 and 2 | — | +| 3.1-S2 | Items 3, 4 and 5: the two options and the single format bump | items 3-5 | — | +| 3.1-S3 | Items 6, 7 and 8: emphasis, composite artifacts, the NEC override | items 6-8 | — | +| 3.1-S4 | Item 9: dual-mode rewind and run-ahead | item 9 | — | +| 3.1-S5 | Items 12-16: the sibling, in parallel with S2-S4 (it touches no oracle file) | items 12-16 | — | +| 3.1-S6 | Items 10 and 11, the ecosystem watch, the release cut | full gates; ladders; release notes listing every format break (D2) | — | + +## Risks + +- **The re-sync can fail a test the oracle cannot pass from documentation.** + The ladder applies, and TriCNES is consultable under D7 with attribution. + Rung 4 is never used silently. +- **FE-02 is easy to get wrong.** Rendering extra sprites must not touch + evaluation: the overflow flag is observable to games, and several timing ROMs + pin it. That is why the gate runs the overflow suites with the option on. +- **One format bump for two options** assumes both land in this release. If + one slips, the bump still happens once, with the remaining option. +- **v3.0.1 not yet merged.** This plan was written before it merged; re-check + the "Where it starts" table. + +## Outcome + +| # | Outcome | Evidence | +| --- | --- | --- | +| — | not started | — | diff --git a/to-dos/plans/v3.2.0-plan.md b/to-dos/plans/v3.2.0-plan.md new file mode 100644 index 00000000..cee8cbae --- /dev/null +++ b/to-dos/plans/v3.2.0-plan.md @@ -0,0 +1,45 @@ + +# v3.2.0: mappers in both cores (MiSTer Phase F1) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. **The sibling half cannot start until the hardware release has +shipped** (D1, D12). If the hardware release takes this number, this plan +becomes the next minor's. The codename is asked at the cut. + +## Scope and gates + +**Oracle: mapper breadth by real titles** (D26, `T-MAPPER-BREADTH-V3`). + +| # | Item | Gate | +| --- | --- | --- | +| 1 | The missing families with real titles, each first searched for in `~/Dropbox/ROMs/` (D21): 8 (FFE), 158 (Tengen 800037), 207 (Taito X1-005 variant), 116, 114/182/215, 197, 165, 172/173, 230, 235, 252. 157 (Datach) is open: it needs a barcode reader | Per family: written from its NESdev page; a register-decode unit test; a boot snapshot from a real dump, or a synthetic CC0 fixture where none exists; a tier set honestly (ADR 0011); `docs/mappers.md` updated. The two staged *Doraemon* hacks that are refused as "mapper 8" today boot | +| 2 | ACC-08 / ACC-09: KNOWN_BLANK triage (`T-KNOWN-BLANK`), starting with *Chu Liu Xiang* (176.2; the mirroring power-on hypothesis, a CPU trace first), the Sansuu 1-3 Nen titles (m185) and *Gradius II (VC)* | Each dump leaves the list by a red-first fix with an epoch rise, or stays with a recorded cause. The ratchet holds in both directions | +| 3 | MAP-01 tier promotions under D23 (`T-CURATED-EVIDENCE`): 47, 121, 191, 290, 154, 243 | Each has a byte-identity boot snapshot in `external_extended.rs`, and a record naming the title, hash, frames and screenshot; the honesty gate's counts are updated | +| 4 | The dump-corpus sweep (D21, `T-DUMP-CORPUS`): every implemented family with a Dropbox dump and no recorded boot | A boot snapshot per family, or a recorded refusal with a reason. Nothing committed but snapshots, screenshots and hashes | + +**Sibling: Phase F1** (D12, first part). One seed sweep for the release. + +| # | Item | Gate | +| --- | --- | --- | +| 5 | FB-16 core options at the start of the release, before the sweep: custom palette, +8 sprites per line (mirrors FE-02), the region placeholder off | Each option checked by co-simulation where it is observable, and labelled "compiled, timing-closed, not seen on hardware" until seen | +| 6 | FB-2 MMC1 SUROM/SXROM (off-die only, 512 KiB PRG): *Dragon Warrior III/IV*, *Final Fantasy I+II* | Bus and checkpoint gates, plus a commercial-frame gate where a dump exists; a caught mutant | +| 7 | The next ~10 by coverage per effort: the 206 family (Namco 108, a subset of MMC3 decode), 66, 11, 79, 9/10, 118/119, 71/232, 34, and the trivial discretes (13, 87, 180, 185, 94, 140) | Same as row 6, per family | +| 8 | FB-10 paddle, FB-8 Four Score, FB-6 cheats (an oracle-gated rung: apply a code on both sides and compare the bus) | Each with an exporter stimulus flag and a gate | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.2-S1 | Items 1 and 4 (oracle families and the corpus) | rows 1, 4 | — | +| 3.2-S2 | Items 2 and 3 | rows 2, 3 | — | +| 3.2-S3 | Item 5, the sibling options, first | row 5 | — | +| 3.2-S4 | Items 6 and 7, the sibling mappers | rows 6, 7 | — | +| 3.2-S5 | Item 8, then the seed sweep and the release cut | row 8; the sweep; both ladders | — | + +## Risks + +- Several families above are Japan-only or pirate boards, and demand for them + is unverified. The Dropbox search comes first, and a family with no dump and + no notable title can move to v3.8.0's long tail. +- On-die M10K is at 85%. Families that need memory are off-die only from the + start. diff --git a/to-dos/plans/v3.3.0-plan.md b/to-dos/plans/v3.3.0-plan.md new file mode 100644 index 00000000..7b083856 --- /dev/null +++ b/to-dos/plans/v3.3.0-plan.md @@ -0,0 +1,44 @@ + +# v3.3.0: the memory platform, and the headline switch (MiSTer Phase F2) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Sibling: Phase F2** (D12, second part; D4; D16). + +| # | Item | Gate | +| --- | --- | --- | +| 1 | FB-20, the slot-scheduled SDRAM arbiter, with refresh in slots known to be idle. RTL-9 (the CHR residual) closes with it | No request over its budget across the full ladder, on a measurement that can fail; the CHR residual gone | +| 2 | DDR3 bring-up for the framework's save-state region | A round trip through the region in simulation; verified on the SuperStation One | +| 3 | FB-4 save states (`T-MISTER-SAVESTATE`): the framework's four slots; every register and memory streamed, PRG/CHR-RAM in SDRAM included | Oracle-gated: serialise mid-game, restore, and compare the bus per cycle against an unbroken run | +| 4 | FB-5 rewind, a ring of states in DDR3 | A rewind of N seconds restores a state byte-identical to the one saved | +| 5 | The real `hps_io` under Verilator for HPS-facing features (D16, TL-8) | Save, save state and cheat loading exercised against `hps_io` itself, with lint suppressed where needed | +| 6 | **The headline switch** (D4): off-die becomes the headline; on-die continues as a reduced "lite" build; `releases/` carries both | `docs/bitstream-release.md`, the README and both repositories' release notes updated; both builds swept | + +**Oracle.** + +| # | Item | Gate | +| --- | --- | --- | +| 7 | ACC-03, PPU register write placement (phi2 against M2-low, half a dot), reopened under D20 against the v2.6.17 reopen conditions (`T-PHI2-WRITE`) | Kept only if AccuracyCoin stays all-pass and nestest 0-diff. The default-off `phi2-write-sweep` knob is either the new default or retired, with the measurement recorded | +| 8 | ACC-04, the sprite-0 stale shifter and internal/external bus split (`T-SPRITE0-STALE`), attempted under D20 without a motivating game; the `stale_sprite_shift_regs_probe.rs` diagnostic is the start | Same bar as row 7. If it fails it, the attempt is recorded as refuted and closed | +| 9 | One state break and one epoch rise for rows 7 and 8, if either lands | The release notes name the break (D2) | +| 10 | The graphics stack: wgpu 31 with egui 0.37, together; the vendored `egui-winit` re-diffed, or removed if crates.io has caught up | Every clippy combination and both wasm builds; the frame-pacing measurement re-run | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.3-S1 | Rows 1 and 5 (the arbiter and the test harness come first) | rows 1, 5 | — | +| 3.3-S2 | Rows 2-4 | rows 2-4 | — | +| 3.3-S3 | Rows 7-9 (oracle, in parallel) | rows 7-9 | — | +| 3.3-S4 | Row 10, row 6, then the sweep and the cut | rows 6, 10; both ladders | — | + +## Risks + +- Save states are the largest single item in the MiSTer backlog. If FB-4 does + not fit, rewind (row 4) moves to the next release rather than the release + growing. +- Rows 7 and 8 were deliberately not done before. Each has a clear refute + path, and a refutation is a result. diff --git a/to-dos/plans/v3.4.0-plan.md b/to-dos/plans/v3.4.0-plan.md new file mode 100644 index 00000000..71ecc0f5 --- /dev/null +++ b/to-dos/plans/v3.4.0-plan.md @@ -0,0 +1,37 @@ + +# v3.4.0: hosted netplay and achievements, and the first big boards (MiSTer Phase F3a) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Oracle: hosted services** (D19, D25). + +| # | Item | Gate | +| --- | --- | --- | +| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), well inside the free 1,000 GB a month | +| 2 | RA-01, the browser RetroAchievements proxy (`T-RA-PROXY`), on the same account; `RA_PROXY_BASE` set | A live browser login and unlock with a real account (RA-02) | +| 3 | A privacy policy, which both RA's compliance page and F-Droid's `NonFreeNet` anti-feature need | Published, and linked from the README and the apps | +| 4 | RA hardcore compliance (`T-RA-HARDCORE`): Lua and TAS playback blocked in hardcore (as well as cheats, rewind, slowdown, frame advance, state loading, memory editors and the debugger); offline unlocks queued; the User-Agent format checked against RA's `Name/v1.0.0 (OS) core/v0.5.0`; `docs/ra-integration-request.md` refreshed (it still says v1.8.8 and MIT/Apache) | An audit of `session_policy.rs` against the full block list, each block pinned by a test; then the application sent (the process is unverified: the forum post returned 403) | +| 5 | NET-01, native 3-4 player netplay over the existing `mesh_net.rs` (`T-NETPLAY-MESH-NATIVE`); MOB-09 for mobile follows | A 4-peer loopback session stays in sync over N frames; the desktop UI reaches it | + +The domain name and who operates the service are still open (the line plan's +open-decisions table). + +**Sibling: Phase F3a.** + +| # | Item | Gate | +| --- | --- | --- | +| 6 | MMC2/MMC4 (9/10): the CHR latch on a PPU fetch; *Punch-Out!!* | Bus and checkpoint gates, a commercial-frame gate, a caught mutant | +| 7 | Sunsoft FME-7 with 5B audio (69). The oracle file is Provenance-headered, so the RTL comes from nesdev with the oracle as a black-box comparator (D15) | Same, plus an audio gate | +| 8 | Konami VRC2/VRC4 (21/22/23/25) | Same as row 6 | +| 9 | FB-9 Zapper (`T-MISTER-ZAPPER`) | An exporter stimulus with a gate; the feel is checked on the board | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.4-S1 | Rows 1-3 | rows 1-3 | — | +| 3.4-S2 | Rows 4-5 | rows 4-5 | — | +| 3.4-S3 | Rows 6-9 (sibling, in parallel) | rows 6-9; one seed sweep; both ladders | — | diff --git a/to-dos/plans/v3.5.0-plan.md b/to-dos/plans/v3.5.0-plan.md new file mode 100644 index 00000000..fce1d694 --- /dev/null +++ b/to-dos/plans/v3.5.0-plan.md @@ -0,0 +1,38 @@ + +# v3.5.0: creator tools, and the expansion-audio boards (MiSTer Phase F3b) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Oracle: creator tools and desktop polish.** + +| # | Item | Gate | +| --- | --- | --- | +| 1 | CR-03, the full movie input stream (`T-MOVIE-INPUT-STREAM`): the Zapper, the microphone, Vs. coins and service, FDS disk events, expansion devices; an `.rnm` format bump, and the netplay input stream if it is affected | A Zapper, a Vs. and an FDS movie each record and replay byte-identically. Older movies are refused with a reason (D2) | +| 2 | CR-01 Lua WebSocket client (`comm.ws*`) and CR-02 real OS shared memory | Each with a host test; no change to the Lua budget model | +| 3 | Desktop polish: FE-03 (A/V sync control), FE-04 (named FDS and multicart slots), FE-05 (browser drag-and-drop), FE-06 (`HostWarning` codes), FE-07 (rebindable Power Pad, microphone, keyboard), FE-08 (emu-thread priority on Windows and macOS), FE-10 (DualSystem in the `wasm-canvas` embed) | Each with a test where one can reach it, and the rest recorded as manual checks | + +**Sibling: Phase F3b.** MMC5 needs v3.3.0's arbiter. + +| # | Item | Gate | +| --- | --- | --- | +| 4 | MMC5 (5): ExRAM, split screen, 1 MiB, expansion audio | Bus and checkpoint gates, the worst SDRAM pattern inside budget, *Castlevania III* frame gates | +| 5 | Namco 163 (19), Konami VRC6 (24/26), VRC7 with OPLL (85), Bandai FCG (16/153/159). N163, VRC7 and FCG are Provenance-headered (D15), so they use rungs 1-3 | Per board: bus, checkpoint and audio gates, a caught mutant | +| 6 | FB-12 expansion audio with each board; FB-11 Famicom peripherals (keyboard, P2 microphone, Power Pad, Miracle Piano) | Audio gates against the oracle's output; input gates by stimulus | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.5-S1 | Row 1 | row 1 | — | +| 3.5-S2 | Rows 2-3 | rows 2-3 | — | +| 3.5-S3 | Rows 4-6 (sibling) | rows 4-6; one seed sweep; both ladders | — | + +## Risks + +- VRC7's OPLL is the largest audio block and is Provenance-headered in the + oracle (`opll.rs`). If documentation does not suffice, the escalation is an + ADR 0037 amendment naming the maintainer (D15), never a silent read. The + board can move to v3.8.0 rather than the release waiting on it. diff --git a/to-dos/plans/v3.6.0-plan.md b/to-dos/plans/v3.6.0-plan.md new file mode 100644 index 00000000..6f9baf71 --- /dev/null +++ b/to-dos/plans/v3.6.0-plan.md @@ -0,0 +1,31 @@ + +# v3.6.0: libretro, Android API 37, and PAL on the FPGA (MiSTer Phase F4a) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Oracle.** + +| # | Item | Gate | +| --- | --- | --- | +| 1 | Libretro Core Options v2 (`RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2`, with v0 as a fallback): categories, sublabels, value labels (`T-LIBRETRO-OPTIONS-V2`) | The C-ABI harness (`abi_tests.rs`) covers both paths; every existing option keeps its key | +| 2 | Float-audio negotiation (`GET_AUDIO_SAMPLE_BATCH_FLOAT`, added to `libretro.h` 2026-06-29) behind a capability check; i16 stays the fallback | Both paths tested; nothing changes when the frontend does not offer it (RetroArch's last tag is still 1.22.2, from 2025-11) | +| 3 | LR-03, a Vs. DIP-switch core option | The option reaches the cabinet; a test pins it | +| 4 | D28, console targets for a Rust libretro core (Switch, PS Vita, 3DS) (`T-LIBRETRO-CONSOLES`) | A recorded go or no-go per target, with what blocked it. `libnx` was dropped at v2.9.8 for a reason that must be re-checked, not assumed. Any target that goes forward gets a buildbot job, green | +| 5 | Android API 37 (`T-ANDROID-API37`): the `ACCESS_LOCAL_NETWORK` runtime permission for LAN and direct-IP netplay; background audio only inside a valid lifecycle (on Android 17 it fails silently otherwise); `targetSdk` 37 | The permission flow covered by a JVM test; a LAN session on the emulator; Play's deadline is about 2027-08-31 (an inference from the yearly pattern) | + +**Sibling: Phase F4a.** + +| # | Item | Gate | +| --- | --- | --- | +| 6 | FB-7 PAL and Dendy with `T-MISTER-VMODE`: 312 lines, 3.2 dots per CPU cycle, the APU PAL tables, video timing, `new_vmode`. The oracle's `frame_counter.rs` (the PAL step table) is Provenance-headered, so the RTL is written from nesdev (D15) | PAL goldens exported from the oracle; a PAL bus and framebuffer gate; a caught mutant per table | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.6-S1 | Rows 1-3 | rows 1-3 | — | +| 3.6-S2 | Row 4 (investigation) and row 5 | rows 4, 5 | — | +| 3.6-S3 | Row 6 (sibling) | row 6; one seed sweep; both ladders | — | diff --git a/to-dos/plans/v3.7.0-plan.md b/to-dos/plans/v3.7.0-plan.md new file mode 100644 index 00000000..7508969d --- /dev/null +++ b/to-dos/plans/v3.7.0-plan.md @@ -0,0 +1,33 @@ + +# v3.7.0: the mobile extras, and FDS on the FPGA (MiSTer Phase F4b) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Oracle: the mobile extras** (D24, `T-MOBILE-EXTRAS`). Every Swift change is +uncompiled until a device or CI build compiles it. Each one gets a run-sheet +row. + +| # | Item | Gate | +| --- | --- | --- | +| 1 | iOS box art (Android has `BoxArt.kt` and `ScraperSources.kt`) and a WidgetKit home-screen widget (Android has `ResumeWidget.kt`) | The iOS CI build compiles; run-sheet rows added | +| 2 | iOS external-display output, with the phone as the controller | Same | +| 3 | The 20-band EQ and a cheat database on both apps, through the bridge | Bridge tests on the host; run-sheet rows | +| 4 | MOB-08, the live Kotlin TODOs (capture audio mux, live save-state thumbnails, the library long-press menu, the list-detail rail, a 3-way cloud-save merge, folder nesting, Tink key rotation, `TODO(i18n)` literals, a box-art fallback) | Each fixed with a JVM test where one reaches it, or recorded | +| 5 | MOB-06, the zero-copy framebuffer (`T-MOB-06-ZEROCOPY`), **only if UniFFI 0.33 has released** (zero-copy `&mut [u8]` is merged upstream as PR #2940, unreleased at drafting). `run_frame` becomes `run_frame_into(&self, out: &mut [u8])` | About 15 MB/s of per-frame copies gone, measured; the bridge change recorded as internal (`rustynes-mobile` is not public API under `VERSION-PLAN.md`) | + +**Sibling: Phase F4b.** + +| # | Item | Gate | +| --- | --- | --- | +| 6 | FB-13 FDS: the disk image in SDRAM or DDR3, the 8 KiB BIOS on die, write persistence through the HPS, a disk-swap OSD, FDS audio. `fds.rs` is Provenance-headered (D15) | The oracle's FDS goldens gated; a disk write round-tripped through the HPS under the real `hps_io` (D16) | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.7-S1 | Rows 1-3 | rows 1-3 | — | +| 3.7-S2 | Rows 4-5 | rows 4-5 | — | +| 3.7-S3 | Row 6 (sibling) | row 6; one seed sweep; both ladders | — | diff --git a/to-dos/plans/v3.8.0-plan.md b/to-dos/plans/v3.8.0-plan.md new file mode 100644 index 00000000..35b04a72 --- /dev/null +++ b/to-dos/plans/v3.8.0-plan.md @@ -0,0 +1,34 @@ + +# v3.8.0: the residuals, and the rest of the console (MiSTer Phase F4c) + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. The codename is asked at the cut. + +## Scope and gates + +**Oracle.** Each item lands only where a source exists to pin it; otherwise it +stays open with the reason recorded. + +| # | Item | Gate | +| --- | --- | --- | +| 1 | ACC-05, the FDS `$4030.D1` DRAM-refresh watchdog IRQ | Only from published hardware research; red first | +| 2 | ACC-06, a measured-RC model of the APU's analog filter chain, opt-in | The default output byte-identical | +| 3 | ACC-10, the 2A03H "unexpected DMA" direction | Only from a measurement; opt-in revision only | +| 4 | ACC-07, the Vs. DualSystem real cabinets: why the combined *Wrecking Crew* dump never reaches attract mode (no new dumps needed), then Tennis and Mahjong if combined dumps appear | The four `#[ignore]`d boots in `vs_dualsystem.rs`, each unignored or recorded with its cause | +| 5 | MAP-05, the long-tail mappers: whatever v3.2.0 deferred, plus families with a Dropbox dump (D21, D26) | Per family as in v3.2.0 | + +**Sibling: Phase F4c.** + +| # | Item | Gate | +| --- | --- | --- | +| 6 | FB-14 NSF player | The oracle's NSF output gated | +| 7 | FB-15 Vs. System: palettes and a DIP OSD | Vs. goldens gated | +| 8 | FB-17 band-limited audio, informed by HW-C4's FFT. `blip.rs` is Provenance-headered (D15), so the RTL is written from documentation | An audio gate against the oracle's band-limited output | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.8-S1 | Rows 1-3 | rows 1-3 | — | +| 3.8-S2 | Rows 4-5 | rows 4-5 | — | +| 3.8-S3 | Rows 6-8 (sibling) | rows 6-8; one seed sweep; both ladders | — | diff --git a/to-dos/plans/v3.9.0-plan.md b/to-dos/plans/v3.9.0-plan.md new file mode 100644 index 00000000..efef930c --- /dev/null +++ b/to-dos/plans/v3.9.0-plan.md @@ -0,0 +1,36 @@ + +# v3.9.x: distribution, and the v4.0.0 preparation + +A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on +2026-10-07. Mobile signing and the store listings come here, right before +v4.0.0's final development, test and release activities (D18). Like v2.9.x +before v3.0.0, this line may take several patches. The codename is asked at +the cut. + +## Scope and gates + +**Mobile distribution** (D18, `T-MOBILE-DISTRIBUTION`; free and without +monetisation, ADR 0035). + +| # | Item | Gate | +| --- | --- | --- | +| 1 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" | +| 2 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process | +| 3 | **The listings:** Google Play (a new personal account needs 12 testers for 14 days first), F-Droid or IzzyOnDroid (the `foss` flavour, reproducible from `Cargo.lock`, IzzyOnDroid's 30 MB APK limit checked), and the App Store under guideline 4.7 | Each listing live, or the blocker recorded | +| 4 | The mobile device run re-done on the release builds, if the hardware release's run is older than the bridge changes since | The run sheet's rows PASS on the shipped builds | + +**v4.0.0 preparation.** + +| # | Item | Gate | +| --- | --- | --- | +| 5 | Trial `T-API-ENUMS` on a branch: every remaining public enum `#[non_exhaustive]`, counted at the time | The workspace, every feature combination and both wasm builds compile; the downstream match sites listed for v4.0.0's notes | +| 6 | Re-run the four audit scopes against the tree, as v2.9.0 and v2.9.9 did | Each new finding fixed red first or dispositioned in its ledger | +| 7 | MiSTer: the parity re-measure against the incumbent's public feature list, then the release-candidate bitstream pair at the RC date | The feature delta written honestly; two clean compiles of each build byte-identical | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 3.9-S1 | Rows 1-2 | rows 1-2 | — | +| 3.9-S2 | Rows 3-4 | rows 3-4 | — | +| 3.9-S3 | Rows 5-7 | rows 5-7 | — | diff --git a/to-dos/plans/v3.x-hardware-verification-plan.md b/to-dos/plans/v3.x-hardware-verification-plan.md index c91ef53e..9e0e2ad3 100644 --- a/to-dos/plans/v3.x-hardware-verification-plan.md +++ b/to-dos/plans/v3.x-hardware-verification-plan.md @@ -9,6 +9,26 @@ a MAJOR of its own is decided when it is planned ([ADR 0043](../../docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md), Decision 2). +**Decided 2026-10-07 (maintainer; the line plan's D1, D11, D13, D16).** + +- **The number (D1).** The number is chosen *after* the board session, with + its evidence in hand, and it is no later than v4.0.0. In + [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md) this release is + the slot "HW". It sits after v3.1.0 and **before any feature RTL**: a + Strand C correction is an RTL change that reopens the seed sweep at that + day's build date, so it must not share a release with feature RTL. +- **One board (D11).** Verification is on the SuperStation One only. The + DE10-Nano is an optional later row (HW-X3), done only if a board turns up. + The two-board box in the contribution checklist is split rather than + ticked on one board. +- **Board against oracle (D13).** When they disagree, the board wins only + where it agrees with documented hardware behaviour. The RTL and the oracle + are fixed in the same release, red first, with an `EMULATION_EPOCH` rise + and an ADR 0037 amendment per case. +- **CI (D16).** The ladder runs on a self-hosted runner from v3.1.0. This + release's fixes are gated there, as well as by the frozen-worktree run. + There are no hosted-CI goldens and no Quartus container. + > **History of this file.** It was written as the v3.0.0 plan, when ADR 0041 > made v3.0.0 the hardware-verified core. ADR 0043 (2026-09-29) made v3.0.0 the > API major with a release-candidate core instead, and moved the board session, @@ -37,7 +57,41 @@ Decision 2). - Strand F's incumbent re-measure and feature delta are written up honestly. - `to-dos/mister/contribution-checklist.md` is complete. Every box that can be evidence about this core is ticked or carries a reason; the two-board box stays - open unless a second board has actually run it. + open unless a second board has actually run it. Under D11 it is split into a + SuperStation One row (ticked by this release) and an open DE10-Nano row. +- HW-A8, the FPGA marking, is photographed and recorded before anything else + is concluded. + - The build targets 5CSEBA6U23I7, and one published spec says 5CSXFC6D6F31. + - If the SS1 really is the second part, the result is a second Quartus target + with its own sweep: an L-sized re-target. The release number (D1) is then + chosen with that cost known. +- HW-O6, the provisional SDRAM constraints, are replaced by the real part's + numbers (HW-A9), and the off-die build is re-swept. + +## The order + +1. **HW-0:** stage the kit (`tools/stage_board_kit.sh`) for this release's own + bitstreams. +2. **Strand A:** the stock core first, including HW-A8 (the device) and HW-A9 + (the SDRAM part). +3. **Strand B:** the on-die build, stopping at the first failure. +4. **Strand C:** the four properties no gate reaches. Budget a second compile + cycle, because a palette or timing correction is RTL. +5. **Strand D:** the battery, AccuracyCoin as bytes, the incumbent A/B on the + same silicon, a soak of 4 hours or more, and the save across a power cycle. +6. **The off-die strand:** MemTest, then B4-B9, AUD-28 (HW-O4), AUD-29 (HW-O5) + and HW-O6. +7. **E1/E2:** direct video, and the menu mask. +8. **F1:** re-measure the incumbent. Then the maintainer's D10 decision, which + covers the public repository and the submission. + +Every fix the board finds becomes a gate first, where simulation can reach it. +Then re-sweep at the release's build date, re-run the failed strand and every +later one, and flip the anchors (HW-X1). + +**The mobile device run** (MOB-01, `docs/mobile-v2.9.3-run-sheet.md`) belongs to +this release under ADR 0043 Decision 2. It may become its own patch if it falls +on a different day from the board session. ## What changes in the same PR @@ -64,3 +118,10 @@ Decision 2). FDS, expansion audio, save states, cheats, Vs. System, NSF, Zapper, Four Score, paddle, keyboard and PAL on the FPGA; the remaining mapper families; the DE10-Nano second board. + +Scheduled on 2026-10-07: + +- Phases F1-F4, v3.2.0 to v3.8.0, lead to the parity milestone at v4.0.0, in + [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md). +- The DE10-Nano stays an optional row (D11). +- The off-die build becomes the headline at v3.3.0 (D4). diff --git a/to-dos/plans/v4.0.0-plan.md b/to-dos/plans/v4.0.0-plan.md new file mode 100644 index 00000000..e38c4add --- /dev/null +++ b/to-dos/plans/v4.0.0-plan.md @@ -0,0 +1,50 @@ + +# v4.0.0: the API major, and MiSTer parity + +The end of the line in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), +drafted on 2026-10-07 and defined by the maintainer (D3): **the remaining +public enums `#[non_exhaustive]`, plus MiSTer feature parity.** The codename is +asked at the cut. + +## Why it is a MAJOR + +Under D2, MAJOR is for a public Rust API break or a new deliverable class. +Format breaks no longer need one. + +- **The API break.** Most public enums are still exhaustive. Counting `pub enum` + against an adjacent `#[non_exhaustive]` on 2026-10-06 gave these as + non-exhaustive: about 7 of 20 in `rustynes-core`, 6 of 27 in + `rustynes-mappers`, 2 of 7 in `rustynes-apu`, 1 of 6 in `rustynes-ppu`, 1 of + 2 in `rustynes-cpu`, and 1 of 14 in `rustynes-netplay`. +- **Why now.** Adding a variant to any of the rest breaks downstream matches. + Making them all non-exhaustive at once means later variants (new options, new + input-event kinds, new error cases) are additive. This is the enum half of + v3.0.0's `T-API-EXTENSIBLE`, and the only item found that must be a MAJOR. +- **The hardware release** carries the new-deliverable-class trigger. If it has + not been numbered by now, it is this release (D1). + +## Scope and gates + +| # | Item | Gate | +| --- | --- | --- | +| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums | +| 2 | Release notes restating every break since v3.0.0: the format bumps of v3.1.0, v3.3.0 and v3.5.0, every epoch rise, the enums | Built from the CHANGELOG's entries and every `!` commit and `BREAKING CHANGE:` footer since v3.0.0, so none is missed | +| 3 | MiSTer parity: save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and mapper families covering the incumbent's licensed-library list | Each feature gated in the ladder; the parity table in `submission-case.md` with the re-measured incumbent number | +| 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4) swept at the release date | Two clean compiles of each byte-identical; md5s verified by download from both releases | +| 5 | The hardware release, if it is this one (D1) | The hardware plan's gate | + +## Sprints + +| Sprint | Deliverable | Gate | Status | +| --- | --- | --- | --- | +| 4.0-S1 | Row 1, merged from the trial | row 1 | — | +| 4.0-S2 | Row 3's remaining gaps | row 3 | — | +| 4.0-S3 | Rows 2, 4 and 5, and the release ceremony | rows 2, 4, 5; full gates; both ladders | — | + +## Out of scope (after v4.0.0) + +- the SuperStation One distribution channel, then the openFPGA port (D17); +- the MiSTer-devel submission's outcome (D10); +- winit 0.31, if not taken earlier; +- a Rust 2027 edition migration; +- the DE10-Nano run (D11). From 2fa7e2cb19329fb68e9ec50d2ff54e2fe250d25d Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 00:16:24 -0400 Subject: [PATCH 34/44] docs(roadmap): move the hardware release to the end of v3.9.x (D29) The v3.1 -> v4.0 line plan, drafted earlier on 2026-10-07, put the hardware release right after v3.1.0 and before any feature RTL. Reading it, the maintainer asked for the opposite (D29). The whole release moves toward the end of v3.9.x, "so as much as possible has been implemented, integrated, fixed, enhanced, improved and optimized" first: the SuperStation One board session (HW-0, Strands A-F, HW-O6, the fixes, the re-sweep, the anchors) and the mobile device run. D1 still holds: the number is chosen after the session, and it is no later than v4.0.0. The new order: - v3.2.0-v3.8.0: the MiSTer feature phases F1-F4 land as planned, verified in simulation only. Rung 6 stays open beneath them; the IMPLEMENTATION_PLAN now states that exception to standing rule 1 rather than leaving the rule to be read as blocking. - v3.9.0: the sibling's RTL feature freeze, the parity re-measure, the release-candidate pair, the enum-break trial, the audit re-run, and the signing set-up (D18). - The hardware release is the last v3.9.x. The board session runs on v3.9.0's frozen pair, then the mobile device run, then the store listings on the builds that run passed. It still carries no feature RTL of its own: a board fix is RTL and reopens the seed sweep, and keeping features out keeps each lost margin attributable to one fix. If its fixes are large it folds into v4.0.0. - v4.0.0 follows it, so the parity core it ships is the one the board verified. Files: - The line plan: the intro, the slot table (HW row moved below v3.9.0), the D1 paragraph, and the HW section, moved to sit before v4.0.0, with its position and prerequisites. Also the v3.1.0, v3.8.0, v3.9.x and v4.0.0 sections, the After list, and the D10/D12 rows reworded without rewriting their substance. A D29 row and a new open decision are added. - v3.9.0-plan.md: rewritten as two releases. v3.9.0 covers the freeze, the preparation and the signing set-up; the hardware release covers the board, the device run, the listings and the D10 write-up. - v3.1.0 and v3.2.0: the "waits for the hardware release" blocks are replaced; the old wording is recorded. - v3.4.0: the Zapper's feel is checked at the hardware release. - v3.8.0: FB-17 is written from documentation. HW-C4's FFT now re-checks it at the end instead of informing it. - v4.0.0: row 4 now ships the board-verified pair byte for byte when the RTL is unchanged since the hardware release. A recompile at a new date is a different bitstream from the one the board tested, which the earlier "sweep at the release date" row would have shipped unremarked. Row 5 applies only if the hardware release folds in. - The hardware-verification plan: a D29 position bullet. The deliverable table is restated with off-die as the headline (D4 makes it so from v3.3.0, before this release now runs). A "Risks of D29" section is added, and the deferred list is marked as landing before this release. - The MiSTer plans: - IMPLEMENTATION_PLAN: the phase table is reordered (a Freeze row, then H, then Parity); the rung 6 exception and a risk note are added. - SPRINT_PLAN: rows reordered into execution order. M16 and M20 keep their IDs because other records cite them. Two re-planning triggers are rewritten for a late HW-A8 and for late D13 corrections. - VERSION-PLAN.md: the forward rows (HW after v3.9.0) and the slot rule. - ROADMAP.md: the status bullet. - DEFERRED: the two "after the v3.x hardware release" notes now say that means the last v3.9.x. - ADR 0043: a NEW dated amendment (Context, Decision, Consequences). It supersedes only the ordering clause of the earlier same-day amendment; the earlier text is left as written. Risks, now written down in the line plan, the hardware plan and the ADR: - about eight releases of feature RTL unverified on silicon; - HW-A8 (FPGA device) and HW-A9 (SDRAM part) are read last, so a wrong part re-targets the parity core rather than the six-mapper core; - off-die is the headline from v3.3.0 while its SDRAM constraints stay provisional until HW-O6; - board-against-oracle corrections (D13) cluster just before v4.0.0, each usually with an EMULATION_EPOCH rise. The mitigations are the existing per-feature simulation gates, a seed sweep in every release that changes RTL, and v3.9.0's freeze. One more is recorded as an OPEN maintainer decision rather than planned: an optional, read-only reading of the SS1's chip markings, with no bitstream loaded, before the end. Conflicts resolved: - D12 said "after the board"; its feature order stands and the qualifier is retired. - D4's headline switch at v3.3.0 now precedes the board, so the hardware plan's deliverable table names off-die as the headline instead of on-die. - ADR 0035's D18 amendment ("about v3.9") was already consistent and is unchanged. Verified: - the pinned markdownlint hook passes on every changed file (to-dos/plans/ is markdownlintignored, so the eight plan files were linted as copies outside that tree); - release_anchor_audit 14/0, release_state_prose_audit 16/0, contribution_checklist_audit 9/0; - every relative .md link and #anchor in the changed files resolves, including #risks-of-d29 and the moved HW heading; - a grep for "before any feature RTL", "after the board", "next free minor" and the "waits for" phrasings finds only the recorded history. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- VERSION-PLAN.md | 13 +- ...-api-major-and-a-release-candidate-core.md | 43 ++++ to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md | 9 +- to-dos/ROADMAP.md | 8 +- to-dos/mister/IMPLEMENTATION_PLAN.md | 25 ++- to-dos/mister/SPRINT_PLAN.md | 25 ++- to-dos/plans/v3.1-to-v4.0-line-plan.md | 194 +++++++++++------- to-dos/plans/v3.1.0-plan.md | 10 +- to-dos/plans/v3.2.0-plan.md | 7 +- to-dos/plans/v3.4.0-plan.md | 2 +- to-dos/plans/v3.8.0-plan.md | 2 +- to-dos/plans/v3.9.0-plan.md | 65 ++++-- .../plans/v3.x-hardware-verification-plan.md | 50 +++-- to-dos/plans/v4.0.0-plan.md | 10 +- 14 files changed, 323 insertions(+), 140 deletions(-) diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index 9dcf9de1..82894dc6 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -64,15 +64,17 @@ released version; these are plans, and `to-dos/plans/` holds the detail. The line from v3.1.0 to v4.0.0 is indexed in [`v3.1-to-v4.0-line-plan.md`](to-dos/plans/v3.1-to-v4.0-line-plan.md), drafted -2026-10-07 with the maintainer's decisions D1-D28. Version numbers after v3.1.0 -are slots: the hardware release takes a number only after its board session -(D1), and the slots after it move up by one if it takes a minor. +2026-10-07 with the maintainer's decisions D1-D29. Version numbers after v3.1.0 +are slots. The hardware release is the last of v3.9.x, after v3.9.0's RTL +feature freeze and before v4.0.0 (D29); it takes a number only after its board +session (D1), and folds into v4.0.0 if its fixes are large. (The first draft +placed it right after v3.1.0, before any feature RTL; D29 reversed that the same +day.) | Version | Scope | Plan | |---------|-------|------| | v3.0.1 "Mortar" | The open items (the palette-offset A/B, T-GA23C-CHRRAM, the MiSTer odd-frame A12 stimulus), every dependency and the Rust 1.99 toolchain, the libretro 1.96 pin test, every unanswered review comment back to #1 | [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md) | | v3.1.0 | The AccuracyCoin re-sync; the CPU overclock and sprite-limit options in movies and netplay; PAL emphasis; opt-in composite artifacts; the NEC MMC3 option; rewind and run-ahead in Vs. dual mode; an epoch fingerprint gate. MiSTer: small RTL items, the self-hosted runner, submission documents | [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md) | -| HW (numbered after the session) | **Hardware verification**: the SuperStation One board session (Strands A-F), the mobile device run, and the fixes each produces. No feature RTL | [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` | | v3.2.0 | Mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions. MiSTer F1: options and about ten cheap families, paddle, Four Score, cheats | [`v3.2.0-plan.md`](to-dos/plans/v3.2.0-plan.md) | | v3.3.0 | Phi2 write placement and the sprite-0 stale shifter; wgpu 31 / egui 0.37. MiSTer F2: the SDRAM arbiter, DDR3, save states, rewind; the off-die build becomes the headline | [`v3.3.0-plan.md`](to-dos/plans/v3.3.0-plan.md) | | v3.4.0 | Hosted netplay and the browser RA proxy on Cloudflare, RA hardcore compliance, native 3-4 player netplay. MiSTer F3a: MMC2/4, FME-7/5B, VRC2/4, the Zapper | [`v3.4.0-plan.md`](to-dos/plans/v3.4.0-plan.md) | @@ -80,7 +82,8 @@ are slots: the hardware release takes a number only after its board session | v3.6.0 | Libretro Core Options v2, float audio, console targets; Android API 37. MiSTer F4a: PAL and Dendy | [`v3.6.0-plan.md`](to-dos/plans/v3.6.0-plan.md) | | v3.7.0 | The mobile extras (iOS box art, widget and external display; EQ and cheat DB; zero-copy frames after UniFFI 0.33). MiSTer F4b: FDS | [`v3.7.0-plan.md`](to-dos/plans/v3.7.0-plan.md) | | v3.8.0 | The remaining accuracy residuals, Vs. cabinets, the long-tail mappers. MiSTer F4c: NSF, Vs. System, band-limited audio | [`v3.8.0-plan.md`](to-dos/plans/v3.8.0-plan.md) | -| v3.9.x | Mobile signing and store listings; the enum-break trial; the audits re-run; the MiSTer RC pair | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md) | +| v3.9.0 | The MiSTer RTL feature freeze and RC pair; the enum-break trial; the audits re-run; mobile signing set up | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md) | +| HW (the last v3.9.x, numbered after the session) | **Hardware verification** (D29): the SuperStation One board session (Strands A-F) on v3.9.0's frozen pair, the mobile device run, the fixes each produces, then the store listings. No feature RTL | [`v3.9.0-plan.md`](to-dos/plans/v3.9.0-plan.md), [`v3.x-hardware-verification-plan.md`](to-dos/plans/v3.x-hardware-verification-plan.md), [`v2.9.x-final-audit-and-hardware-plan.md`](to-dos/plans/v2.9.x-final-audit-and-hardware-plan.md) (Strands A-F), `docs/mobile-v2.9.3-run-sheet.md` | | v4.0.0 | **The API major**: the remaining public enums `#[non_exhaustive]`; MiSTer feature parity | [`v4.0.0-plan.md`](to-dos/plans/v4.0.0-plan.md) | ### Post-1.0 release line (v1.1.0 → current) diff --git a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md index 9736a31e..00f5a39c 100644 --- a/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md +++ b/docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md @@ -141,3 +141,46 @@ decisions D1-D3). Decisions 1, 3 and 4 above stand as history. This amendment changes no shipped artefact. + +## Amendment (2026-10-07, later the same day): the hardware release moves to the end of v3.9.x (D29) + +### Context + +The amendment above placed the hardware-verification release right after +v3.1.0 and before any feature RTL, so the board would verify the six-mapper core +of v3.0.x and every MiSTer feature after it would be built on a verified base. +On reading the drafted line, the maintainer asked for the opposite: the whole of +the hardware release, the board session and the mobile device run, toward the +end of v3.9.x, "so as much as possible has been implemented, integrated, fixed, +enhanced, improved and optimized" before it runs. + +### Decision + +**D29.** The hardware release is the last release of v3.9.x, after v3.9.0's +RTL feature freeze and release-candidate pair, and immediately before v4.0.0. +It still carries no feature RTL of its own, only board fixes, each a simulation +gate first where simulation reaches it. Its number is still chosen after the +session (D1); if its fixes are large it folds into v4.0.0. The mobile device run +moves with it (Decision 2), and the store listings (ADR 0035's 2026-10-07 +amendment, D18) follow that run. + +This supersedes only the ordering clause of the amendment above ("after v3.1.0 +and before any feature RTL"). Its D1 numbering rule, D2 and D3 stand. + +### Consequences + +- The MiSTer features of v3.2.0 to v3.8.0 land verified in simulation only, and + rung 6 stays open under them; the feature phases need a green ladder, not a + board. +- The board verifies the near-parity core, so v4.0.0 ships a hardware-verified + parity core, and the submission decision (D10) is taken just before v4.0.0 + with nearly the whole feature delta in hand. +- The FPGA device (HW-A8) and SDRAM part (HW-A9) are read last. A wrong part + means an L-sized re-target of the parity core rather than the six-mapper core. + An optional read-only reading of the chip markings before then is recorded as + an open decision, not planned. +- The off-die build is the headline from v3.3.0 (D4) while its SDRAM + constraints stay provisional until HW-O6, at the end. +- Board-against-oracle corrections (D13) cluster just before v4.0.0, each with + an oracle change and usually an `EMULATION_EPOCH` rise. +- The full risk list is in the line plan's "Risks of D29". diff --git a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md index c677f86b..55a94df6 100644 --- a/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md +++ b/to-dos/DEFERRED-AND-CARRYOVER-FEATURES.md @@ -165,7 +165,10 @@ > date). Mobile signing and the free store listings come at about **v3.9.x**, > right before v4.0.0's final activities. That covers Android developer > verification, which is global from 2027 and covers sideloads too, and iOS -> signing so that TestFlight uploads run.)* +> signing so that TestFlight uploads run. D29, later the same day, put the v3.x +> hardware release itself at the end of v3.9.x, so "after the v3.x hardware +> release" above now means the last v3.9.x: signing is set up at v3.9.0, and +> the listings follow the hardware release's device run.)* --- @@ -950,7 +953,9 @@ app stores (**after the v3.x hardware release**).)* the bridge at v2.9.7; their device rows are T1-T12 of the run sheet. In the [v3.1 → v4.0 line plan](plans/v3.1-to-v4.0-line-plan.md): -- the device run belongs to the hardware release (ADR 0043 Decision 2); +- the device run belongs to the hardware release (ADR 0043 Decision 2), which + D29 placed at the end of v3.9.x, so the stores ("after the v3.x hardware + release", above) come at the end of v3.9.x too; - the mobile extras are **v3.7.0** (D24): iOS box art, widget and external display, and the EQ and cheat database on both apps; - the zero-copy framebuffer (MOB-06) follows UniFFI 0.33; diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md index 29a6a532..1183e7f7 100644 --- a/to-dos/ROADMAP.md +++ b/to-dos/ROADMAP.md @@ -58,12 +58,12 @@ v2.8.0 → v0.9.7; the synthesis itself = **v1.0.0**. ## Status - **Current release:** **RustyNES v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** -- **The line after v3.0.0 (drafted 2026-10-07):** [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) indexes v3.1.0 → v4.0.0, with one plan file per release and the maintainer's 28 decisions (D1-D28) tabled there. In order: +- **The line after v3.0.0 (drafted 2026-10-07):** [`plans/v3.1-to-v4.0-line-plan.md`](plans/v3.1-to-v4.0-line-plan.md) indexes v3.1.0 → v4.0.0, with one plan file per release and the maintainer's 29 decisions (D1-D29) tabled there. In order (D29 moved the hardware release to the end of v3.9.x the same day): - v3.0.1 "Mortar" (in progress); - v3.1.0: options, emphasis, the AccuracyCoin re-sync; - - the hardware release, numbered after its board session and no later than v4.0.0, with no feature RTL; - - v3.2.0 to v3.8.0: oracle themes alongside MiSTer phases F1-F4; - - v3.9.x: mobile signing and store listings, and v4.0 preparation; + - v3.2.0 to v3.8.0: oracle themes alongside MiSTer phases F1-F4, the MiSTer features verified in simulation; + - v3.9.0: the MiSTer RTL feature freeze and RC pair, mobile signing set up, and v4.0 preparation; + - the hardware release, the last v3.9.x: the board session on that pair and the mobile device run, then the store listings; numbered after the session, no later than v4.0.0, with no feature RTL; - **v4.0.0**: the remaining public enums `#[non_exhaustive]`, plus MiSTer feature parity. Format breaks no longer need a MAJOR (D2; `VERSION-PLAN.md`). The tickets the line minted are under "Tickets for the v3.1 → v4.0 line" below. diff --git a/to-dos/mister/IMPLEMENTATION_PLAN.md b/to-dos/mister/IMPLEMENTATION_PLAN.md index 914ece9e..67204181 100644 --- a/to-dos/mister/IMPLEMENTATION_PLAN.md +++ b/to-dos/mister/IMPLEMENTATION_PLAN.md @@ -1,11 +1,13 @@ # RustyNES MiSTer core — implementation plan -## v3.1 → v4.0: the hardware release, then feature parity (current, 2026-10-07) +## v3.1 → v4.0: feature parity, then the hardware release (current, 2026-10-07) The execution view of the MiSTer half of [`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). The -decisions it cites (D1-D28) were taken by the maintainer on 2026-10-07 and are -tabled there. The narrative for the hardware release is +decisions it cites (D1-D29) were taken by the maintainer on 2026-10-07 and are +tabled there. **D29, later that day, moved Phase H from right after v3.1.0 to +the end of v3.9.x**, after every feature phase, so the board verifies the most +complete core; the phase table below is in the new order. The narrative for the hardware release is [`v3.x-hardware-verification-plan.md`](../plans/v3.x-hardware-verification-plan.md). ### Where the core actually is (after v3.0.1) @@ -29,18 +31,25 @@ tabled there. The narrative for the hardware release is | Phase | Release slot | Content | Decisions | |---|---|---|---| -| **S** (submission prep, docs only) | v3.1.0, then alongside H | SUB-1 (a dated `ref-docs/` record of the live contribution page; it changed on 2026-09-26), SUB-2 (re-scope the checklist), SUB-5 (refresh `submission-case.md`); TL-5 (this file, done) | D10, D14 (the RTL keeps its long comments) | -| **H** (hardware) | the hardware release, numbered after the session, no later than v4.0.0 | HW-0, Strands A-F on the SuperStation One, HW-O6, fixes as gates, the re-sweep, the anchors flipped. No feature RTL | D1, D11, D13 | +| **S** (submission prep, docs only) | v3.1.0, then kept current through to H | SUB-1 (a dated `ref-docs/` record of the live contribution page; it changed on 2026-09-26), SUB-2 (re-scope the checklist), SUB-5 (refresh `submission-case.md`); TL-5 (this file, done) | D10, D14 (the RTL keeps its long comments) | | **F1** (cheap breadth) | v3.2.0 | FB-16 options first (custom palette, +8 sprites), FB-2 SUROM/SXROM, the 206 family, 66, 11, 79, 9/10, 118/119, 71/232, 34, the trivial discretes, FB-10 paddle, FB-8 Four Score, FB-6 cheats | D12, D26 | | **F2** (the memory platform) | v3.3.0 | FB-20 arbiter (RTL-9 closes), DDR3, FB-4 save states, FB-5 rewind, the real `hps_io` under Verilator; **the off-die build becomes the headline** and on-die a "lite" build | D4, D12, D16 | | **F3** (big boards, audio) | v3.4.0-v3.5.0 | MMC2/4, FME-7/5B, VRC2/4, the Zapper (v3.4.0); MMC5, N163, VRC6, VRC7, Bandai FCG with expansion audio, Famicom peripherals (v3.5.0) | D15 | | **F4** (region and media) | v3.6.0-v3.8.0 | PAL/Dendy with VMODE (v3.6.0); FDS (v3.7.0); NSF, Vs. System, band-limited audio (v3.8.0) | D15 | +| **Freeze** | v3.9.0 | the RTL feature freeze, the parity re-measure, the release-candidate pair the board session runs on | D29 | +| **H** (hardware) | the last v3.9.x (D29), numbered after the session, no later than v4.0.0 | HW-0, Strands A-F on the SuperStation One on the frozen pair, HW-O6, fixes as gates, the re-sweep, the anchors flipped. No feature RTL | D1, D11, D13, D29 | | **Parity** | v4.0.0 | save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, the licensed-library mapper list, the re-measured incumbent | D3 | | **After** | v4.x | the SuperStation One distribution channel, then an openFPGA (Analogue Pocket) port | D17 | Open RTL items slot into the start of any release: RTL-1, RTL-5 and RTL-10 at v3.1.0. RTL-2 and RTL-4 are investigations. RTL-3 (OAM corruption) and RTL-6 -(APU power-on phase) wait on the board. +(APU power-on phase) wait on the board, which since D29 means the end of v3.9.x. + +**Risk of D29, for this half:** features F1-F4 land verified in simulation +only, and HW-A8/HW-A9 (the FPGA device and SDRAM part) are read last. Write each +feature's `bringup-log.md` rows as it lands, so the session's checklist is ready +rather than assembled at the end. The full list: +[Risks of D29](../plans/v3.1-to-v4.0-line-plan.md#risks-of-d29). ### Scope, re-decided 2026-10-07 @@ -62,6 +71,10 @@ recorded ladder run. That is the frozen worktree until the self-hosted runner exists, and both afterwards. CI's nine rung-1 gates are a subset and never stand in for the ladder. Rules 2-7 below are unchanged. +**Rung 6 is the exception, by D29.** Rung 6 (hardware) stays open while the +feature phases above it proceed: they need a green ladder, not a board. It +closes at the hardware release, at the end of v3.9.x. + --- ## History: the v2.5.1 → v3.0.0 plan diff --git a/to-dos/mister/SPRINT_PLAN.md b/to-dos/mister/SPRINT_PLAN.md index ed10ac12..64e29f53 100644 --- a/to-dos/mister/SPRINT_PLAN.md +++ b/to-dos/mister/SPRINT_PLAN.md @@ -3,14 +3,17 @@ ## v3.1 → v4.0 (current, 2026-10-07) One release per phase step. The release plans in `to-dos/plans/` carry the -gates per row. Decisions D1-D28 are tabled in +gates per row. Decisions D1-D29 are tabled in [`v3.1-to-v4.0-line-plan.md`](../plans/v3.1-to-v4.0-line-plan.md). +**D29 (maintainer, 2026-10-07, later the same day) moved the hardware sprints +to the end.** M16 (the board) and M20 (the contribution decision) keep their +IDs, which other documents cite, but now run after M29's freeze and before M30. +The rows are in execution order. + | Sprint | Release | Deliverable | Gate | Status | |---|---|---|---|---| | M21 | v3.1.0 | RTL-1, RTL-5, RTL-10; TL-1; the self-hosted ladder runner (D16); Phase S documents (SUB-1, SUB-2, SUB-5) | Each RTL item measured per cycle; the runner runs one PR's ladder end to end; `contribution_checklist_audit.rs` green | — | -| M16 | HW (numbered after the session, D1) | **Hardware bring-up on the SuperStation One** (D11): Strands A-F, HW-O6, the fixes as gates, the anchors flipped | Every `bringup-log.md` row PASS against the console's md5; the ladder green on the tagged commit. **Rung 6 closes** | **Waiting on the session**; no longer blocked on a second board | -| M20 | after HW | The contribution decision (D10): the feature delta, the incumbent number, then public repository and submission yes or no | The maintainer's decision recorded | — | | M22 | v3.2.0 | Phase F1: options, SUROM/SXROM, about ten cheap families, paddle, Four Score, cheats | Per family: bus, checkpoint and commercial-frame gates, a caught mutant; one seed sweep | — | | M23 | v3.3.0 | Phase F2: the arbiter, DDR3, save states, rewind, the real `hps_io`; off-die becomes the headline (D4) | Save states oracle-gated by a round trip; no SDRAM request over budget | — | | M24 | v3.4.0 | Phase F3a: MMC2/4, FME-7/5B, VRC2/4, the Zapper | As M22, plus audio gates | — | @@ -18,14 +21,22 @@ gates per row. Decisions D1-D28 are tabled in | M26 | v3.6.0 | Phase F4a: PAL and Dendy, VMODE | PAL goldens gated | — | | M27 | v3.7.0 | Phase F4b: FDS with its audio | FDS goldens gated; a disk write round-tripped through the HPS | — | | M28 | v3.8.0 | Phase F4c: NSF, Vs. System, band-limited audio | Each gated against the oracle | — | -| M29 | v3.9.x | The parity re-measure; the RC pair | Two clean compiles of each build byte-identical | — | +| M29 | v3.9.0 | **The RTL feature freeze** (D29); the parity re-measure; the RC pair the board session runs on | Two clean compiles of each build byte-identical; the freeze recorded | — | +| M16 | HW, the last v3.9.x (D1, D29) | **Hardware bring-up on the SuperStation One** (D11) on M29's frozen pair: Strands A-F, HW-O6, the fixes as gates, the anchors flipped | Every `bringup-log.md` row PASS against the console's md5; the ladder green on the tagged commit. **Rung 6 closes** | Waits for M29; no longer blocked on a second board | +| M20 | after HW | The contribution decision (D10): the feature delta, the incumbent number, then public repository and submission yes or no | The maintainer's decision recorded | — | | M30 | v4.0.0 | **Feature parity** (D3) | The parity table complete, with the re-measured incumbent | — | ### Re-planning triggers -- **HW-A8 says the device is the 5CSXFC6D6F31.** Stop feature work. A second - Quartus target and sweep is an L-sized re-target. The hardware release's - number (D1) is chosen with that cost known. +- **HW-A8 says the device is the 5CSXFC6D6F31.** Since D29 this is read at the + end, on the parity core, so the re-target covers every feature built by then: + a second Quartus target, its sweep, and Strands B-F again. The hardware + release's number (D1) is chosen with that cost known; it may fold into + v4.0.0. An optional read-only reading of the marking earlier is open for the + maintainer. +- **A board result contradicts the oracle (D13), late.** Each case changes the + oracle in the same release with an epoch rise; several at once may justify a + patch of their own before v4.0.0. - **On-die M10K passes about 95%.** Move that release's memory-hungry family to off-die only, rather than shrinking something that already works. - **A Provenance-headered family will not yield to rungs 1-3.** Escalate to an diff --git a/to-dos/plans/v3.1-to-v4.0-line-plan.md b/to-dos/plans/v3.1-to-v4.0-line-plan.md index e3e6fb73..f9012237 100644 --- a/to-dos/plans/v3.1-to-v4.0-line-plan.md +++ b/to-dos/plans/v3.1-to-v4.0-line-plan.md @@ -4,7 +4,8 @@ This is the line after v3.0.0 "Cornerstone" and the v3.0.1 "Mortar" patch. It was drafted on 2026-10-07 from three read-only surveys of both repositories and the outside ecosystem, about 110 oracle items and 60 sibling items, and shaped -by 28 maintainer decisions taken the same day (D1-D28, the table at the end). +by 28 maintainer decisions taken the same day (D1-D28, the table at the end), +plus D29, which moved the hardware release to the end of the line. It continues [`v2.9.4-to-v3.0.0-line-plan.md`](v2.9.4-to-v3.0.0-line-plan.md), whose "After v3.0.0" list it schedules. @@ -13,8 +14,16 @@ Each release gets its own plan file. Each file states its gate before work starts, and its outcome table is filled in as the release lands. This file is the line's index. Version numbers here are slots, not promises. A release that grows splits into patches, and the order of the oracle themes can change -without a new decision. Two things are fixed: the hardware release sits -before any feature RTL, and v4.0.0 is the API major (D3). +without a new decision. Two things are fixed: the hardware release is the +last thing before v4.0.0, after every feature has landed (D29), and v4.0.0 is +the API major (D3). + +**D29 (maintainer, 2026-10-07, later the same day) reversed this line's first +ordering.** The first draft put the hardware release right after v3.1.0 and +before any feature RTL. The maintainer moved the whole of it, the board session +and the mobile device run, to the end of v3.9.x, "so as much as possible has +been implemented, integrated, fixed, enhanced, improved and optimized" before +the board sees it. The cost is recorded under [Risks of D29](#risks-of-d29). **Where v3.0.1 leaves things** (verified 2026-10-06/07; `docs/STATUS.md` is the authority): @@ -98,7 +107,6 @@ Unchanged from the v2.7 to v3.0 lines, except where a decision is cited. | --- | --- | --- | --- | | v3.0.1 | (in progress) the open items, the toolchain, the review sweep, and the libretro 1.96 pin test (D5) | odd-frame A12 fix | [`v3.0.1-mortar-plan.md`](v3.0.1-mortar-plan.md) | | **v3.1.0** | AccuracyCoin re-sync, the overclock and sprite-limit options, emphasis, the NEC option, rewind and run-ahead in dual mode, records | small RTL items, the self-hosted runner, submission docs (Phase S) | [`v3.1.0-plan.md`](v3.1.0-plan.md) | -| **HW** | the mobile device run | the board session, Strands A-F (Phase H) | [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md) | | v3.2.0 | mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions | options and cheap mappers (Phase F1) | [`v3.2.0-plan.md`](v3.2.0-plan.md) | | v3.3.0 | phi2 write placement and the sprite-0 stale shifter, the graphics stack | the memory platform: arbiter, DDR3, save states, rewind, headline switch (Phase F2) | [`v3.3.0-plan.md`](v3.3.0-plan.md) | | v3.4.0 | hosted netplay and RetroAchievements: Cloudflare, the RA proxy, hardcore compliance, native 3-4 players | the first big boards: MMC2/4, FME-7/5B, VRC2/4, the Zapper (Phase F3a) | [`v3.4.0-plan.md`](v3.4.0-plan.md) | @@ -106,26 +114,56 @@ Unchanged from the v2.7 to v3.0 lines, except where a decision is cited. | v3.6.0 | libretro: Core Options v2, float audio, the DIP option, console targets; Android API 37 | PAL and Dendy (Phase F4a) | [`v3.6.0-plan.md`](v3.6.0-plan.md) | | v3.7.0 | the mobile extras | FDS with its audio (Phase F4b) | [`v3.7.0-plan.md`](v3.7.0-plan.md) | | v3.8.0 | the remaining accuracy residuals, Vs. cabinets, the long-tail mappers | NSF, Vs. System, band-limited audio (Phase F4c) | [`v3.8.0-plan.md`](v3.8.0-plan.md) | -| v3.9.x | mobile signing and store listings (D18); the v4.0 trial and re-audit | the parity re-measure and the RC pair | [`v3.9.0-plan.md`](v3.9.0-plan.md) | +| v3.9.0 | signing set up (D18); the v4.0 trial and re-audit | **the RTL feature freeze**, the parity re-measure, the RC pair | [`v3.9.0-plan.md`](v3.9.0-plan.md) | +| **HW** (the last v3.9.x, D1, D29) | the mobile device run, then the store listings (D18) | the board session, Strands A-F (Phase H), on the frozen RC pair | [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md) | | **v4.0.0** | the public enums `#[non_exhaustive]` (the API major) | MiSTer feature parity | [`v4.0.0-plan.md`](v4.0.0-plan.md) | -**The hardware release has no number yet (D1).** It is chosen after the board -session, and it is no later than v4.0.0. - -- If the session is clean, or its fixes are small, it takes the next free minor - after v3.1.0, and every later slot moves up by one. -- If it finds the wrong FPGA device, the cost is an L-sized re-target (HW-A8). - The maintainer then picks the number with the evidence in hand. -- Either way, the hardware release comes **before any feature RTL** (v3.2.0's - sibling half and later). A Strand C correction is an RTL change, which reopens - the seed sweep at that day's build date, and mixing it with feature RTL would - hide which change cost the timing margin. -- Oracle work does not wait for the board: v3.1.0's oracle half and v3.2.0's - can land while it is pending. +**The hardware release has no number yet (D1), and it comes last (D29).** + +- **Position.** It is the last release of the v3.9.x line, after v3.9.0's RTL + feature freeze and release-candidate pair, and immediately before v4.0.0. Its + number is chosen after the session: the next v3.9.x patch if its fixes are + small. If they are large, it folds into v4.0.0, which then carries both. +- **Feature RTL does not wait for it.** The sibling's feature work (Phases F1 + to F4) lands in v3.2.0 to v3.8.0, verified in simulation only, as every + release since the core began has been. +- **It still carries no feature RTL of its own.** The session runs on v3.9.0's + frozen release-candidate pair. A fix the board finds is RTL, so it reopens the + seed sweep at that day's build date; keeping features out of the release keeps + each lost margin attributable to one fix. +- **Oracle work runs to the end.** Only the sibling freezes at v3.9.0. + +### Risks of D29 + +Moving the board to the end trades early evidence for a more complete core under +test. What that costs, written down so it is weighed rather than discovered: + +- **About eight releases of RTL, unverified on silicon.** Every feature from + v3.2.0 to v3.8.0 is proven against the oracle in simulation and nowhere else. + A systematic error that only the board shows (clocking, reset, the HDMI path) + would be found under a much larger core. +- **A wrong device or memory part is found late.** If the SuperStation One's + FPGA is not the 5CSEBA6U23I7 the build targets (HW-A8), or its SDRAM is not + the part the constraints assume (HW-A9), the re-target is L-sized, and it + lands on the parity core rather than on six mapper families. +- **The headline build stays provisional the longest.** Off-die becomes the + headline at v3.3.0 (D4), but its SDRAM constraints stay provisional until + HW-O6. +- **Late board-against-oracle fixes cost the oracle too.** Under D13 each one + changes the oracle in the same release, red first, usually with an + `EMULATION_EPOCH` rise; found at the end, they cluster right before v4.0.0. +- **Mitigation:** + - a simulation gate for every feature, mutation-proven, as now; + - a seed sweep in every release that changes RTL; + - v3.9.0's freeze, so the board sees one build; + - an optional, read-only look at the board's chip markings before then (still + open, below). ### v3.1.0: options, emphasis, and the re-sync -Oracle-heavy. The sibling side is small and has no feature RTL. Detail: +Oracle-heavy. The sibling side is small: RTL items, the runner, the +submission documents, and preparation for Phase F1, which no longer waits for +the board (D29). Detail: [`v3.1.0-plan.md`](v3.1.0-plan.md). - **AccuracyCoin re-sync first** (D25, `T-ACCURACYCOIN-RESYNC-2610`). Re-sync @@ -179,48 +217,6 @@ Oracle-heavy. The sibling side is small and has no feature RTL. Detail: - the epoch fingerprint gate shown to fail on a seeded mutation; - the sibling ladder 0 failed, with no seed sweep unless the RTL changed. -### HW: the hardware-verification release (number chosen after the session, D1) - -Plan: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md). -Strands A-F come from -[`v2.9.x-final-audit-and-hardware-plan.md`](v2.9.x-final-audit-and-hardware-plan.md), -and the bring-up worksheet is the sibling's `docs/bringup-log.md`. - -- **The SuperStation One only** (D11). The DE10-Nano becomes an optional later - row (HW-X3). The two-board checklist box is split, never ticked on one board - (HW-X2). -- **Order:** - 1. HW-0, stage the kit; - 2. Strand A, including HW-A8 (the FPGA marking: 5CSEBA6U23I7 against the - published 5CSXFC6D6F31) and HW-A9 (the SDRAM part); - 3. Strand B on-die; - 4. Strand C (palette, H/V timing, resting audio level, band-limiting); - 5. Strand D (battery, AccuracyCoin as bytes, the incumbent A/B, a soak of 4 - hours or more, the save across a power cycle); - 6. the off-die strand, plus HW-O6, the real SDRAM constraints; - 7. E1/E2; - 8. F1, re-measuring the incumbent on the same silicon. -- **No feature RTL in this release.** Every fix the board finds becomes a gate - first where simulation can reach it. Then re-sweep at the release date, - re-run the failed strand and every later one, and flip the anchors (HW-X1). -- **Board against oracle (D13).** The board wins only when it agrees with - documented hardware behaviour. The RTL and the oracle are fixed in the same - release, red first, with an epoch rise and an ADR 0037 amendment per case. -- **The mobile device run** (MOB-01, the 60 rows of - `docs/mobile-v2.9.3-run-sheet.md`) belongs to this release under ADR 0043 - Decision 2. It can split into its own patch if the phone session and the board - session fall on different days. -- **After it (D10).** Write the feature delta and the re-measured incumbent - number into `submission-case.md`. Then the maintainer decides whether the - sibling goes public and whether to email MiSTer-devel. - -**Gate:** - -- every `bringup-log.md` row PASS against the md5 read off the console; -- the ladder green on the tagged commit; -- AccuracyCoin read back as bytes and diffed; -- every hardware anchor flipped in the same PR. - ### v3.2.0: mappers, both cores Plan: [`v3.2.0-plan.md`](v3.2.0-plan.md). @@ -357,9 +353,10 @@ Plan: [`v3.8.0-plan.md`](v3.8.0-plan.md). first); - MAP-05, the long-tail mappers. - **Sibling, Phase F4c:** FB-14 NSF, FB-15 Vs. System, and FB-17 band-limited - audio informed by HW-C4. + audio, written from documentation and the oracle. It was to be informed by + HW-C4, which now runs after it (D29), so HW-C4 re-checks it at the end. -### v3.9.x: distribution, and v4.0 preparation +### v3.9.x: the freeze, distribution, v4.0 preparation, and the hardware release Plan: [`v3.9.0-plan.md`](v3.9.0-plan.md). @@ -374,10 +371,63 @@ Plan: [`v3.9.0-plan.md`](v3.9.0-plan.md). without monetisation (ADR 0035). - This is the release right before v4.0.0's final development, test and release activities. -- **v4.0 preparation:** +- **v4.0 preparation (v3.9.0):** - trial the enum break on a branch; - re-run the four audit scopes; - - MiSTer: the parity re-measure, then the RC bitstream pair at the RC date. + - MiSTer: **the RTL feature freeze**, the parity re-measure, then the RC + bitstream pair at the RC date. The board session runs on that pair. +- **Then the hardware release (D29)**, the last v3.9.x: the board session and + the mobile device run, described under [the hardware release](#hw-the-hardware-verification-release-number-chosen-after-the-session-d1). + The store listings go in after its device run (ADR 0035 asks for the device + evidence first; D18 places them here). + +### HW: the hardware-verification release (number chosen after the session, D1) + +Plan: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md). +Strands A-F come from +[`v2.9.x-final-audit-and-hardware-plan.md`](v2.9.x-final-audit-and-hardware-plan.md), +and the bring-up worksheet is the sibling's `docs/bringup-log.md`. + +- **Last before v4.0.0 (D29).** It follows v3.9.0's RTL feature freeze and + runs on that release's frozen release-candidate pair: the near-parity core, + on-die and off-die, rather than the six-mapper core of v3.0.x. +- **The SuperStation One only** (D11). The DE10-Nano becomes an optional later + row (HW-X3). The two-board checklist box is split, never ticked on one board + (HW-X2). +- **Order:** + 1. HW-0, stage the kit; + 2. Strand A, including HW-A8 (the FPGA marking: 5CSEBA6U23I7 against the + published 5CSXFC6D6F31) and HW-A9 (the SDRAM part); + 3. Strand B on-die; + 4. Strand C (palette, H/V timing, resting audio level, band-limiting); + 5. Strand D (battery, AccuracyCoin as bytes, the incumbent A/B, a soak of 4 + hours or more, the save across a power cycle); + 6. the off-die strand, plus HW-O6, the real SDRAM constraints; + 7. E1/E2; + 8. F1, re-measuring the incumbent on the same silicon. +- **No feature RTL in this release.** Every fix the board finds becomes a gate + first where simulation can reach it. Then re-sweep at the release date, + re-run the failed strand and every later one, and flip the anchors (HW-X1). +- **Board against oracle (D13).** The board wins only when it agrees with + documented hardware behaviour. The RTL and the oracle are fixed in the same + release, red first, with an epoch rise and an ADR 0037 amendment per case. +- **The mobile device run** (MOB-01, the 60 rows of + `docs/mobile-v2.9.3-run-sheet.md`, extended by every mobile feature landed + since, v3.7.0's extras included) belongs to this release under ADR 0043 + Decision 2. It can split into its own patch if the phone session and the board + session fall on different days. The store listings (D18) are submitted after + it, on the builds it passed. +- **After it (D10).** Write the feature delta and the re-measured incumbent + number into `submission-case.md`; under D29 that delta is nearly the parity + list. Then the maintainer decides whether the + sibling goes public and whether to email MiSTer-devel. + +**Gate:** + +- every `bringup-log.md` row PASS against the md5 read off the console; +- the ladder green on the tagged commit; +- AccuracyCoin read back as bytes and diffed; +- every hardware anchor flipped in the same PR. ### v4.0.0: the API major and MiSTer parity (D3) @@ -393,8 +443,9 @@ Plan: [`v4.0.0-plan.md`](v4.0.0-plan.md). Score; - the mapper families covering the incumbent's licensed-library list; - a re-measured incumbent comparison. -- **The hardware release lands by v4.0.0 at the latest** (D1). If it has not - been numbered earlier, it is this release. +- **v4.0.0 follows the hardware release** (D1, D29), so the parity core it + ships is the one the board verified. If the session's fixes were large, the + hardware release folds into v4.0.0 and this release carries both. - **MOB-04 is not a MAJOR on its own.** `rustynes-mobile` is internal under `VERSION-PLAN.md`'s definition of public API, so the bridge API change can land in any minor. @@ -404,7 +455,8 @@ Plan: [`v4.0.0-plan.md`](v4.0.0-plan.md). - **The SuperStation One distribution channel, then an openFPGA (Analogue Pocket) port** (D17), as v4.x line items. `nes_top.sv` is kept framework-free for the port. -- **The MiSTer-devel submission**, if D10's post-hardware decision says go. +- **The MiSTer-devel submission**, if D10's decision, taken after the + hardware release (now just before v4.0.0, D29), says go. Allow a month or more for review. The repository transfer is one-way, and whether it is still part of the process must be confirmed. - **winit 0.31**, once egui adopts it (not before Q1 2027, an inference). @@ -427,9 +479,9 @@ Taken 2026-10-07 unless marked. Each binds every plan in this line. | D7 | The vendored TriCNES source moves out of the repository after a licence check (MIT, verified 2026-10-07 upstream and locally); it stays available, attributed, as reference for AccuracyCoin troubleshooting | | D8 | v3.0.1's bitstreams ship at build date 261007 even if the merge is later | | D9 | Cleanup authorised: the v3.0.1 sweep branches, PR #584 closed after the merge, the release, review and pin-test branches deleted after the merges | -| D10 | MiSTer submission: prepare the documents alongside the board work; decide the public repository and the submission after the hardware release and the incumbent re-measure | +| D10 | MiSTer submission: prepare the documents alongside the board work; decide the public repository and the submission after the hardware release and the incumbent re-measure (which D29 places just before v4.0.0) | | D11 | Verification on the SuperStation One only; the DE10-Nano is an optional later row | -| D12 | After the board: options and cheap mappers (v3.2.0), then save states (v3.3.0), then PAL/Dendy and FDS | +| D12 | Feature order: options and cheap mappers (v3.2.0), then save states (v3.3.0), then PAL/Dendy and FDS. Originally "after the board"; since D29 the order stands and no longer waits for it | | D13 | When the board and the oracle disagree, the board wins where it agrees with documented hardware; RTL and oracle are fixed in the same release, red first, with an epoch rise and an ADR 0037 amendment | | D14 | The RTL keeps its long comments | | D15 | Provenance-headered families: rungs 1-3 by default; rung 4 needs an ADR 0037 amendment naming the maintainer, per family | @@ -446,6 +498,7 @@ Taken 2026-10-07 unless marked. Each binds every plan in this line. | D26 | Mapper scope ranked by real titles, in both repositories | | D27 | Lift ADR 0032's rewind and run-ahead exclusions in dual mode | | D28 | Investigate console targets (Switch, PS Vita, 3DS) for the libretro core | +| D29 | The hardware release, the board session and the mobile device run, moves to the end of v3.9.x, after v3.9.0's RTL freeze and immediately before v4.0.0, so the board verifies the most complete core (later the same day; supersedes the "before any feature RTL" ordering of D1's first reading) | **Still open** (asked at the release that needs them): @@ -454,6 +507,7 @@ Taken 2026-10-07 unless marked. Each binds every plan in this line. | each release | the codename, and merge authorisation | | HW | the hardware release's number (D1), with the session's evidence in hand | | HW | whether the sibling repository goes public, and whether to submit to MiSTer-devel (D10) | +| any time before HW | an optional, read-only identification of the SuperStation One's FPGA marking (HW-A8) and SDRAM part (HW-A9), with no bitstream loaded, as insurance against an L-sized re-target found at the end (D29's main risk). Not planned; the maintainer's call | | v3.2.0 | whether 157 (Datach) is in scope: it needs a barcode-reader input device | | v3.4.0 | the netplay and RA proxy domain, and who operates it (D19 sets the provider, not the name) | | v3.6.0 | which console targets go forward, after D28's investigation | diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md index 41ac7704..12054357 100644 --- a/to-dos/plans/v3.1.0-plan.md +++ b/to-dos/plans/v3.1.0-plan.md @@ -9,9 +9,10 @@ maintainer's, asked at the cut, and the file is renamed the tree when the release begins, because v3.0.1 may still move some of them. **The release is mostly oracle work.** The sibling side carries small RTL items -and documents, and no feature RTL, because feature RTL waits for the hardware -release (D1, D12). If the board session happens during this release, it is its -own release, not part of this one. +and documents. Feature RTL starts in v3.2.0: it no longer waits for the +hardware release, which moved to the end of v3.9.x (D29, 2026-10-07). The +sibling can prepare Phase F1 here (the options surface, the mapper batch's +stimuli). ## Where it starts @@ -53,7 +54,8 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. **Not in this release:** -- feature RTL (it waits for the hardware release); +- feature RTL (it starts in v3.2.0; until D29 it waited for the hardware + release); - ACC-03 and ACC-04 (v3.3.0, one state break together); - mapper breadth (v3.2.0); - hosting (v3.4.0). diff --git a/to-dos/plans/v3.2.0-plan.md b/to-dos/plans/v3.2.0-plan.md index cee8cbae..e02f8ba5 100644 --- a/to-dos/plans/v3.2.0-plan.md +++ b/to-dos/plans/v3.2.0-plan.md @@ -2,9 +2,10 @@ # v3.2.0: mappers in both cores (MiSTer Phase F1) A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on -2026-10-07. **The sibling half cannot start until the hardware release has -shipped** (D1, D12). If the hardware release takes this number, this plan -becomes the next minor's. The codename is asked at the cut. +2026-10-07. The sibling half starts here: the hardware release moved to the +end of v3.9.x (D29), so Phase F1 no longer waits for it, and its RTL is verified +in simulation until then. (The first draft said the sibling half could not start +until the hardware release shipped.) The codename is asked at the cut. ## Scope and gates diff --git a/to-dos/plans/v3.4.0-plan.md b/to-dos/plans/v3.4.0-plan.md index 71ecc0f5..492170c2 100644 --- a/to-dos/plans/v3.4.0-plan.md +++ b/to-dos/plans/v3.4.0-plan.md @@ -26,7 +26,7 @@ open-decisions table). | 6 | MMC2/MMC4 (9/10): the CHR latch on a PPU fetch; *Punch-Out!!* | Bus and checkpoint gates, a commercial-frame gate, a caught mutant | | 7 | Sunsoft FME-7 with 5B audio (69). The oracle file is Provenance-headered, so the RTL comes from nesdev with the oracle as a black-box comparator (D15) | Same, plus an audio gate | | 8 | Konami VRC2/VRC4 (21/22/23/25) | Same as row 6 | -| 9 | FB-9 Zapper (`T-MISTER-ZAPPER`) | An exporter stimulus with a gate; the feel is checked on the board | +| 9 | FB-9 Zapper (`T-MISTER-ZAPPER`) | An exporter stimulus with a gate; the feel is checked on the board at the hardware release (end of v3.9.x, D29) | ## Sprints diff --git a/to-dos/plans/v3.8.0-plan.md b/to-dos/plans/v3.8.0-plan.md index 35b04a72..935f20c0 100644 --- a/to-dos/plans/v3.8.0-plan.md +++ b/to-dos/plans/v3.8.0-plan.md @@ -23,7 +23,7 @@ stays open with the reason recorded. | --- | --- | --- | | 6 | FB-14 NSF player | The oracle's NSF output gated | | 7 | FB-15 Vs. System: palettes and a DIP OSD | Vs. goldens gated | -| 8 | FB-17 band-limited audio, informed by HW-C4's FFT. `blip.rs` is Provenance-headered (D15), so the RTL is written from documentation | An audio gate against the oracle's band-limited output | +| 8 | FB-17 band-limited audio. `blip.rs` is Provenance-headered (D15), so the RTL is written from documentation. HW-C4's FFT now runs after this release (D29), so it re-checks the result at the hardware release rather than informing it | An audio gate against the oracle's band-limited output | ## Sprints diff --git a/to-dos/plans/v3.9.0-plan.md b/to-dos/plans/v3.9.0-plan.md index efef930c..33222986 100644 --- a/to-dos/plans/v3.9.0-plan.md +++ b/to-dos/plans/v3.9.0-plan.md @@ -1,36 +1,59 @@ -# v3.9.x: distribution, and the v4.0.0 preparation +# v3.9.x: the freeze, distribution, the v4.0.0 preparation, and the hardware release A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on -2026-10-07. Mobile signing and the store listings come here, right before -v4.0.0's final development, test and release activities (D18). Like v2.9.x -before v3.0.0, this line may take several patches. The codename is asked at -the cut. +2026-10-07. Like v2.9.x before v3.0.0, this line takes several releases. The +codenames are asked at each cut. -## Scope and gates +**Its shape changed the day it was drafted (D29, maintainer, 2026-10-07).** The +hardware release, the board session and the mobile device run, moved here from +right after v3.1.0, so the board verifies the most complete core. The line is +now, in order: -**Mobile distribution** (D18, `T-MOBILE-DISTRIBUTION`; free and without -monetisation, ADR 0035). +1. **v3.9.0**: the sibling's RTL feature freeze, the v4.0.0 preparation, the + release-candidate pair, and the signing set-up (D18); +2. **the hardware release**, the last v3.9.x: the board session on that pair, + the mobile device run, then the store listings. Its number is chosen after + the session (D1); if its fixes are large it folds into v4.0.0. + +Its risks are in the line plan's +[Risks of D29](v3.1-to-v4.0-line-plan.md#risks-of-d29). + +## v3.9.0: scope and gates + +**The freeze and v4.0.0 preparation.** + +| # | Item | Gate | +| --- | --- | --- | +| 1 | **The sibling's RTL feature freeze.** No feature RTL after this release; the hardware release takes board fixes only | The freeze recorded in the sibling's `docs/bitstream-release.md` and `TASKS.md`; the feature list matches the parity re-measure (row 4) | +| 2 | Trial `T-API-ENUMS` on a branch: every remaining public enum `#[non_exhaustive]`, counted at the time | The workspace, every feature combination and both wasm builds compile; the downstream match sites listed for v4.0.0's notes | +| 3 | Re-run the four audit scopes against the tree, as v2.9.0 and v2.9.9 did | Each new finding fixed red first or dispositioned in its ledger | +| 4 | MiSTer: the parity re-measure against the incumbent's public feature list, then the release-candidate bitstream pair at the RC date. **The board session runs on this pair** | The feature delta written honestly; two clean compiles of each build byte-identical; the pair archived outside `/tmp` | + +**Mobile distribution, the set-up half** (D18, `T-MOBILE-DISTRIBUTION`; free +and without monetisation, ADR 0035). | # | Item | Gate | | --- | --- | --- | -| 1 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" | -| 2 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process | -| 3 | **The listings:** Google Play (a new personal account needs 12 testers for 14 days first), F-Droid or IzzyOnDroid (the `foss` flavour, reproducible from `Cargo.lock`, IzzyOnDroid's 30 MB APK limit checked), and the App Store under guideline 4.7 | Each listing live, or the blocker recorded | -| 4 | The mobile device run re-done on the release builds, if the hardware release's run is older than the bridge changes since | The run sheet's rows PASS on the shipped builds | +| 5 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" | +| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process | + +## The hardware release (the last v3.9.x): scope and gates -**v4.0.0 preparation.** +Detail: [`v3.x-hardware-verification-plan.md`](v3.x-hardware-verification-plan.md). | # | Item | Gate | | --- | --- | --- | -| 5 | Trial `T-API-ENUMS` on a branch: every remaining public enum `#[non_exhaustive]`, counted at the time | The workspace, every feature combination and both wasm builds compile; the downstream match sites listed for v4.0.0's notes | -| 6 | Re-run the four audit scopes against the tree, as v2.9.0 and v2.9.9 did | Each new finding fixed red first or dispositioned in its ledger | -| 7 | MiSTer: the parity re-measure against the incumbent's public feature list, then the release-candidate bitstream pair at the RC date | The feature delta written honestly; two clean compiles of each build byte-identical | +| 7 | **The board session** on v3.9.0's pair: HW-0, Strands A-F, HW-O6, every fix a gate first where simulation reaches it, the re-sweep at the release date, the anchors flipped | Every `bringup-log.md` row PASS against the md5 read off the console; the ladder green on the tagged commit | +| 8 | **The mobile device run** (MOB-01): the run sheet, extended by every mobile feature landed since v2.9.3, on the release builds | The run sheet's rows PASS on the builds that ship | +| 9 | **The listings**, after row 8 and on the builds it passed: Google Play (a new personal account needs 12 testers for 14 days first), F-Droid or IzzyOnDroid (the `foss` flavour, reproducible from `Cargo.lock`, IzzyOnDroid's 30 MB APK limit checked), and the App Store under guideline 4.7 | Each listing live, or the blocker recorded | +| 10 | D10: the feature delta and the re-measured incumbent number in `submission-case.md`, for the maintainer's public-repository and submission decision | The document updated; the decision asked | ## Sprints -| Sprint | Deliverable | Gate | Status | -| --- | --- | --- | --- | -| 3.9-S1 | Rows 1-2 | rows 1-2 | — | -| 3.9-S2 | Rows 3-4 | rows 3-4 | — | -| 3.9-S3 | Rows 5-7 | rows 5-7 | — | +| Sprint | Release | Deliverable | Gate | Status | +| --- | --- | --- | --- | --- | +| 3.9-S1 | v3.9.0 | Rows 1-4 | rows 1-4 | — | +| 3.9-S2 | v3.9.0 | Rows 5-6 | rows 5-6 | — | +| 3.9-S3 | HW | Rows 7-8 | rows 7-8 | — | +| 3.9-S4 | HW | Rows 9-10 | rows 9-10 | — | diff --git a/to-dos/plans/v3.x-hardware-verification-plan.md b/to-dos/plans/v3.x-hardware-verification-plan.md index 9e0e2ad3..4a21752d 100644 --- a/to-dos/plans/v3.x-hardware-verification-plan.md +++ b/to-dos/plans/v3.x-hardware-verification-plan.md @@ -14,9 +14,15 @@ Decision 2). - **The number (D1).** The number is chosen *after* the board session, with its evidence in hand, and it is no later than v4.0.0. In [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md) this release is - the slot "HW". It sits after v3.1.0 and **before any feature RTL**: a - Strand C correction is an RTL change that reopens the seed sweep at that - day's build date, so it must not share a release with feature RTL. + the slot "HW". +- **Its position (D29, later the same day).** It is the **last release of + v3.9.x**, immediately before v4.0.0, after v3.9.0's RTL feature freeze, so + the board verifies the most complete core. The first reading of D1 put it + after v3.1.0 and before any feature RTL; the maintainer reversed that. It + still carries no feature RTL of its own: a Strand C correction is an RTL + change that reopens the seed sweep at that day's build date, so it must not + share a release with feature RTL. If its fixes are large, it folds into + v4.0.0 (D1). - **One board (D11).** Verification is on the SuperStation One only. The DE10-Nano is an optional later row (HW-X3), done only if a board turns up. The two-board box in the contribution checklist is split rather than @@ -41,11 +47,29 @@ Decision 2). ## The deliverable -| Artefact | Status at v3.0.0 | +Written for v3.0.0 and restated for D29's position. By the time this release +runs, the off-die build has been the headline since v3.3.0 (D4), and both builds +carry the v3.2.0-v3.8.0 features. + +| Artefact | Status at the hardware release | | --- | --- | -| **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The headline.** Every bring-up strand A–F passed on the SuperStation One | -| **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **Secondary, labelled experimental.** MemTest and its own Strand B rows passed | -| Oracle and sibling | Tagged v3.0.0 together | +| **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **The headline since v3.3.0 (D4).** MemTest, its own Strand B rows and HW-O6's real SDRAM constraints passed | +| **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The "lite" build.** Every bring-up strand A–F that applies to it passed on the SuperStation One | +| Oracle and sibling | Tagged together, at the hardware release's number | + +## Risks of D29 + +The board sees the core only at the end. The line plan's +[Risks of D29](v3.1-to-v4.0-line-plan.md#risks-of-d29) carries the full list; +the two this plan owns are: + +- **HW-A8 and HW-A9 come last.** A wrong FPGA device or SDRAM part re-targets + the parity core, not the six-mapper core, and the sweep and every strand + repeat. An optional read-only look at the chip markings before then, with no + bitstream loaded, is the cheap insurance; it is listed as an open decision. +- **More to verify.** Strand B grows with every feature: save states, PAL and + Dendy, FDS, the expansion audio and the new mapper families each need rows in + `bringup-log.md`, written as each lands rather than at the session. ## The gate @@ -89,9 +113,10 @@ Every fix the board finds becomes a gate first, where simulation can reach it. Then re-sweep at the release's build date, re-run the failed strand and every later one, and flip the anchors (HW-X1). -**The mobile device run** (MOB-01, `docs/mobile-v2.9.3-run-sheet.md`) belongs to -this release under ADR 0043 Decision 2. It may become its own patch if it falls -on a different day from the board session. +**The mobile device run** (MOB-01, `docs/mobile-v2.9.3-run-sheet.md`, extended +by every mobile feature landed since) belongs to this release under ADR 0043 +Decision 2. It may become its own patch if it falls on a different day from the +board session. The store listings (D18) follow it, on the builds it passed. ## What changes in the same PR @@ -113,11 +138,12 @@ on a different day from the board session. maintainer 2026-10-05). The sibling release carries both, plus the datecoded copy, via `scripts/release-rbf.sh`. -## Deferred past v3.0.0 +## Deferred past v3.0.0, and now landed before this release FDS, expansion audio, save states, cheats, Vs. System, NSF, Zapper, Four Score, paddle, keyboard and PAL on the FPGA; the remaining mapper families; the -DE10-Nano second board. +DE10-Nano second board. Under D29 everything in that list except the DE10-Nano +lands in v3.2.0-v3.8.0, before this release, and is verified on the board here. Scheduled on 2026-10-07: diff --git a/to-dos/plans/v4.0.0-plan.md b/to-dos/plans/v4.0.0-plan.md index e38c4add..463204ac 100644 --- a/to-dos/plans/v4.0.0-plan.md +++ b/to-dos/plans/v4.0.0-plan.md @@ -20,8 +20,10 @@ Format breaks no longer need one. Making them all non-exhaustive at once means later variants (new options, new input-event kinds, new error cases) are additive. This is the enum half of v3.0.0's `T-API-EXTENSIBLE`, and the only item found that must be a MAJOR. -- **The hardware release** carries the new-deliverable-class trigger. If it has - not been numbered by now, it is this release (D1). +- **v4.0.0 follows the hardware release** (D29, 2026-10-07). That release is + the last v3.9.x and carries the new-deliverable-class trigger; if its fixes + were large it folds into this one, which then carries both (D1). Either way + the parity core v4.0.0 ships is the one the board verified. ## Scope and gates @@ -30,8 +32,8 @@ Format breaks no longer need one. | 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums | | 2 | Release notes restating every break since v3.0.0: the format bumps of v3.1.0, v3.3.0 and v3.5.0, every epoch rise, the enums | Built from the CHANGELOG's entries and every `!` commit and `BREAKING CHANGE:` footer since v3.0.0, so none is missed | | 3 | MiSTer parity: save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and mapper families covering the incumbent's licensed-library list | Each feature gated in the ladder; the parity table in `submission-case.md` with the re-measured incumbent number | -| 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4) swept at the release date | Two clean compiles of each byte-identical; md5s verified by download from both releases | -| 5 | The hardware release, if it is this one (D1) | The hardware plan's gate | +| 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4). If the RTL is unchanged since the hardware release, ship the pair the board verified, byte for byte; a recompile at a new date is a different bitstream from the one tested. If the RTL changed, sweep at the release date and say the result is unverified on the board | Two clean compiles of each byte-identical (or the verified pair's md5s); md5s verified by download from both releases | +| 5 | The hardware release, only if it folded into this one (D1, D29) | The hardware plan's gate | ## Sprints From 620c073bb23124c6b5125ce7a5a5a05fff029c2f Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 00:16:59 -0400 Subject: [PATCH 35/44] docs(agents): worktree-isolated agents cannot run git here Two roadmap forks on 2026-10-07 hit it. Both isolated worktrees were created from main, not from the session's release branch. In the second, every git command was refused: the RTK hook rewrites git to `rtk git`, and the worktree-isolation guard refuses rtk with git among its operands. It made no change. The workaround that worked was a main-tree fork with explicit-path staging and no session commits while it ran (2fa7e2cb). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- docs/agents/tooling-traps.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/agents/tooling-traps.md b/docs/agents/tooling-traps.md index 2bb26354..cd6a2c78 100644 --- a/docs/agents/tooling-traps.md +++ b/docs/agents/tooling-traps.md @@ -31,3 +31,4 @@ - **`set -- $var` DOES NOT WORD-SPLIT IN zsh EITHER (v2.9.8).** Same root cause as the `for x in $var` entry above: a loop `for rb in "repo branch" ...; do set -- $rb; gh api -X DELETE .../$1/.../$2` passed the whole string as `$1` and got three HTTP 404s. Call the command once per item with literal arguments. - **A 1PASSWORD OUTAGE BLOCKS CHERRY-PICKS, NOT ONLY COMMITS (v2.9.8).** Integrating an agent's branch re-signs each picked commit, so the whole integration stops until 1Password is back. Nothing is lost: the agent's commits stay on its branch, and staged work stays staged. Ask the maintainer to unlock it; never pass `--no-gpg-sign`. - **PARALLEL WORKTREE AGENTS NEED THE TIP'S MOVES TOLD TO THEM (v2.9.8).** Five agents worked from one base while their siblings' commits landed. Each time the branch moved (a rename, a removed API), the still-running agents were sent the new tip and the breaking facts and told to rebase before their final commit. Integration then needed only CHANGELOG and ADR conflict resolution. To relieve a loaded host, an agent can be paused at a safe point: let its current cargo job finish, write a status file, end its turn, and resume it later by message. +- **A WORKTREE-ISOLATED AGENT CANNOT RUN GIT HERE, AND ITS WORKTREE STARTS FROM `main` (v3.0.1).** Two forks were given `isolation: worktree` on 2026-10-07. Both worktrees were created from `main` (`9c23715b`), not from the release branch the session was on. The first agent could still run git and reset its own clean worktree to the right commit. The second could not run git at all: the RTK hook rewrites every `git` into `rtk git`, and the worktree-isolation guard refuses a command that "runs rtk with a git command among its operands" (plain `git`, `git -C`, `command git` and `rtk git` were all refused). It made no change, and the harness removed the unchanged worktree itself. For documents-only work, run the fork in the main tree instead, stage by explicit path, and make no commits from the session until it reports. Otherwise, check the agent's first `git log` before trusting anything it writes. From 31758270185c80f5eb7d986e04a1d0b8eba14276 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 00:38:46 -0400 Subject: [PATCH 36/44] build(toolchain): move the libretro buildbot to Rust 1.99 too v3.0.1 had split the toolchain. The workspace moved to 1.99.0, while the libretro buildbot stayed on 1.96.0 (`RUSTUP_TOOLCHAIN` in .gitlab-ci.yml), because its build image injected `-Car=,Clink-arg=...` into every Apple job and `-C ar` is a hard error from Rust 1.97. The seven crates that core compiles kept `rust-version = "1.96"`, and libretro-cross built them on 1.96. That premise had already expired. The v3.1 roadmap research found that libretro-infrastructure/libretro-build-rust removed the flag on 2026-09-03 (841f3619, "Remove -C usage as no longer supported by rust"); the rebuilt image's own pipeline passed on 2026-09-23. libretro's mirror builds every pushed branch, so this was tested before merge rather than after. test/libretro-rust-1.99 (771fdb82) changed only `RUSTUP_TOOLCHAIN` to "1.99.0". Its pipeline 119614 passed all 15 jobs, including osx-x64, osx-arm64, ios-arm64 and tvos-arm64, the four `-C ar` used to break. Maintainer decision D5 (2026-10-07): test it in v3.0.1, and drop the pin if everything is green. Changes: - .gitlab-ci.yml `.core-defs`: `RUSTUP_TOOLCHAIN: "1.99.0"`, the exact configuration 119614 tested. The variable stays because every job's `rustup toolchain install ${RUSTUP_TOOLCHAIN}` needs a name. The tvOS comment no longer calls `-C ar` a live hazard. - The seven crates (cpu, ppu, apu, mappers, core, gamedb, libretro) now use `rust-version.workspace = true` (1.99); cargo metadata confirms it. - ci.yml libretro-cross: instead of building on an older toolchain, it now FAILS if `.gitlab-ci.yml`'s RUSTUP_TOOLCHAIN differs from rust-toolchain.toml's `channel`. Both are parsed table-scoped and fail-closed. The step was extracted from the workflow YAML and run against copies: match -> rc 0, channel 1.100.0 -> rc 1 with the error, no [toolchain] channel -> rc 1. A pin move that forgets the buildbot is now caught on the PR, not on git.libretro.com after merge. - rust-toolchain.toml keeps the two-line RUSTFLAGS fix in case a future image reintroduces `-C ar`. It adds the trap the research flagged: the image now separates its `-C link-arg` flags properly, so those link arguments reach the linker for the first time. They used to be swallowed as the `ar` value. 119614 links fine, but this is the first suspect if an Apple link ever changes behaviour. - mmc3_boards.rs `mirror_bank`: three `size & size.wrapping_neg()` become `size.isolate_lowest_one()`. This is an exact rewrite (lowest set bit; `size` is never 0 because `count > 0`). Clippy's `manual_isolate_lowest_one` is MSRV-gated, so it fired only once the crate's rust-version reached 1.99. Trap worth knowing: a workspace `cargo clippy` run straight after the rust-version edit reported nothing, and only the `-p rustynes-libretro` run (a separate feature unification, so a fresh lint pass) flagged it. Clippy's cached results evidently do not key on rust-version; that is a hypothesis not tested further, and a fresh CI cache lints it either way. - Docs: AGENTS.md toolchain paragraph, docs/agents/ci-and-release.md and libretro.md (a new bullet, "AND v3.0.1 UNDID THE SPLIT"), the canary header, CHANGELOG ("Rust 1.99, everywhere"), and the roadmap records: the line plan, v3.0.1 and v3.1.0 plans, libretro SPRINT_PLAN L1 (done), VERSION-PLAN and T-LIBRETRO-TOOLCHAIN (closes on the first green `main` pipeline after merge). Verified on this tree: - clippy -D warnings for the workspace, the scripting, scripting+hd-pack, retroachievements and full frontend sets, both wasm32 sets, and -p rustynes-libretro; - rustdoc -D warnings, fmt, and the no_std thumbv7em build; - mmc3_boards tests 33/0; - release_anchor 14/0, release_state_prose 16/0, contribution_checklist 9/0; - markdownlint, actionlint and check-toml. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .github/workflows/ci.yml | 41 ++++++++++++++-------- .github/workflows/toolchain-canary.yml | 3 +- .gitlab-ci.yml | 40 ++++++++++----------- AGENTS.md | 2 +- CHANGELOG.md | 23 ++++++------ VERSION-PLAN.md | 2 +- crates/rustynes-apu/Cargo.toml | 2 +- crates/rustynes-core/Cargo.toml | 2 +- crates/rustynes-cpu/Cargo.toml | 2 +- crates/rustynes-gamedb/Cargo.toml | 2 +- crates/rustynes-libretro/Cargo.toml | 2 +- crates/rustynes-mappers/Cargo.toml | 2 +- crates/rustynes-mappers/src/mmc3_boards.rs | 8 +++-- crates/rustynes-ppu/Cargo.toml | 2 +- docs/agents/ci-and-release.md | 2 +- docs/agents/libretro.md | 1 + rust-toolchain.toml | 32 +++++++---------- to-dos/ROADMAP.md | 2 +- to-dos/libretro/SPRINT_PLAN.md | 2 +- to-dos/plans/v3.0.1-mortar-plan.md | 2 +- to-dos/plans/v3.1-to-v4.0-line-plan.md | 8 ++--- to-dos/plans/v3.1.0-plan.md | 2 +- 22 files changed, 98 insertions(+), 86 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caa29f9d..5e5ac172 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -502,8 +502,8 @@ jobs: # Cross-platform behaviour: full `cargo test` on stable across the three # shipped OSes. The previous ubuntu MSRV *test* entry is dropped — the # `lint` job above already compiles the whole workspace (`clippy - # --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 + # --all-targets`) on the pin, which IS the MSRV compile gate (one floor for + # every crate since v3.0.1; `libretro-cross` builds the buildbot's), and the core's # determinism makes "tests pass on the pin" equivalent to "tests pass on stable". test: name: test (${{ matrix.os }}) @@ -831,17 +831,16 @@ jobs: - uses: actions/checkout@v7 with: persist-credentials: false - # 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. + # This job rehearses the libretro buildbot, so it runs the buildbot's + # toolchain, read from `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN`, and it + # FAILS if that differs from `rust-toolchain.toml`'s `channel`. The two + # must move together: v3.0.1 briefly held the buildbot on 1.96.0 (the + # build image's `-C ar`, a hard error from 1.97) and lifted the hold the + # same release once the image dropped the flag (pipeline 119614, 15/15 + # on 1.99.0). The check keeps a drift from being found on the buildbot + # after merge. `.github/` still carries no version literal + # (`docs/agents/ci-and-release.md`). Both parsers are table-scoped and + # fail-closed, like `rust-setup`'s own resolver. - name: Resolve the buildbot toolchain id: buildbot shell: bash @@ -858,7 +857,21 @@ jobs: echo "::error::No RUSTUP_TOOLCHAIN under .core-defs in .gitlab-ci.yml" exit 1 fi - echo "Buildbot toolchain: $tc" + pin="$(awk ' + /^\[/ { in_tc = ($0 == "[toolchain]"); next } + in_tc && $1 == "channel" { + v = $3; gsub(/"/, "", v); print v; exit + } + ' rust-toolchain.toml)" + if [ -z "$pin" ]; then + echo "::error::No channel under [toolchain] in rust-toolchain.toml" + exit 1 + fi + if [ "$tc" != "$pin" ]; then + echo "::error::.gitlab-ci.yml RUSTUP_TOOLCHAIN ($tc) differs from rust-toolchain.toml channel ($pin); move them together" + exit 1 + fi + echo "Buildbot toolchain: $tc (matches rust-toolchain.toml)" echo "toolchain=$tc" >> "$GITHUB_OUTPUT" echo "RUSTUP_TOOLCHAIN=$tc" >> "$GITHUB_ENV" - uses: ./.github/actions/rust-setup diff --git a/.github/workflows/toolchain-canary.yml b/.github/workflows/toolchain-canary.yml index 8fe9ad52..2792f986 100644 --- a/.github/workflows/toolchain-canary.yml +++ b/.github/workflows/toolchain-canary.yml @@ -4,7 +4,8 @@ name: Toolchain canary # # 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). +# passed `-C ar`, a hard error from 1.97; the image dropped it on 2026-09-03 and +# the buildbot follows the pin again). # 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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46f627eb..1e50ccb2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -70,22 +70,19 @@ # 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 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" + # The toolchain every job here installs and builds with. It MUST equal + # `rust-toolchain.toml`'s `channel`: GitHub CI's `libretro-cross` job + # fails if the two differ. The variable outranks `rust-toolchain.toml`, + # and each job installs exactly this toolchain with its target. + # + # History: v3.0.1 first held this at 1.96.0 while the workspace moved to + # 1.99.0, because the libretro build image injected `-Car=...` into every + # Apple job and `-C ar` is a hard error from Rust 1.97. The image dropped + # that flag on 2026-09-03 (libretro-build-rust `841f3619`, "Remove -C usage + # as no longer supported by rust"), and a test branch built on 1.99.0 + # (pipeline 119614) passed all 15 jobs, the four Apple ones included, so + # the hold was lifted in the same release. + RUSTUP_TOOLCHAIN: "1.99.0" # Shared `before_script` for the jobs whose upstream template defines none # (Windows, Linux, and all four Android). See the post-mortem above. @@ -306,7 +303,7 @@ libretro-build-ios-arm64: # among them) and `cargo check --release -p rustynes-libretro --target # aarch64-apple-tvos` compiles the entire graph, bindgen included. # -# Restoring the shared script puts tvOS on the same pinned 1.96.0 as every +# Restoring the shared script puts tvOS on the same pinned toolchain as every # other job, and dissolves three separate workarounds this job used to need # rather than accumulating a fourth: # @@ -334,7 +331,8 @@ libretro-build-ios-arm64: # long-deprecated no-op became a hard error in Rust 1.97 (bisected: # 1.96.1 warns, 1.97.1 errors). On the pinned 1.96.0 it is still only a # warning — exactly as it already was for the three Apple jobs that -# were green throughout. +# were green throughout. (The image stopped injecting it on 2026-09-03; +# see `.core-defs`.) # # `!reference [.libretro-rust-apple-base, script]` pulls in the generic Apple # build/package steps instead of duplicating them here, so upstream fixes to @@ -342,9 +340,9 @@ libretro-build-ios-arm64: # tvOS template already sets to `aarch64-apple-tvos`, and honours the same # `STRIP_CORE_LIB` / `DSYM_CORE_LIB` flags the template sets. # -# NOTE: `-C ar` is still a latent hazard for ALL FOUR Apple jobs, now -# including this one. Read `rust-toolchain.toml` before bumping `channel` to -# 1.97 or newer. +# NOTE: `-C ar` WAS a latent hazard for all four Apple jobs until the build +# image stopped injecting it (2026-09-03). If a future image brings it back, +# the two-line `before_script` fix is recorded in `rust-toolchain.toml`. libretro-build-tvos-arm64: extends: - .libretro-rust-tvos-arm64-default diff --git a/AGENTS.md b/AGENTS.md index 6ab815dc..4e045121 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,7 +153,7 @@ cargo bench -p rustynes-mappers cargo bench -p rustynes-core ``` -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. +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 follows the same pin**: `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN` must equal `rust-toolchain.toml`'s `channel`, and CI's `libretro-cross` job fails if they differ, so move both in one change. v3.0.1 first held the buildbot on 1.96.0 (its image passed `-C ar`, a hard error from Rust 1.97), then lifted the hold once a test branch proved the image had dropped the flag (pipeline 119614, all 15 jobs on 1.99.0); every crate now inherits the workspace `rust-version`. A pushed branch gets its own buildbot pipeline, so test a toolchain move there before merging (`docs/agents/libretro.md`). 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/CHANGELOG.md b/CHANGELOG.md index 066146ef..84026018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,16 +84,19 @@ 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). +- **Rust 1.99, everywhere.** The pinned toolchain moves from 1.96.0 to 1.99.0, + the newest stable, and the libretro buildbot moves with it. The release first + held the buildbot on 1.96.0, because its build image passed `-C ar`, a hard + error from Rust 1.97; the image turned out to have dropped that flag on + 2026-09-03, and a test branch built on 1.99.0 passed all 15 buildbot jobs, + the four Apple ones included. CI's `libretro-cross` job now fails if + `.gitlab-ci.yml`'s toolchain differs from `rust-toolchain.toml`. While the + split stood, the 1.96 build 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: diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index 82894dc6..eee3d69b 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -73,7 +73,7 @@ day.) | Version | Scope | Plan | |---------|-------|------| -| v3.0.1 "Mortar" | The open items (the palette-offset A/B, T-GA23C-CHRRAM, the MiSTer odd-frame A12 stimulus), every dependency and the Rust 1.99 toolchain, the libretro 1.96 pin test, every unanswered review comment back to #1 | [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md) | +| v3.0.1 "Mortar" | The open items (the palette-offset A/B, T-GA23C-CHRRAM, the MiSTer odd-frame A12 stimulus), every dependency and the Rust 1.99 toolchain, the libretro buildbot on 1.99 too (its 1.96 hold tested and dropped), every unanswered review comment back to #1 | [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md) | | v3.1.0 | The AccuracyCoin re-sync; the CPU overclock and sprite-limit options in movies and netplay; PAL emphasis; opt-in composite artifacts; the NEC MMC3 option; rewind and run-ahead in Vs. dual mode; an epoch fingerprint gate. MiSTer: small RTL items, the self-hosted runner, submission documents | [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md) | | v3.2.0 | Mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions. MiSTer F1: options and about ten cheap families, paddle, Four Score, cheats | [`v3.2.0-plan.md`](to-dos/plans/v3.2.0-plan.md) | | v3.3.0 | Phi2 write placement and the sprite-0 stale shifter; wgpu 31 / egui 0.37. MiSTer F2: the SDRAM arbiter, DDR3, save states, rewind; the off-die build becomes the headline | [`v3.3.0-plan.md`](to-dos/plans/v3.3.0-plan.md) | diff --git a/crates/rustynes-apu/Cargo.toml b/crates/rustynes-apu/Cargo.toml index 2079e459..cdf6b839 100644 --- a/crates/rustynes-apu/Cargo.toml +++ b/crates/rustynes-apu/Cargo.toml @@ -4,7 +4,7 @@ description = "Cycle-accurate Ricoh 2A03 APU (Audio Processing Unit) for the NES version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-core/Cargo.toml b/crates/rustynes-core/Cargo.toml index 644ceebe..83635cd2 100644 --- a/crates/rustynes-core/Cargo.toml +++ b/crates/rustynes-core/Cargo.toml @@ -4,7 +4,7 @@ description = "Cycle-accurate NES emulator core: scheduler, bus, save state, pub version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-cpu/Cargo.toml b/crates/rustynes-cpu/Cargo.toml index d6526195..9e22b5a0 100644 --- a/crates/rustynes-cpu/Cargo.toml +++ b/crates/rustynes-cpu/Cargo.toml @@ -4,7 +4,7 @@ description = "Cycle-accurate Ricoh 2A03 (6502 derivative) CPU core for the NES" version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-gamedb/Cargo.toml b/crates/rustynes-gamedb/Cargo.toml index b14c97f6..fa3c958e 100644 --- a/crates/rustynes-gamedb/Cargo.toml +++ b/crates/rustynes-gamedb/Cargo.toml @@ -12,7 +12,7 @@ name = "rustynes-gamedb" version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-libretro/Cargo.toml b/crates/rustynes-libretro/Cargo.toml index f403b6cc..60fd7170 100644 --- a/crates/rustynes-libretro/Cargo.toml +++ b/crates/rustynes-libretro/Cargo.toml @@ -4,7 +4,7 @@ description = "Cycle-accurate NES emulator libretro core wrapper" version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-mappers/Cargo.toml b/crates/rustynes-mappers/Cargo.toml index 57430cc0..56f8fc2a 100644 --- a/crates/rustynes-mappers/Cargo.toml +++ b/crates/rustynes-mappers/Cargo.toml @@ -4,7 +4,7 @@ description = "Cartridge file format parsing and mapper implementations for the version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/crates/rustynes-mappers/src/mmc3_boards.rs b/crates/rustynes-mappers/src/mmc3_boards.rs index 23432d3d..9d383004 100644 --- a/crates/rustynes-mappers/src/mmc3_boards.rs +++ b/crates/rustynes-mappers/src/mmc3_boards.rs @@ -226,11 +226,13 @@ fn mirror_bank(bank: usize, count: usize) -> usize { let mut bank = bank & (count.next_power_of_two() - 1); while bank >= count { // Find the doubling stage whose copied region holds `bank`. + // `isolate_lowest_one` is `lowbit(size)` (`size & size.wrapping_neg()`, + // spelled that way until v3.0.1 moved these crates to Rust 1.99). let mut size = count; - while size + (size & size.wrapping_neg()) <= bank { - size += size & size.wrapping_neg(); + while size + size.isolate_lowest_one() <= bank { + size += size.isolate_lowest_one(); } - bank -= size & size.wrapping_neg(); + bank -= size.isolate_lowest_one(); } bank } diff --git a/crates/rustynes-ppu/Cargo.toml b/crates/rustynes-ppu/Cargo.toml index 84c7c624..1f3c46b7 100644 --- a/crates/rustynes-ppu/Cargo.toml +++ b/crates/rustynes-ppu/Cargo.toml @@ -4,7 +4,7 @@ description = "Cycle-accurate Ricoh 2C02 PPU (Picture Processing Unit) for the N version.workspace = true edition.workspace = true # The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). -rust-version = "1.96" +rust-version.workspace = true license.workspace = true authors.workspace = true repository.workspace = true diff --git a/docs/agents/ci-and-release.md b/docs/agents/ci-and-release.md index be478e7b..e82c5a75 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.** (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. +- **There is ONE toolchain, `rust-toolchain.toml`'s `channel`, and no version literal anywhere in `.github/` — don't add one.** (The libretro buildbot needs the version written once more, as `.gitlab-ci.yml`'s `RUSTUP_TOOLCHAIN`; `libretro-cross` parses it with the same table-scoped, fail-closed `awk` and fails if it differs from the channel. It briefly held 1.96.0 during v3.0.1.) `.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 a8c35a3f..36f81077 100644 --- a/docs/agents/libretro.md +++ b/docs/agents/libretro.md @@ -21,6 +21,7 @@ - **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; 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. +- **AND v3.0.1 UNDID THE SPLIT, the same day (2026-10-07).** The research for the v3.1 roadmap found that `libretro-infrastructure/libretro-build-rust` removed the `-C` usage from the build image on 2026-09-03 (`841f3619`, "Remove -C usage as no longer supported by rust"; the rebuilt image's own pipeline passed 2026-09-23). A throwaway branch, `test/libretro-rust-1.99`, set only `RUSTUP_TOOLCHAIN: "1.99.0"`; its pipeline 119614 passed all 15 jobs, `osx-x64`, `osx-arm64`, `ios-arm64` and `tvos-arm64` included. So the pin now equals `rust-toolchain.toml` again, the seven crates inherit the workspace `rust-version`, and `libretro-cross` FAILS if the two toolchains differ, so neither can drift alone. Two lessons: the hazard lived in someone else's image, so re-check it there before planning around it; and since the image now separates its `-C link-arg` flags properly, those link arguments reach the linker for the first time. That is harmless on 119614, but it is the first thing to suspect if an Apple link changes behaviour. - **`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/rust-toolchain.toml b/rust-toolchain.toml index 4b4a19c3..7b98107c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -27,23 +27,17 @@ components = ["rustfmt", "clippy"] # is. See the post-mortem comment at the top of `.gitlab-ci.yml`. # # 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. +# 2026-10-06). The libretro buildbot follows it: `.gitlab-ci.yml` sets +# `RUSTUP_TOOLCHAIN` to the same value (CI's `libretro-cross` fails if the two +# differ). For one day it did not. The libretro build image used to inject +# `-Car=,Clink-arg=...` into every Apple job, and `-C ar` became a hard +# error in Rust 1.97 (`error: `-C ar`: this option has been removed`), so the +# buildbot was first held on 1.96.0. The image dropped the flag on 2026-09-03 +# (libretro-build-rust `841f3619`), and a branch built on 1.99.0 passed all 15 +# buildbot jobs (pipeline 119614), so the hold was lifted in v3.0.1. # -# 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 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`: +# If a future image brings `-C ar` back, the fix is two lines in each Apple +# job's `before_script`: # # - unset CARGO_ENCODED_RUSTFLAGS # - export RUSTFLAGS="" @@ -51,8 +45,8 @@ components = ["rustfmt", "clippy"] # Cargo takes extra flags from exactly one source, first match wins # (CARGO_ENCODED_RUSTFLAGS -> RUSTFLAGS -> target..rustflags -> # build.rustflags), so that pair wins without needing to know where the -# image sets them. Discarding them is safe: rustc splits `-C` at the FIRST -# `=`, so the whole comma-joined string is consumed as the `ar` value and -# those link arguments have never reached the linker for any core. +# image sets them. Check first what the image passes: since the 2026-09-03 +# change its `-C link-arg` flags are separated properly and DO reach the +# linker, which they never did while they were swallowed as the `ar` value. targets = ["thumbv7em-none-eabihf", "wasm32-unknown-unknown"] profile = "default" diff --git a/to-dos/ROADMAP.md b/to-dos/ROADMAP.md index 1183e7f7..5515b634 100644 --- a/to-dos/ROADMAP.md +++ b/to-dos/ROADMAP.md @@ -1443,7 +1443,7 @@ re-minted: `T-PS-dual-runahead` (ADR 0032), `T-MISTER-SAVESTATE`, | Ticket | What | Backlog | Slot | | --- | --- | --- | --- | | `T-ECOSYSTEM-WATCH` | Every minor: the wgpu/egui pair, the AccuracyCoin upstream diff, rcheevos, the libretro build image, the Android/iOS policy calendar, the Rust pin | ecosystem survey | every minor | -| `T-LIBRETRO-TOOLCHAIN` | Drop the libretro build's Rust 1.96 pin if a branch pipeline on 1.99 passes all 15 jobs (D5) | LR-02 | v3.0.1, else v3.1.0 | +| `T-LIBRETRO-TOOLCHAIN` | Drop the libretro build's Rust 1.96 pin if a branch pipeline on 1.99 passes all 15 jobs (D5). **Dropped in v3.0.1** (pipeline 119614, 15/15); closes when the first `main` pipeline after merge is green | LR-02 | v3.0.1 | | `T-ACCURACYCOIN-RESYNC-2610` | Re-sync AccuracyCoin to upstream HEAD (two new tests and a "Misaligned OAM Behavior" fix since 2026-09-19) and triage red first (D25) | ecosystem 7 | v3.1.0 | | `T-EPOCH-FINGERPRINT` | A committed panel of output hashes that fails CI when it moves without an `EMULATION_EPOCH` rise | CI-02 | v3.1.0 | | `T-CPU-OVERCLOCK` | The CPU-multiplier overclock, in `HardwareOptions`, movies and netplay (D22) | FE-01 | v3.1.0 | diff --git a/to-dos/libretro/SPRINT_PLAN.md b/to-dos/libretro/SPRINT_PLAN.md index 2718119b..4cace1d9 100644 --- a/to-dos/libretro/SPRINT_PLAN.md +++ b/to-dos/libretro/SPRINT_PLAN.md @@ -9,7 +9,7 @@ it cites (D5, D28) were taken by the maintainer on 2026-10-07 and are tabled in | Sprint | Release | Deliverable | Gate | Status | | --- | --- | --- | --- | --- | -| L1 | v3.0.1 | **The Rust 1.96 pin** (D5). libretro's build image stopped passing `-C ar` on 2026-09-03 (`libretro-build-rust` `841f3619`; the rebuilt image passed 2026-09-23). Push a short-lived branch with `RUSTUP_TOOLCHAIN` removed and run its pipeline on 1.99. The mirror builds every pushed branch | All 15 jobs green, Apple included: drop the pin, the seven crates' `rust-version = "1.96"`, and the `libretro-cross` 1.96 leg. Any red job: keep the pin and record the job and error in `docs/agents/libretro.md`. Watch the Apple link lines either way, because the image's `-C link-arg` flags now reach the linker | — | +| L1 | v3.0.1 | **The Rust 1.96 pin** (D5). libretro's build image stopped passing `-C ar` on 2026-09-03 (`libretro-build-rust` `841f3619`; the rebuilt image passed 2026-09-23). Push a short-lived branch with `RUSTUP_TOOLCHAIN` removed and run its pipeline on 1.99. The mirror builds every pushed branch | All 15 jobs green, Apple included: drop the pin, the seven crates' `rust-version = "1.96"`, and the `libretro-cross` 1.96 leg. Any red job: keep the pin and record the job and error in `docs/agents/libretro.md`. Watch the Apple link lines either way, because the image's `-C link-arg` flags now reach the linker | **Done in v3.0.1.** Branch `test/libretro-rust-1.99` (only `RUSTUP_TOOLCHAIN: "1.99.0"`): pipeline 119614, 15/15 green. The pin now equals `rust-toolchain.toml`, the seven crates inherit the workspace `rust-version`, and `libretro-cross` fails on a mismatch instead of building a 1.96 leg | | L2 | v3.1.0 | `libretro/docs#1215` (the v3.0.0 page) answered and merged; `docs/libretro/UPSTREAM_SYNC.md` brought up to date (it still says no PR is open) | The upstream state recorded | — | | L3 | v3.6.0 | Core Options v2 (`SET_CORE_OPTIONS_V2`, v0 fallback), float-audio negotiation (`GET_AUDIO_SAMPLE_BATCH_FLOAT`, behind a capability check), LR-03, the Vs. DIP option | `abi_tests.rs` covers each path; every existing option keeps its key | — | | L4 | v3.6.0 | **Console targets** (D28): Switch, PS Vita and 3DS, investigated for a Rust libretro core. `libnx` was dropped at v2.9.8; re-check why rather than assume | A recorded go or no-go per target; any target that goes ahead gets a green buildbot job | — | diff --git a/to-dos/plans/v3.0.1-mortar-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md index 7a25cd2f..2daa9d27 100644 --- a/to-dos/plans/v3.0.1-mortar-plan.md +++ b/to-dos/plans/v3.0.1-mortar-plan.md @@ -30,7 +30,7 @@ The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer, | 1 | pending a quiet host | the runner waits on load and a `BUSY` handshake with every heavy job | | 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) | +| 4 | done | `4aae8c13` (Rust 1.99, libretro buildbot first held on 1.96 via `RUSTUP_TOOLCHAIN`, then moved to 1.99 once test pipeline 119614 passed 15/15 (D5), 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 diff --git a/to-dos/plans/v3.1-to-v4.0-line-plan.md b/to-dos/plans/v3.1-to-v4.0-line-plan.md index f9012237..834f45d2 100644 --- a/to-dos/plans/v3.1-to-v4.0-line-plan.md +++ b/to-dos/plans/v3.1-to-v4.0-line-plan.md @@ -105,7 +105,7 @@ Unchanged from the v2.7 to v3.0 lines, except where a decision is cited. | Slot | Oracle theme | Sibling theme (MiSTer phase) | Plan | | --- | --- | --- | --- | -| v3.0.1 | (in progress) the open items, the toolchain, the review sweep, and the libretro 1.96 pin test (D5) | odd-frame A12 fix | [`v3.0.1-mortar-plan.md`](v3.0.1-mortar-plan.md) | +| v3.0.1 | (in progress) the open items, the toolchain, the review sweep, and the libretro 1.96 pin, tested and dropped (D5) | odd-frame A12 fix | [`v3.0.1-mortar-plan.md`](v3.0.1-mortar-plan.md) | | **v3.1.0** | AccuracyCoin re-sync, the overclock and sprite-limit options, emphasis, the NEC option, rewind and run-ahead in dual mode, records | small RTL items, the self-hosted runner, submission docs (Phase S) | [`v3.1.0-plan.md`](v3.1.0-plan.md) | | v3.2.0 | mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions | options and cheap mappers (Phase F1) | [`v3.2.0-plan.md`](v3.2.0-plan.md) | | v3.3.0 | phi2 write placement and the sprite-0 stale shifter, the graphics stack | the memory platform: arbiter, DDR3, save states, rewind, headline switch (Phase F2) | [`v3.3.0-plan.md`](v3.3.0-plan.md) | @@ -195,9 +195,9 @@ the board (D29). Detail: (D25); - `docs/mappers.md` and `docs/compatibility.md` open questions; - CI-04's search exclusion for anything vendored that stays. -- **The 1.96 pin.** v3.0.1 tests it (D5). If the test is green, v3.0.1 drops - it. If it is red, v3.1.0 records the failing job and keeps the pin, and - `T-LIBRETRO-TOOLCHAIN` stays open. +- **The 1.96 pin: dropped in v3.0.1 (D5).** Test pipeline 119614 passed all + 15 jobs on 1.99.0, the Apple ones included. v3.1.0 only confirms the next + `main` pipeline, and `libretro-cross` now fails if the two toolchains differ. - **Sibling:** - RTL-1 (`$2006` on a `v_pipeline` load dot), RTL-5 (the decay stimulus), RTL-10 (register the 15 AccuracyCoin sub-test ROMs); diff --git a/to-dos/plans/v3.1.0-plan.md b/to-dos/plans/v3.1.0-plan.md index 12054357..a6ed6398 100644 --- a/to-dos/plans/v3.1.0-plan.md +++ b/to-dos/plans/v3.1.0-plan.md @@ -45,7 +45,7 @@ on the v3.0.1 tree, and a mutation that reverts the fix and is caught. | 8 | **ACC-13, the NEC rev B MMC3 as a per-game or config override** (D20) | `T-MMC3-NEC-OVERRIDE` | `mmc3_test_2/6` passes under the override; the default (rev A) results unchanged; the option recorded in `HardwareOptions` | | 9 | **FE-09, rewind and run-ahead in Vs. DualSystem mode** (D27) | `T-PS-dual-runahead` | Both consoles snapshot and restore as one (the existing "RVSD" container). Red first: a rewind across a cabinet frame restores both framebuffers byte-identically. Run-ahead gives the same output as without it. ADR 0032 amended | | 10 | **Records** (DOC-01..DOC-09) | — | Each stale statement corrected against the code with a citation; `to-dos/v1.8.x-on-device-verification.md` folded into `docs/mobile-v2.9.3-run-sheet.md` and deleted (D25); markdownlint and the release audits green | -| 11 | **The libretro toolchain** (D5) | `T-LIBRETRO-TOOLCHAIN` | If v3.0.1 dropped the pin: nothing to do but confirm the next buildbot pipeline. If not: the failing job's name and error recorded in `docs/agents/libretro.md`, and the pin kept | +| 11 | **The libretro toolchain** (D5) | `T-LIBRETRO-TOOLCHAIN` | v3.0.1 dropped the pin (pipeline 119614, 15/15 on 1.99.0): confirm the first `main` pipeline after the merge is green, then close the ticket | | 12 | **Sibling RTL-1**: `$2006`'s `v <- t` copy on a `v_pipeline` load dot | — | A stimulus that lands the copy on dot 257 or an increment dot, compared per cycle; RTL changes only if it diverges | | 13 | **Sibling RTL-5**: a PPU open-bus decay stimulus that reaches the three inert mutations | — | Each mutation CAUGHT, or classified as stimulus-blind with its reach counted | | 14 | **Sibling RTL-10**: register the 15 unregistered AccuracyCoin sub-test ROMs | — | Each resolves to a suite and test address and runs in the ladder | From fa48dfc5cdb16248e42866ab9590c00783ac2a65 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 06:36:35 -0400 Subject: [PATCH 37/44] chore(release): cut v3.0.1 "Mortar" Moves every release anchor from 3.0.0 "Cornerstone" to 3.0.1 "Mortar": - the workspace and cosim versions, both Cargo.locks; - the libretro .info display_version; - Android versionName / versionCode 30001; - iOS MARKETING_VERSION; - the AGENTS, README, STATUS, VERSION-PLAN, ROADMAP, OVERVIEW, ARCHITECTURE, SECURITY and SUPPORT anchors. CHANGELOG [3.0.1] is dated 2026-10-07, with an intro, a roadmap entry and a Verification section. .github/release-notes/v3.0.1.md is the release body. bump_release.py had a latent bug, found here and fixed in this cut. The Cargo.toml anchor pattern `version = "{v}"` was unanchored, so it also matched every internal path-dependency requirement. v3.0.0 moved those requirements to "3.0.0", so the first cut whose OUTGOING release is an X.0.0 found 15 copies of the string and refused. No earlier cut could see it, because the requirements sat at "2.0.0" while the workspace moved through 2.x.y. Both Cargo.toml patterns are now whole-line ("\nversion = ...\n"). A selftest case builds a manifest with a matching requirement. Mutation: reverting to the unanchored pattern makes that case FAIL (caught); the selftest passes otherwise. The script also refused, correctly, until to-dos/ROADMAP.md's release-line chain got a written v3.0.1 entry (its "owed a summary" rule). Records: - VERSION-PLAN moves the forward v3.0.1 row into the release table, marked (current). - README's MiSTer paragraph and STATUS's top-line counts now carry v3.0.1's numbers. - The Cargo.toml comment no longer describes the one-day 1.96 split. - The plan's outcome rows: item 5 is done; item 1, the palette A/B, is still pending a quiet host. Verification on the final tree (31758270 + this cut; the cut changes no code): - test-roms 3,234 / 0 / 11; workspace 2,886 / 0 / 7; cosim 54 / 0; - commercial 60 / 0, 137 / 0, 6 / 0; - fmt, 18 clippy combinations, rustdoc, no_std; - the seven release audits (release_anchor, release_state_prose, contribution_checklist, release_notes_render, provenance_record, libretro_info, cosim_manifest), 0 failed (per-audit counts not quoted: they came from one combined run, whose output order does not name the suite); - bump_release --selftest; - markdownlint. MiSTer: ladders 200/0/1 on-die and 201/0/1 off-die. Seed 2 at 261007, with byte-identical double compiles: on-die 7e81a718..., off-die 88d1dfa5.... NOT included: an uncommitted condensation of AGENTS.md (and an untracked docs/history/ archive) written by another session at 01:55. This commit stages AGENTS.md as HEAD plus only the anchor edits, produced by running the bump in a clean worktree. Every other file was compared byte for byte against that clean run. The other session's edits remain in the working tree for the maintainer. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .github/release-notes/v3.0.1.md | 52 +++++++++++++++++ AGENTS.md | 4 +- ARCHITECTURE.md | 2 +- CHANGELOG.md | 56 +++++++++++++++++++ Cargo.lock | 38 ++++++------- Cargo.toml | 10 ++-- OVERVIEW.md | 6 +- README.md | 10 ++-- ROADMAP.md | 4 +- SECURITY.md | 2 +- SUPPORT.md | 2 +- VERSION-PLAN.md | 6 +- android/app/build.gradle.kts | 4 +- crates/rustynes-cosim/Cargo.lock | 12 ++-- crates/rustynes-cosim/Cargo.toml | 2 +- .../rustynes-libretro/rustynes_libretro.info | 2 +- docs/STATUS.md | 4 +- ios/project.yml | 2 +- scripts/release-automation/bump_release.py | 18 +++++- to-dos/ROADMAP.md | 4 +- to-dos/plans/v3.0.1-mortar-plan.md | 4 +- 21 files changed, 183 insertions(+), 61 deletions(-) create mode 100644 .github/release-notes/v3.0.1.md diff --git a/.github/release-notes/v3.0.1.md b/.github/release-notes/v3.0.1.md new file mode 100644 index 00000000..3f7cee68 --- /dev/null +++ b/.github/release-notes/v3.0.1.md @@ -0,0 +1,52 @@ +# RustyNES v3.0.1 — "Mortar" + +A maintenance release on v3.0.0. It fixes one game's graphics, reaches the last untested exception of the MMC3 timing rule in the MiSTer core (and fixes the one-cycle defect that found), moves the toolchain and every dependency to its newest release, answers every bot review left unanswered since PR #1, settles two provenance questions, and writes the plan to v4.0.0. **Movies recorded with v3.0.0, and netplay peers running it, are refused**: the emulation epoch rises to 2. Save states are unaffected. + +## Breaking change + +- **Movies and netplay from v3.0.0 are refused.** The mapper 45 fix below changes what *Famicom Yarou Vol.1* produces, so `EMULATION_EPOCH` rises from 1 to 2 ([ADR 0045](https://github.com/doublegate/RustyNES/blob/main/docs/adr/0045-a-core-timing-epoch-guards-movies-and-netplay.md)). The refusal names both epochs. Save states load as before. + +## Fixed + +- ***Famicom Yarou Vol.1 7-in-1* draws its menu.** Mapper 45 (GA23C) addresses a cartridge's CHR-RAM unbanked, as the board does; the emulator had banked it like CHR-ROM and drew noise (T-GA23C-CHRRAM). +- **The MiSTer core's MMC3 timing, one cycle on odd frames.** v3.0.0's dot-0 rule for the background's A12 had an exception for the dot the odd-frame skip replaces, and nothing ever reached it. A new generated test ROM does, and found that a `$2001` write taking effect one dot late still applied the rule, so the MMC3 interrupt came one CPU cycle early. The rule now asks whether cycle 0 was rendering. The core and the emulator agree on all 2,978,055 cycles of the new test, and a new comparison of the cycle on which each interrupt rises catches the exception's mutant, which the bus comparison alone could not. +- **Every unanswered bot review, back to PR #1, answered.** 290 unanswered review threads, review-body findings and Antigravity reviews across both repositories got 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; the release tooling could exit 0 on a stale lockfile; three release audits could pass on prose they should fail; and Dependabot's egui holds blocked every future egui and wgpu update (now a group that moves them together). + +## Provenance + +- **The shared Bisqwit NTSC pass is recorded as derived.** Its documentation called it an independent implementation, but it is a generated copy of the desktop filter, whose tables were long recorded as ported from Bisqwit's C via Mesen2. It now carries the same attribution, and the audit lost the exception that hid it. +- **The TriCNES source moved out of the repository.** TriCNES (MIT, by the AccuracyCoin author) stays the one reference whose source may be consulted, for AccuracyCoin work and always attributed. The committed cross-diff evidence stays. +- **A softened comment in the Sunsoft 5B mixer says "derived from" again.** + +## Toolchain and dependencies + +- **Rust 1.99, everywhere.** The libretro buildbot moves too: the release first held it on 1.96 because its build image passed a flag Rust 1.97 rejects, then found the image had dropped it, and a test branch built all 15 buildbot jobs on 1.99, Apple included. CI now fails if the two toolchains ever differ. +- **Every dependency at its newest release**: crates, GitHub Actions (macOS jobs move to `macos-15`), Android (`cargo-ndk` 4, NDK r30), the web build (wasm-opt now pinned), the documentation build, and Docker images (Rust 1.99, Debian 13). + +## The MiSTer core + +- **Release candidate, not hardware-verified.** No hardware has run either bitstream. +- **The co-simulation ladder is 200 passed, 0 failed, 1 expected failure on-die and 201 passed, 0 failed, 1 expected failure off-die**, each from one run of a frozen tree, with nothing skipped. The one new gate is the odd-frame A12 test above. +- **New bitstreams, because the fix is RTL.** Both builds are compiled at fitter seed 2, chosen from eight seeds swept on one build date (261007), every one of which closes on both builds. Each was compiled twice to the same bytes: + - on-die `RustyNES_MiSTer-v3.0.1.rbf`, md5 `7e81a71869760b35a2fd04e6309c5c39` (timing margin +0.448 ns setup, +0.113 ns hold); + - off-die `RustyNES_MiSTer-v3.0.1-offdie.rbf`, md5 `88d1dfa53a94996a92c0f768b3be3039` (+0.401 / +0.096 ns; the SDRAM read +0.447 / +1.184 ns, assuming zero board delay). +- The bitstreams carry the sweep's build date, which may be earlier than the release date. + +## The road to v4.0.0 + +The plan from v3.1.0 to v4.0.0 is written ([`to-dos/plans/v3.1-to-v4.0-line-plan.md`](https://github.com/doublegate/RustyNES/blob/main/to-dos/plans/v3.1-to-v4.0-line-plan.md)), from 29 maintainer decisions. v4.0.0 makes the remaining public enums `#[non_exhaustive]` and brings the MiSTer core to feature parity. The hardware-verification release (the SuperStation One board session and the mobile device run) comes at the end of the v3.9.x line, so it tests the near-final core. + +## Verification + +- `--features test-roms`: 3,234 passed, 0 failed, 11 ignored. +- The local commercial suites: `external_real_games` 60/0, `external_extended` 137/0, `external_coverage` 6/0 over every staged ROM. The one moved baseline is *Famicom Yarou Vol.1*, which now draws its menu. +- AccuracyCoin 144/144, nestest 0-diff. +- The libretro buildbot: all 15 jobs on Rust 1.99 before the pin was lifted. + +## Install + +- Download the pre-built binaries for Linux, macOS, and Windows below. +- The MiSTer core bitstreams are attached below, as a release candidate, not hardware-verified. The on-die build is `RustyNES_MiSTer-v3.0.1.rbf`, also attached under its datecoded name `RustyNES_20261007.rbf`, and the off-die build is `RustyNES_MiSTer-v3.0.1-offdie.rbf`. +- The WebAssembly build is live at [doublegate.github.io/RustyNES](https://doublegate.github.io/RustyNES/). +- The RetroArch core is in RetroArch's Online Updater on the platforms the libretro buildbot publishes to. +- Licensed under GPL-3.0-or-later. diff --git a/AGENTS.md b/AGENTS.md index 4e045121..a49cf998 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ Enforcement lives alongside the prose: `/ref-proj/` is gitignored/`.dockerignore RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in pure Rust. The accuracy bar is Mesen2 / higan / ares: tight lockstep scheduling at PPU-dot resolution on a master-clock-precise timebase, sub-instruction PPU events visible to subsequent CPU code, and a lookup-table non-linear audio mixer with band-limited synthesis. The frontend is pure Rust (`winit` + `wgpu` + `cpal` + `egui`). -**Current release: v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here. +**Current release: v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here. - **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward. @@ -238,7 +238,7 @@ that is a reason to add a tenth — not a reason to grow this section back. - `ref-docs/` is immutable. Research updates go in dated supplemental files. - ADRs go in `docs/adr/` (Michael Nygard format). - `rustynes-core` re-exports the public types from the chip crates; downstream consumers (`rustynes-frontend`, `rustynes-test-harness`) should depend on `rustynes-core` rather than the chip crates directly. -- When relabeling old engine "v2.x" narrative for users, present it as upstream lineage/history — **never as a current RustyNES release version.** The current release is **v3.0.0 "Cornerstone"** (2026-10-06). **Never claim any version *later* than v3.0.0 is released.** Two distinct "v2.0"s exist and must not be conflated: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) and was the only scheduler through v1.10.0; RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is a different milestone that REPLACES that dot-lockstep scheduler with the one-clock, every-cycle-bus-access model (ADR 0002 / 0028 / 0029) and broke byte-identity and save-state compatibility, by design (v2.9.8 later broke save, movie and API compatibility again, ahead of v3.0.0). The per-release narrative that used to be inlined here is in `CHANGELOG.md`, the per-release notes under `.github/release-notes/`, and the published GitHub releases — three places that are maintained, against one copy here that was not. +- When relabeling old engine "v2.x" narrative for users, present it as upstream lineage/history — **never as a current RustyNES release version.** The current release is **v3.0.1 "Mortar"** (2026-10-07). **Never claim any version *later* than v3.0.1 is released.** Two distinct "v2.0"s exist and must not be conflated: the **engine-lineage v2.0** master-clock work shipped as the **v1.0.0** production core (2026-06-13) and was the only scheduler through v1.10.0; RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is a different milestone that REPLACES that dot-lockstep scheduler with the one-clock, every-cycle-bus-access model (ADR 0002 / 0028 / 0029) and broke byte-identity and save-state compatibility, by design (v2.9.8 later broke save, movie and API compatibility again, ahead of v3.0.0). The per-release narrative that used to be inlined here is in `CHANGELOG.md`, the per-release notes under `.github/release-notes/`, and the published GitHub releases — three places that are maintained, against one copy here that was not. - **Forward plans + roadmap live in `to-dos/`.** `to-dos/ROADMAP.md` (updated in #129) is the planning entry point and frames the release line + "the path to v2.0.0 and beyond"; `to-dos/plans/` holds the per-release plan docs (through `v1.7.0-forge-plan.md` on `main`, plus the staged-forward `v1.8.0-android-plan.md` / `v1.9.0-ios-plan.md` / `v2.0.0-master-clock-plan.md`) + the `to-dos/plans/engine-lineage/` history archive + a `to-dos/plans/research/` reference-mining archive. - The v1.0.0 release + GitHub Pages/CI + post-release record is in `docs/v1.0.0-synthesis-handoff-2026-06-13.md` — read it before touching CI, Pages, or release tooling. Full per-release history is in `CHANGELOG.md`. - **Markdownlint is a CI gate** (pre-commit, pinned `markdownlint-cli v0.49.1`). The pin was v0.39.0 until the v2.6.3 dependency refresh, held because the newer local binary reported rules the pin lacked — chiefly **MD060** (`table-column-style`), which was therefore NOT gated. That is now measured and resolved: MD060's inferred default reads this corpus as style `compact` and reports **1,936 findings across 122 files** and nothing else, so `.markdownlint.json` pins `MD060` to the style actually in use (`leading_and_trailing`), which measures **zero** and rewrites no document. It IS a gate now. Still verify with `pre-commit run markdownlint --all-files` rather than the bare binary — the pin and the local build can drift apart again. `.markdownlint.json` also keeps `MD013`/`MD033`/`MD041` disabled by design (long technical tables, the README HTML banner/``, the HTML-led README). `.markdownlintignore` exempts `ref-docs/`, `ref-proj/` (the reference-emulator clone, now removed from disk but kept in the ignore lists as a firewall guard so it can never re-enter the tree — see the MOST IMPORTANT RULE section above), the vendored `tricnes/` + upstream READMEs, and the frozen `docs/archive/` + `to-dos/archive/` trees — don't lint or reformat those. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 30850292..33d2d21f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ **Document Version:** 2.1.0 **Last Updated:** 2026-08-30 -**Applies to:** RustyNES v3.0.0 (the scheduling model is v2.0.0 "Timebase" onward) +**Applies to:** RustyNES v3.0.1 (the scheduling model is v2.0.0 "Timebase" onward) This document fixes the high-level architecture of RustyNES. The per-subsystem specs under `docs/` (`cpu-6502.md`, `ppu-2c02.md`, `apu-2a03.md`, `mappers.md`, `scheduler.md`) take these decisions as given and elaborate one chip each. After reading this you should know the workspace shape, the scheduling model, the public boundary, and the load-bearing invariants. The canonical, always-current architecture spec is [`docs/architecture.md`](docs/architecture.md); this file is the top-level companion. diff --git a/CHANGELOG.md b/CHANGELOG.md index 84026018..ba07dda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,21 @@ cycle-accurate core later replaced. ## [Unreleased] +## [3.0.1] - 2026-10-07 - "Mortar" (the open items closed, one game's graphics fixed, the last MMC3 rule exception tested in the MiSTer core, Rust 1.99 everywhere, every unanswered bot review answered, and a roadmap to v4.0.0) + +A maintenance release on v3.0.0. It closes the items v3.0.0 left open: +*Famicom Yarou Vol.1*'s CHR-RAM, the MiSTer core's odd-frame A12 exception +(now reached by a test ROM, which found a real one-cycle defect). It moves the toolchain and every dependency to its +newest release (the libretro buildbot included, once a test pipeline proved its +build image no longer blocks it), answers every bot review left unanswered since +PR #1, settles two provenance questions, and writes the plan from v3.1.0 to +v4.0.0 from 29 maintainer decisions. **Movies and netplay from v3.0.0 are +refused**: the mapper 45 fix changes one game's output, so `EMULATION_EPOCH` +rises to 2 (ADR 0045). Save states are unaffected. The MiSTer bitstreams are +rebuilt, because the A12 fix is RTL, and remain a **release candidate, not +hardware-verified**. The maintainer's decisions are in +`to-dos/plans/v3.0.1-mortar-plan.md` and `to-dos/plans/v3.1-to-v4.0-line-plan.md`. + ### Fixed - **Mapper 45 (GA23C) CHR-RAM is unbanked (T-GA23C-CHRRAM).** *Famicom Yarou @@ -84,6 +99,16 @@ cycle-accurate core later replaced. ### Changed +- **The plan from v3.1.0 to v4.0.0.** `to-dos/plans/v3.1-to-v4.0-line-plan.md` + and one plan per release, written from three research passes (the oracle's + backlog, the MiSTer core's, and the outside ecosystem) and 29 maintainer + decisions taken on 2026-10-07. v4.0.0 is the remaining public enums made + `#[non_exhaustive]` plus MiSTer feature parity. The hardware-verification + release (the SuperStation One board session and the mobile device run) moves + to the end of the v3.9.x line, so it tests the near-final core, and is + numbered after the session. `VERSION-PLAN.md` now lets save-state, movie, + netplay and epoch breaks land in any release that says so; a MAJOR is a + public Rust API break or a new kind of deliverable (ADR 0043 amendments). - **Rust 1.99, everywhere.** The pinned toolchain moves from 1.96.0 to 1.99.0, the newest stable, and the libretro buildbot moves with it. The release first held the buildbot on 1.96.0, because its build image passed `-C ar`, a hard @@ -111,6 +136,37 @@ cycle-accurate core later replaced. Vol.1* produces, so movies recorded and netplay peers running v3.0.0 are refused, naming both epochs (ADR 0045). +### Verification + +- `cargo test --release --workspace --features test-roms --no-fail-fast`: + 3,234 passed, 0 failed, 11 ignored on the release tree (v3.0.0: 3,223 / 0 / + 11). `cargo test --workspace`: 2,886 / 0 / 7; the cosim crate 54 / 0. + AccuracyCoin 144/144, nestest 0-diff. +- The local commercial suites (`--features test-roms,commercial-roms`): + `external_real_games` 60/0, `external_extended` 137/0, `external_coverage` + 6/0. The one moved baseline is *Famicom Yarou Vol.1* (T-GA23C-CHRRAM), which + now draws its menu. +- fmt; clippy for all 18 feature combinations, including `retroachievements`, + `full` and both wasm builds; rustdoc `-D warnings`; the `no_std` build; the + release audits; markdownlint. The code fixes carry tests, with the mutation + that checks each one recorded in its commit body. +- The libretro buildbot: a test branch on Rust 1.99.0 (pipeline 119614) passed + all 15 jobs, the four Apple ones included, before the pin was lifted. +- The MiSTer core: on-die ladder 200 passed, 0 failed, 1 expected failure, + off-die 201 / 0 / 1, each one frozen-worktree run of the final sibling RTL + against the oracle pinned at this release branch, nothing skipped. The new + odd-frame A12 gate matches all 2,978,055 cycles and the cycle of every /IRQ + rise; its three mutants were classified (two caught, the third inert by + construction and documented at the site). Both builds were swept at seeds + 1-8 on one build date (261007), and every seed closes on both. Seed 2 is + pinned (on-die +0.448 / +0.113 ns, off-die +0.401 / +0.096 ns, SDRAM read + +0.447 / +1.184 ns), and two clean compiles of each are byte-identical + (on-die `7e81a718...`, off-die `88d1dfa5...`). The stuck-register and + suppressed-message checks pass on both. That pair ships as a release + candidate. **No hardware has run any bitstream.** +- The Android unit tests run in CI on the release PR; the iOS Swift and the + mobile device behaviour are unverified on this Linux host. + ## [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/Cargo.lock b/Cargo.lock index 42bc6a8e..145f9a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4290,7 +4290,7 @@ dependencies = [ [[package]] name = "rustynes-android" -version = "3.0.0" +version = "3.0.1" dependencies = [ "android-activity", "android_logger", @@ -4308,7 +4308,7 @@ dependencies = [ [[package]] name = "rustynes-apu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags 2.13.2", "criterion", @@ -4321,7 +4321,7 @@ dependencies = [ [[package]] name = "rustynes-cheevos" -version = "3.0.0" +version = "3.0.1" dependencies = [ "cc", "libc", @@ -4330,7 +4330,7 @@ dependencies = [ [[package]] name = "rustynes-core" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags 2.13.2", "criterion", @@ -4347,7 +4347,7 @@ dependencies = [ [[package]] name = "rustynes-cpu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags 2.13.2", "criterion", @@ -4358,7 +4358,7 @@ dependencies = [ [[package]] name = "rustynes-frontend" -version = "3.0.0" +version = "3.0.1" dependencies = [ "anstyle", "arboard", @@ -4417,18 +4417,18 @@ dependencies = [ [[package]] name = "rustynes-gamedb" -version = "3.0.0" +version = "3.0.1" dependencies = [ "rustynes-core", ] [[package]] name = "rustynes-gfx-shaders" -version = "3.0.0" +version = "3.0.1" [[package]] name = "rustynes-hdpack" -version = "3.0.0" +version = "3.0.1" dependencies = [ "lewton", "png", @@ -4439,7 +4439,7 @@ dependencies = [ [[package]] name = "rustynes-ios" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bytemuck", "cpal", @@ -4453,7 +4453,7 @@ dependencies = [ [[package]] name = "rustynes-libretro" -version = "3.0.0" +version = "3.0.1" dependencies = [ "libc", "rust-libretro", @@ -4463,7 +4463,7 @@ dependencies = [ [[package]] name = "rustynes-mappers" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags 2.13.2", "criterion", @@ -4475,7 +4475,7 @@ dependencies = [ [[package]] name = "rustynes-mobile" -version = "3.0.0" +version = "3.0.1" dependencies = [ "rustynes-core", "rustynes-gamedb", @@ -4491,7 +4491,7 @@ dependencies = [ [[package]] name = "rustynes-netplay" -version = "3.0.0" +version = "3.0.1" dependencies = [ "futures-util", "js-sys", @@ -4507,7 +4507,7 @@ dependencies = [ [[package]] name = "rustynes-ppu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags 2.13.2", "criterion", @@ -4519,21 +4519,21 @@ dependencies = [ [[package]] name = "rustynes-probe" -version = "3.0.0" +version = "3.0.1" dependencies = [ "rustynes-core", ] [[package]] name = "rustynes-ra" -version = "3.0.0" +version = "3.0.1" dependencies = [ "rustynes-cheevos", ] [[package]] name = "rustynes-script" -version = "3.0.0" +version = "3.0.1" dependencies = [ "mlua", "piccolo", @@ -4544,7 +4544,7 @@ dependencies = [ [[package]] name = "rustynes-test-harness" -version = "3.0.0" +version = "3.0.1" dependencies = [ "insta", "png", diff --git a/Cargo.toml b/Cargo.toml index 4954b83f..a46a81c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,12 +77,12 @@ default-members = ["crates/rustynes-libretro"] # `release-auto.yml` reads the `## [X.Y.Z]` line for BOTH the release body # fallback and the title codename — so the date and quoted codename are load- # bearing, not decoration. -version = "3.0.0" +version = "3.0.1" edition = "2024" -# 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. +# The toolchain is 1.99 (rust-toolchain.toml), and every crate inherits this +# floor, the libretro path included: the libretro buildbot follows the same pin +# (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`, which CI's `libretro-cross` checks +# against rust-toolchain.toml). v3.0.1 held seven crates at "1.96" for a day. rust-version = "1.99" license = "GPL-3.0-or-later" authors = ["DoubleGate "] diff --git a/OVERVIEW.md b/OVERVIEW.md index 10a88be3..06fa56f0 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -2,7 +2,7 @@ **Document Version:** 2.1.0 **Last Updated:** 2026-10-06 -**Applies to:** RustyNES v3.0.0 +**Applies to:** RustyNES v3.0.1 --- @@ -22,9 +22,9 @@ RustyNES is the **definitive NES emulator for the modern era** — combining cycle-perfect accuracy with a complete contemporary feature set and the safety guarantees of Rust. It is more than an emulator: it is a platform for NES preservation, competitive online play, tool-assisted speedrunning, and homebrew development. -As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the one deliberate breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. +As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the one deliberate breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. -> RustyNES's emulation core descends from an extensively-documented accuracy program. Where this and related docs reference deep "v1.x"/"v2.x" engine narrative, read it as upstream engine lineage (engineering history), not as RustyNES release versions. Two distinct "v2.0"s exist and must not be conflated: the engine-lineage v2.0 master-clock work shipped as RustyNES **v1.0.0**, while RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is the later release that *replaced* that same scheduler. The current release is **v3.0.0**. +> RustyNES's emulation core descends from an extensively-documented accuracy program. Where this and related docs reference deep "v1.x"/"v2.x" engine narrative, read it as upstream engine lineage (engineering history), not as RustyNES release versions. Two distinct "v2.0"s exist and must not be conflated: the engine-lineage v2.0 master-clock work shipped as RustyNES **v1.0.0**, while RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is the later release that *replaced* that same scheduler. The current release is **v3.0.1**. --- diff --git a/README.md b/README.md index d76e8e2f..d09cd2c6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- Build Status License: GPL-3.0-or-later Version Rust: 1.99
+ Build Status License: GPL-3.0-or-later Version Rust: 1.99
AccuracyCoin nestest Mapper families Try in browser
Platform

@@ -357,7 +357,7 @@ detailed in [`docs/architecture.md`](docs/architecture.md) and ## Current release -RustyNES's current release is **v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. +RustyNES's current release is **v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. **v3.0.0 is the API major** ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). It gathers every breaking change since v2.x, most of them made early in v2.9.8 and @@ -397,10 +397,10 @@ one, cycle by cycle. Its co-simulation ladder covers the 6502, the bus and interrupts, the 2C02, the 2A03, AccuracyCoin parity and six mapper boards. Each release attaches a timing-closed -bitstream pair; v3.0.0's (on-die and off-die, both at fitter seed 5, each compiled +bitstream pair; v3.0.1's (on-die and off-die, both at fitter seed 2, each compiled twice to the same bytes) is a **release candidate, not hardware-verified**. Its -co-simulation ladder reads 199 passed, 0 failed, 1 expected failure on-die and -200 / 0 / 1 off-die. **No hardware has run any bitstream yet**, so a booting core, a synced +co-simulation ladder reads 200 passed, 0 failed, 1 expected failure on-die and +201 / 0 / 1 off-die. **No hardware has run any bitstream yet**, so a booting core, a synced display, audible sound and a working pad are not claimed. The details, and what each rung can and cannot verify, are in [`docs/mister.md`](docs/mister.md). diff --git a/ROADMAP.md b/ROADMAP.md index 6cb670b6..4df8a813 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,13 +2,13 @@ **Document Version:** 2.0.4 **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). +**Project Status:** v3.0.1 "Mortar" released — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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) and **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). --- ## Where we are -RustyNES is well past v1.0.0. The current release is **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +RustyNES is well past v1.0.0. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** **This root ROADMAP is a historical snapshot of the v1.0.0 cut.** For the authoritative, current forward roadmap see **[`to-dos/ROADMAP.md`](to-dos/ROADMAP.md)**; for the authoritative current-state pass counts and platform matrix see **[`docs/STATUS.md`](docs/STATUS.md)**; for the full per-release history see **[`CHANGELOG.md`](CHANGELOG.md)**. Many of the "post-1.0 directions" listed further down (mobile, Lua scripting, TAS editor, Vs. DualSystem, HD packs, hosted netplay) have since shipped — the tables below record what was **done at v1.0.0**, not the current feature set. diff --git a/SECURITY.md b/SECURITY.md index 10359fa1..09903d49 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -The current release is **v3.0.0 "Cornerstone"**. Built on **v2.9.9 "Ballast"** and **v2.9.8 "Vanguard"** and **v2.9.7 "Tandem"** and **v2.9.6 "Roster"** and **v2.9.5 "Caliper"** and **v2.9.4 "Plumb"** and **v2.9.3 "Handset"** and **v2.9.2 "Candidate"** and **v2.9.1 "Hone"** and **v2.9.0 "Survey"** and **v2.8.4 "Tether"** and **v2.8.3 "Rivet"** and **v2.8.2 "Solder"** and **v2.8.1 "Gasket"** and **v2.8.0 "Bulkhead"** and **v2.7.6 "Recount"** and **v2.7.5 "Tally"** and **v2.7.4 "Pocket"** and **v2.7.3 "Hearth"** and **v2.7.2 "Bankroll"** and **v2.7.1 "Keepsake"** and **v2.7.0 "Palisade"** and **v2.6.23 "Pulse"** and **v2.6.22 "Rigging"** and **v2.6.21 "Steward"** and **v2.6.20 "Telltale"** and **v2.6.19 "Accession"** and **v2.6.18 "Errata"** and **v2.6.17 "Terminus"** and **v2.6.16 "Interlock"** and **v2.6.15 "Warrant"** and **v2.6.14 "Docket"** and **v2.6.13 "Slack"** and **v2.6.12 "Groundwork"** and **v2.6.11 "Exposure"** and **v2.6.10 "Inference"** and **v2.6.9 "Abeyance"** and **v2.6.8 "Arrears"** and **v2.6.7 "Detent"** and **v2.6.6 "Chassis"** and **v2.6.5 "Muster"** and **v2.6.4 "Rubric"** and **v2.6.3 "Mainspring"** and **v2.6.2 "Witness"** and **v2.6.1 "Interleave"** and **v2.6.0 "Assay"** and **v2.5.9 "Overture"** and **v2.5.8 "Blanking"** and **v2.5.7 "Collimation"** and **v2.5.6 "Vestige"** and **v2.5.5 "Raster"** and **v2.5.4 "Escapement"** and **v2.5.3 "Hysteresis"** and **v2.5.2 "Dormant"** and **v2.5.1 "Retrace"** and **v2.5.0 "Rungwork"** and **v2.4.9 "Plumbline II"** and **v2.4.8 "Palimpsest"** and **v2.4.7 "Keystone"** and **v2.4.6 "Abacus"** and **v2.4.5 "Compass"** and **v2.4.4 "Ignition"** and **v2.4.3 "Touchstone"** and **v2.4.2 "Cairn"** and **v2.4.1 "Fabric"**, which also carries the never-tagged v2.4.0 "Concordance". RustyNES ships from `main` on a +The current release is **v3.0.1 "Mortar"**. Built on **v3.0.0 "Cornerstone"** and **v2.9.9 "Ballast"** and **v2.9.8 "Vanguard"** and **v2.9.7 "Tandem"** and **v2.9.6 "Roster"** and **v2.9.5 "Caliper"** and **v2.9.4 "Plumb"** and **v2.9.3 "Handset"** and **v2.9.2 "Candidate"** and **v2.9.1 "Hone"** and **v2.9.0 "Survey"** and **v2.8.4 "Tether"** and **v2.8.3 "Rivet"** and **v2.8.2 "Solder"** and **v2.8.1 "Gasket"** and **v2.8.0 "Bulkhead"** and **v2.7.6 "Recount"** and **v2.7.5 "Tally"** and **v2.7.4 "Pocket"** and **v2.7.3 "Hearth"** and **v2.7.2 "Bankroll"** and **v2.7.1 "Keepsake"** and **v2.7.0 "Palisade"** and **v2.6.23 "Pulse"** and **v2.6.22 "Rigging"** and **v2.6.21 "Steward"** and **v2.6.20 "Telltale"** and **v2.6.19 "Accession"** and **v2.6.18 "Errata"** and **v2.6.17 "Terminus"** and **v2.6.16 "Interlock"** and **v2.6.15 "Warrant"** and **v2.6.14 "Docket"** and **v2.6.13 "Slack"** and **v2.6.12 "Groundwork"** and **v2.6.11 "Exposure"** and **v2.6.10 "Inference"** and **v2.6.9 "Abeyance"** and **v2.6.8 "Arrears"** and **v2.6.7 "Detent"** and **v2.6.6 "Chassis"** and **v2.6.5 "Muster"** and **v2.6.4 "Rubric"** and **v2.6.3 "Mainspring"** and **v2.6.2 "Witness"** and **v2.6.1 "Interleave"** and **v2.6.0 "Assay"** and **v2.5.9 "Overture"** and **v2.5.8 "Blanking"** and **v2.5.7 "Collimation"** and **v2.5.6 "Vestige"** and **v2.5.5 "Raster"** and **v2.5.4 "Escapement"** and **v2.5.3 "Hysteresis"** and **v2.5.2 "Dormant"** and **v2.5.1 "Retrace"** and **v2.5.0 "Rungwork"** and **v2.4.9 "Plumbline II"** and **v2.4.8 "Palimpsest"** and **v2.4.7 "Keystone"** and **v2.4.6 "Abacus"** and **v2.4.5 "Compass"** and **v2.4.4 "Ignition"** and **v2.4.3 "Touchstone"** and **v2.4.2 "Cairn"** and **v2.4.1 "Fabric"**, which also carries the never-tagged v2.4.0 "Concordance". RustyNES ships from `main` on a rolling patch cadence rather than maintaining long-lived release branches, so security fixes land in the next patch release rather than being backported. Report against the latest release or `main`. diff --git a/SUPPORT.md b/SUPPORT.md index 38018151..ee55055e 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -94,7 +94,7 @@ A: RustyNES is a cycle-accurate NES emulator written in pure Rust, clearing the **Q: Can I use RustyNES now?** -A: Yes. RustyNES is well past its first stable release — the current release is **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** +A: Yes. RustyNES is well past its first stable release — the current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). **No hardware has run any bitstream.** **Q: How accurate is RustyNES?** diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index eee3d69b..43b70541 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -1,6 +1,6 @@ # RustyNES Version Plan -**Current release: v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. **No hardware has run any bitstream.** +**Current release: v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. **No hardware has run any bitstream.** RustyNES follows [Semantic Versioning 2.0.0](https://semver.org/). @@ -73,7 +73,6 @@ day.) | Version | Scope | Plan | |---------|-------|------| -| v3.0.1 "Mortar" | The open items (the palette-offset A/B, T-GA23C-CHRRAM, the MiSTer odd-frame A12 stimulus), every dependency and the Rust 1.99 toolchain, the libretro buildbot on 1.99 too (its 1.96 hold tested and dropped), every unanswered review comment back to #1 | [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md) | | v3.1.0 | The AccuracyCoin re-sync; the CPU overclock and sprite-limit options in movies and netplay; PAL emphasis; opt-in composite artifacts; the NEC MMC3 option; rewind and run-ahead in Vs. dual mode; an epoch fingerprint gate. MiSTer: small RTL items, the self-hosted runner, submission documents | [`v3.1.0-plan.md`](to-dos/plans/v3.1.0-plan.md) | | v3.2.0 | Mapper breadth by real titles, the dump corpus, KNOWN_BLANK triage, tier promotions. MiSTer F1: options and about ten cheap families, paddle, Four Score, cheats | [`v3.2.0-plan.md`](to-dos/plans/v3.2.0-plan.md) | | v3.3.0 | Phi2 write placement and the sprite-0 stale shifter; wgpu 31 / egui 0.37. MiSTer F2: the SDRAM arbiter, DDR3, save states, rewind; the off-die build becomes the headline | [`v3.3.0-plan.md`](to-dos/plans/v3.3.0-plan.md) | @@ -167,7 +166,8 @@ The 1.x line was **additive / off-by-default** — every release stayed byte-ide | **v2.7.5 "Tally"** | Every audit claim closed with a measurement or a reason -- the sixth and last release of the v2.7.x audit line; the core and frontend ledgers have no open row. Of the core audit's twelve hot-path proposals, ten were measured here before any was built, IMP-12 had been measured in v2.3.1, and the phase-`match` fold was closed by reasoning: one combined ceiling probe (seven proposals at their maximum, knowingly breaking correctness) bounded those seven at zero on two clean runs, and IMP-04 run alone twice also measured zero; IMP-05 (CPU inlining + cold-path outlining, whose premise was true) and the branchless palette got their own runs, also zero; IMP-12 was already v2.3.1's G10. **One adopted:** `BlipBuf::drain_all` keeps its capacity between frames, -0.89% frame time on two runs of a frame-then-drain workload (the stock benches never drain audio, which review on #553 exposed after the probe had first been credited with it); it helps the mobile bridge and the wasm build. 18 dead `rustynes_cpu::Bus` methods (found by the compiler: 5 of the audit's 22 were live) and `ApuBus` are `#[deprecated]`, removal decided at v2.9.0. An HD pack refunds the budget of an image that fails after its header. MOB-06 is blocked on UniFFI releasing mutable borrowed bytes. No emulation behaviour change; AccuracyCoin 144/144, nestest 0-diff. | | **v2.7.6 "Recount"** | The v2.7.5 deletions measured one at a time -- a measurement release between the v2.7.x and v2.8.x audit lines. v2.7.5 bounded six of the core audit's proposals (§3.1 A/B/C, the IMP-06 stores, §3.5b, §3.6) only in one combined ceiling; each was re-run alone, twice, with the A/B/A control. Five are zero; §3.5b had never been reached (the bench ROMs are silent, 0 sweep-mute checks per frame), and on `spritecans.nes` bounds at about 0.2%, with the cheap byte-identical candidate 0.67% slower, so it is rejected. Three of the IMP-06 stores re-wrote fields the fast path's guard already requires, and are now a `debug_assert!` pinned by a unit test (no ROM reaches the violating state). §3.1 B and C are dead work that goes with the v2.9.0 deprecation decision. Also ships #554 (contributed): the macOS buildbot jobs build again, and 32-bit Windows, 32-bit Linux and webOS targets were added. Plan: `to-dos/plans/v2.7.6-recount-plan.md` | | **v2.8.0 "Bulkhead"** | The libretro core stops a fault at its own boundary -- the first release of the v2.8.x audit line, from the libretro audit's FFI-safety findings (L-1.1 to L-1.5, L-2.1, L-2.2, L-2.4), each shown failing in a new C-ABI harness that drives the core as a frontend does. The core was built `panic = "abort"`, so its handlers were dead code; it now unwinds on every supported build path (+0.1% instructions per frame; a direct `cargo build --release` still aborts, and now warns and logs it), and every emulating callback contains a panic, stops the game and keeps the console so RetroArch's memory pointers stay valid. `serialize_size` reserves room for the largest expansion device on both ports (a Zapper no longer breaks every later save) and the reader treats an all-zero tail as padding; unload withdraws the memory maps; the vendored `rust-libretro-sys` writes out `retro_game_info`, so the core loads without `GET_GAME_INFO_EXT`. The save state gains the 2A03 internal data bus (older states still load), and the schema audit now covers the bus. No emulation behaviour change; AccuracyCoin 144/144, nestest 0-diff. Plan: `to-dos/plans/v2.8.0-bulkhead-plan.md` | -| **v3.0.0 "Cornerstone"** (current) | The API major -- the first release of the v3 line (ADR 0043). It gathers every break since v2.x in one place (the header-free game identity, refused older states and movies, the protocol and API changes of v2.8.x-v2.9.9) and adds the last: a core timing epoch that movies (`.rnm` format 5) and netplay (protocol 6, `"RNE6"`) carry, so a version that emulates differently is refused with a reason (ADR 0045); `Cartridge`, `BoardDescription`, `HardwareOptions` and `Movie` `#[non_exhaustive]`; `MapperError::Truncated` renamed `WrongLength`. T-MMC3-BG-A12 closes blargg's `4-scanline_timing` (13/13) in the emulator and on the MiSTer core (`PPU_SNAPSHOT_VERSION` 12). Also: the spectator buffer bounded, mapper 45's power-on CHR-AND (two *Famicom Yarou* menus), the mobile apps on the workspace version, and `CI success` that cannot pass untested. The MiSTer bitstreams ship as a release candidate, not hardware-verified; verification moves to v3.x | +| **v3.0.1 "Mortar"** (current) | A maintenance release on v3.0.0, plan [`v3.0.1-mortar-plan.md`](to-dos/plans/v3.0.1-mortar-plan.md). Mapper 45's CHR-RAM is unbanked, so *Famicom Yarou Vol.1* draws its menu (T-GA23C-CHRRAM; `EMULATION_EPOCH` 2 refuses v3.0.0 movies and netplay peers). The MiSTer core's dot-0 A12 rule is gated on whether cycle 0 was rendering, found by a new odd-frame stimulus (`mapper4mmc3oddskip080`, plus an /IRQ-rise comparison); new bitstreams at seed 2 / 261007, a release candidate, not hardware-verified. Rust 1.99 and every dependency at its newest, the libretro buildbot included once test pipeline 119614 passed 15/15. Every bot review left unanswered since PR #1 answered (473 verdicts, 80 fixes). The shared Bisqwit NTSC pass recorded as derived; the TriCNES source moved out of the repository. The plan to v4.0.0 written from 29 maintainer decisions | +| **v3.0.0 "Cornerstone"** | The API major -- the first release of the v3 line (ADR 0043). It gathers every break since v2.x in one place (the header-free game identity, refused older states and movies, the protocol and API changes of v2.8.x-v2.9.9) and adds the last: a core timing epoch that movies (`.rnm` format 5) and netplay (protocol 6, `"RNE6"`) carry, so a version that emulates differently is refused with a reason (ADR 0045); `Cartridge`, `BoardDescription`, `HardwareOptions` and `Movie` `#[non_exhaustive]`; `MapperError::Truncated` renamed `WrongLength`. T-MMC3-BG-A12 closes blargg's `4-scanline_timing` (13/13) in the emulator and on the MiSTer core (`PPU_SNAPSHOT_VERSION` 12). Also: the spectator buffer bounded, mapper 45's power-on CHR-AND (two *Famicom Yarou* menus), the mobile apps on the workspace version, and `CI success` that cannot pass untested. The MiSTer bitstreams ship as a release candidate, not hardware-verified; verification moves to v3.x | | **v2.9.9 "Ballast"** | The release candidate for v3.0.0 -- the tenth release of the v2.9.x line and the last of the line to v3.0.0. All four audit scopes re-run (0 rows regressed; NC-09..17, NF-11..23, NL-11..15, NR-13..17 fixed or decided, NC-17 and NR-14 by the maintainer). T-ORACLE-001 fixed to `4-scanline_timing` sub-test 9: the oracle's `$C001` reload discriminator was the late IRQ; the NESdev rule plus a one-cycle IRQ output deferral replace it (MMC3 section v4). T-MMC5-8X8-SET fixed and wider than the ticket: the MMC5 decodes `$2000`/`$2001` itself and follows the page's CHR table and `$2007` rule, which also fixed *Uchuu Keibitai SDF* (T-COMMERCIAL-GARBLE closed; MMC5 section v6). Audio exact across a save state (APU v5, NL-12). Movie format 4 (NC-10). Mobile save keys migrated to the v2.9.8 identity (NF-21). The sibling's oracle pin moved v2.9.2 -> v2.9.9 with every changed golden attributed, the APU `$4017` / scanline-0 parity in RTL, a frame-sequencer stall found and fixed, and `apu_test` 1-10, `mapper4mmc3irq065` and four checkpoint streams gated. RC bitstreams at seed 6 (261005), byte-identical double compiles. T-GA23C-POWERON searched and left open. | | **v2.9.8 "Vanguard"** | The preparation release for v3.0.0 -- the ninth release of the v2.9.x line. v3.0.0's planned breaks landed early at the maintainer's direction (ADR 0042/0043 amendments of 2026-10-01; the number stays v2.9.x): `Nes::rom_sha256` hashes the bytes after the header, so earlier saves, cheats and movies are not found; `.rns` epoch 3 and BUS section 2 refuse older states with every legacy reader removed; `.rnm` format 3 and the netplay handshake carry every emulation option (ADR 0044) and Four Score P3/P4; `SystemBus` (was `LockstepBus`), the 2.7.5 deprecations, `serialize_header` and the dead /NMI detector removed, `Header` and `FrameInput` `#[non_exhaustive]`; the Vs. database keyed by the new identity. Every one of 748 staged dumps booted and looked at: fixed from their documentation were the game database rewriting correct NES 2.0 headers (10 games), the iNES 1.0 dirty-tail mapper nibble, mappers 19, 64, 78, 105 (`$4017` inhibit), 153, 191 (non-power-of-two images), 218 and 226, Vs. work RAM and the Goonies palette, and PAL regions now reach iNES 1.0 games. The game database's corrections now reach Android, iOS and libretro; power-on options apply before a game's first frame; Power Cycle keeps the PPU/APU settings; an opt-in Famicom console model; the documented NTSC emphasis model. Performance: the /NMI removal -4.9% to -6.1% on three workloads, three campaign candidates adopted (-1.8% to -4.3% together); end to end only shipped `nestest_fast` is established (-5% to -9% vs v2.9.7), the rest a v2.9.9 lead. MiSTer: aspect/integer-scale/crop/2C03-palette menu options (not seen on hardware), a palette-gate, seed 2 kept after re-sweeps. `cargo test --release --workspace --features test-roms,commercial-roms` 3,367 passed / 0 failed; AccuracyCoin 144/144, nestest 0-diff; co-simulation 175/0/1 on-die, 176/0/1 off-die. **No hardware has run any bitstream.** Plan: `to-dos/plans/v2.9.8-vanguard-plan.md` | | **v2.9.7 "Tandem"** | The desktop's features on the web and on phones, and an A12 fix found by real games -- the eighth release of the v2.9.x line and the fourth of the line to v3.0.0. Release binaries are the `full` build (`release.yml`, the PGO/BOLT steps), and a new CI job builds `full` on macOS and Windows. Web: battery saves in IndexedDB, Vs. DualSystem with both screens. Mobile: FDS (host-supplied BIOS), NSF, and Vs. DualSystem through the bridge, plus a "Cancel opposite directions" switch (Swift uncompiled; run-sheet rows T1-T12). Desktop: cabinet save states (`T-PS-dual-savestate`), the Settings overclock wired (stock timing under movies and netplay), `client.frameskip` closed by design, the user-facing panels translatable (498 keys, 496 Spanish). The PPU reported one A12 pulse per scanline where the console makes eight. `read_vram` now reports every read, MC-ACC (4.3, promoted to Curated), mapper 91 and the J.Y. ASIC count at their documented rates (the Donkey Kong Country 4 map is fixed), and four MMC3-core boards gained MMC3's filter; cost about +1.9% on the nestest workloads. Time Diver (mapper 250) fixed; Uchuu Keibitai localised, open. `--features test-roms` 3,065 passed, 0 failed; AccuracyCoin 144/144, nestest 0-diff. MiSTer bitstreams: v2.9.2's pair byte for byte. Plan: `to-dos/plans/v2.9.7-tandem-plan.md` | diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 0d63d739..4a0187b7 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -65,8 +65,8 @@ android { // `scripts/release-automation/bump_release.py` from now on, starting // with the 3.0.0 cut. versionCode = MAJOR * 10000 + MINOR * 100 + // PATCH, so 20909 still rises past 20004. - versionCode = 30000 - versionName = "3.0.0" + versionCode = 30001 + versionName = "3.0.1" // No abiFilters here — set per buildType so release ships arm64 only // while debug keeps x86_64 for the emulator. // PLAY_BUILD is set per-flavor below (`false` for `foss`, `true` for `play`), diff --git a/crates/rustynes-cosim/Cargo.lock b/crates/rustynes-cosim/Cargo.lock index 3a5706da..170bc24e 100644 --- a/crates/rustynes-cosim/Cargo.lock +++ b/crates/rustynes-cosim/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "rustynes-apu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags", "libm", @@ -107,7 +107,7 @@ dependencies = [ [[package]] name = "rustynes-core" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags", "lz4_flex", @@ -121,7 +121,7 @@ dependencies = [ [[package]] name = "rustynes-cosim" -version = "3.0.0" +version = "3.0.1" dependencies = [ "rustynes-core", "sha2", @@ -129,7 +129,7 @@ dependencies = [ [[package]] name = "rustynes-cpu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags", "thiserror", @@ -137,7 +137,7 @@ dependencies = [ [[package]] name = "rustynes-mappers" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags", "rustynes-apu", @@ -146,7 +146,7 @@ dependencies = [ [[package]] name = "rustynes-ppu" -version = "3.0.0" +version = "3.0.1" dependencies = [ "bitflags", "libm", diff --git a/crates/rustynes-cosim/Cargo.toml b/crates/rustynes-cosim/Cargo.toml index 732d9ebe..39290976 100644 --- a/crates/rustynes-cosim/Cargo.toml +++ b/crates/rustynes-cosim/Cargo.toml @@ -8,7 +8,7 @@ description = "RustyNES as a co-simulation oracle for an external HDL device-und # The duplication is PINNED, not merely noticed: `cosim_manifest_audit.rs` in # `rustynes-test-harness` asserts these values still match the workspace's, so # drift fails a test instead of accumulating quietly. -version = "3.0.0" +version = "3.0.1" edition = "2024" rust-version = "1.99" license = "GPL-3.0-or-later" diff --git a/crates/rustynes-libretro/rustynes_libretro.info b/crates/rustynes-libretro/rustynes_libretro.info index 5f9b248e..39129e6a 100644 --- a/crates/rustynes-libretro/rustynes_libretro.info +++ b/crates/rustynes-libretro/rustynes_libretro.info @@ -5,7 +5,7 @@ supported_extensions = "nes|fds|unf|unif" corename = "RustyNES" license = "GPLv3+" permissions = "" -display_version = "v3.0.0" +display_version = "v3.0.1" categories = "Emulator" # Hardware Information diff --git a/docs/STATUS.md b/docs/STATUS.md index 050bf849..5744c484 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # RustyNES — Project Status Matrix -> **Current release: v3.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. Built on **v2.9.9 "Ballast"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** (2026-09-25) — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** (2026-09-25) — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** (2026-09-25) — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** (2026-09-24) — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** (2026-09-24) — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** (2026-09-24) — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** (2026-09-23) — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** (2026-09-23) — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** (2026-09-23) — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** (2026-09-23) — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** (2026-09-20) — the access does not increment, it pulses the load already there. A `$2007` access during rendering pulses the rendering pipeline's existing load rather than computing a private increment, and resolving against that same value is what closed the CHR-during-rendering divergence in the MiSTer sibling: `chrram-live` went from **32,861 of 61,440 differing pixels**, red for eight releases, to "All 61440 pixels match", and `chrram-fetch` from 21,941 to 18. Eleven prior variants had been measured correctly and the conclusion drawn from them was wrong — they swept what the `$2007` arm computes and never what it computes it from. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction** and were re-run anyway. The bitstream is re-cut at fitter seed 4 (+0.421 ns setup / +0.112 ns hold) and **no hardware has run it**. The board session is now **v2.9.2** and the hardware-verified core **v3.0.0**, after three audit lines ([ADR 0041](adr/0041-hardware-release-is-v3.0.0.md)). Built on **v2.6.22 "Rigging"** — the instruments for the board, built before the board. **No hardware has run any bitstream**; the session with the board (then planned as "Shakedown"; since ADR 0041, v2.9.2) is the hardware half and this is the non-hardware half of it, cut separately so that name keeps meaning what it says. AccuracyCoin now reads back from hardware as BYTES rather than as a photograph — a mirror ROM copies its result vector into the battery-backed save window, with a byte budget that proves the patch moved nothing and a simulation control that proves it changed no answer. The catalog turned out to have **149 rows and 144 results**: five `Power On State` rows share upstream's omit-sentinel, and counting them made the headline a function of *when the run was sampled*. **144/144 and nestest 0-diff are unchanged** — the count stopped depending on the sampling instant. And the `46199ae4` re-sync turned out to have been half-done: three goldens described a ROM no longer in the tree, which no gate could see because `rom_sha256` was written into every manifest and compared to nothing. Built on **v2.6.21 "Steward"** — the board arrives, and the core is not ready for it. Battery saves exist for the first time; the CHR-during-rendering gate the retrospective audit asked for is added and is **RED at 32,861 of 61,440 pixels**, with the obvious fix measured insufficient at 715 recovered; two gates existed that nothing ran; and the deploy loop the runbook prescribed was built, which found two defects in its own spec. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.20 "Telltale"** — the counter had no reader, and two knobs turned out to be one decision. The FPGA core's OAM2 address counter was write-only for a release, so `$2004`'s post-fetch rest value was a hardcoded index 0; making it observable took the DUT to **AccuracyCoin 149 of 149** and the co-simulation ladder to **150 of 150, 0 failed**. Restoring the increment window v2.6.19 had narrowed then broke sprite rendering by six pixels, because the window and the deleted dot-257 latch are ONE decision with two self-consistent answers and only one of them is the hardware's. The shipped configuration restores the latch: window 256, the dot-257 freeze latch back and consumed by sprite fetch, and `$2004` reading the live counter. A CHR-RAM write gate was added. **v2.6.21 corrects this sentence: it did NOT close the coverage the audit named.** The audit named writes *while rendering is enabled*, and PROGRAM53 writes with `PPUMASK = 0` and renders afterwards. The real case is gated at v2.6.21 and is RED -- 32,861 of 61,440 pixels. The emulation core is unchanged, so **144/144 and nestest 0-diff hold by construction**. Built on **v2.6.19 "Accession"** — the DUT absorbs two releases of oracle behaviour, and the seed rule catches something for the first time. The FPGA core implements the OAM2 address counter it never had and stops acting on a `$2001` change that lands during dot 256 -- AccuracyCoin on the DUT **148 of 149**, the ladder **147 of 147**. The fitter pin is re-derived over ten seeds and stays at 3. (The contemporaneous claim that seed 1 stopped closing is withdrawn -- that sweep recompiled in place and was order-dependent; seed 1 closes, and seed 8 is the one that does not.) The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.18** (2026-09-12) — **"Errata"**, the recorded cause was wrong in three ways, and the last AccuracyCoin entry closes at 144/144 -- a `$2001` change during dot N must not act on dot N, at the dot-256 vertical increment and the dot-339 OAM2 reset. Built on **v2.6.17** (2026-09-11) — **"Terminus"**, the accuracy battery grows to 144 assigned tests and one of the new ones names the dots a write lands on -- this core is two dots early, measured twice on two independent `$2001` writes, and moving it is refuted by the gate this version wrote in advance, so the documented compensation stays and 143 of 144 ships. Built on **v2.6.16 "Interlock"** (2026-09-04) — the arbiter's numbers describe a stimulus, not the console -- and the console meets the CPU's deadline at zero margin, on every one of 240,303 requests, where the gate said it missed by four. Built on **v2.6.15 "Warrant"** (2026-09-04) — the claims v2.7.0 will make become checkable, and the instrument pays the oracle back. Built on **v2.6.14 "Docket"** (2026-09-03) — 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 the list in `to-dos/mister/contribution-checklist.md` decides whether the core is ready -- and it had 30 boxes, 16 unticked, and FOURTEEN OF THOSE SIXTEEN saying nothing at all about why. That ambiguity is the defect: an unticked box with no reason cannot be told apart from work outstanding, work blocked outside this repository, and WORK ALREADY DONE AND NEVER TICKED, and the third case occurred five times -- the provenance CI job, the SPDX sweep, the firewall statement, the AccuracyCoin vector and the preservation-value case were all true and all unticked, so the list reported the project as further from submission than it is by a fifth of its own length. ONE BOX COULD NEVER HAVE BEEN TICKED HONESTLY: it asked that `docs/provenance.md` state "that no NES core was ever opened", and that same document's section Do not self-certify forbids exactly that class of finished claim, so satisfying the box required writing the one sentence the provenance rules exist to prevent -- a wrong requirement rather than a missing tick, and only reading every item found it. RE-MEASURING THE TICKED HALF found two more, which is v2.6.9's lesson in a different document: `RustyNES.sdc` still said "there is exactly one core clock", true of v2.6.3 and false since v2.6.13, which added a 4x SDRAM clock and a phase-shifted pin clock that the shipped timing report names alongside it; and the `.qsf` entry said all 109 pin assignments come from `sys/sys.tcl`, where 109 is what that script supplies before stopping short of the I/O board, so a core must also source one of two 36-assignment variants -- 145 in total, from two scripts. THE ALARMING READING OF THE FIRST WAS CHECKED BEFORE IT WAS WRITTEN DOWN: it looks as though the framework's `set_clock_groups -exclusive` might be cutting the console-to-SDRAM crossing and leaving ADR 0039's safety argument unfalsifiable, and it is not -- exclusive cuts paths BETWEEN groups, all three outputs match one glob, and v2.6.13's own -24.769 ns measurement of that crossing is only observable because those paths are analysed. THE NAMING DIVERGENCE IS MEASURED RATHER THAN PARAPHRASED, from `Main_MiSTer/file_io.cpp` instead of the wiki: `get_display_name` searches for the literal underscore-two-zero and TRUNCATES THE DISPLAY NAME THERE, taking the rest as a datecode, and `DirentComp` groups by that truncated name -- so a version-named bitstream makes every release a SEPARATE CORE ENTRY, named for the version rather than the core, ordered alphabetically, which puts v2.6.9 after v2.6.13. The fix is one line and is NOT taken here, because version-naming is a maintainer decision with a stated rationale and reversing it is not an audit's call. THE TASK BOARD HAD THE SAME DEFECT AND ONE ROW WORSE: four delivered items never ticked, a hardware row still naming a version seven releases past, and an SDRAM row whose precondition -- "after a board exists" -- v2.6.13 simply did not follow, having accepted the controller against a behavioural model written from the datasheet instead; that is rung 7's own recorded lesson repeating one row below where it is written, since the blocker applied to hardware ACCEPTANCE rather than to building the thing. A CLAIM v2.6.13 SHIPPED IS RETRACTED: answering a review finding, I wrote that MMC1 was the sixth approved family and not yet implemented, and the cartridge decodes mapper 1 at three sites with a registered gate green since rung 7 opened -- asserted from memory inside a reply correcting somebody else's reading of the same line. The gate that keeps all this true asserts a SHAPE rather than a judgement and is demonstrated by five mutations, one of which proves the continuation-line folding load-bearing by producing nine false violations without it. The bitstream is BYTE-IDENTICAL to v2.6.13's, which is the point: the only sibling change is a comment, and an identical artifact demonstrates it. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.13 "Slack"** (2026-09-03) — 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, all 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 a single figure read off the fetch structure and never measured; `nes_top`'s `CHR_LAT` sweep asks the console directly, and there are THREE answers: a background or sprite fetch tolerates 28 cycles and uses 17, the PPUDATA 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 PPUDATA port could never fit, half its budget being the crossing, so it leaves the shared bus entirely: `ppu2c02` gains `BUFFER_HANDSHAKE` and fills its read buffer through a port of its own on the arbiter, affordable because the CPU does not read that buffer until its next PPUDATA 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; `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read a zero byte where the oracle reads one, one and two. The fix REMOVES the address rather than correcting it: `cart.sv` already publishes the translation for the fetch path, so the request carries none and 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. The control and the code differed by a single cycle, which 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 that 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, and a variable holding its previous value on every live path is a latch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue ("outside BUFFER_HANDSHAKE neither signal ever moves", which is true and is exactly the condition). 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 the off-die failures exactly -- closed with a stamp-file prerequisite, demonstrated by mutation. THE OPEN ROW: accesses no longer auto-precharge, a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM is respected by an early close in idle, and two defects came out of the rewrite -- re-entering idle with the request still asserted issued every access TWICE, and subtracting one from CAS the way the other waits are computed broke every read to all zeroes, CAS being "data appears at cycle N" rather than a command-to-command gap. Two MiSTer tickets close: `sdram_sz` is consumed VALIDITY BIT FIRST, so `absent` is deliberately not `!present` and a power-on all zeroes cannot read as "no board" (gated exhaustively over all 65,536 values), and `status_menumask` is computed rather than tied off, greying Reset whenever the console is already held in reset. 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 SDRAM add-on while rung 7's five mapper families fit on the die at 468 of 553 blocks. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.12 "Groundwork"** (2026-09-02) — the bitstream was an NROM-only console. Rung 7 landed five mapper families and 142 co-simulation gates verify them, and the layer that turns that RTL into a bitstream was never told: `rtl/emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND -- mapper 0 for EVERY cartridge, `prg_8k_count = 0` collapsing PRG to an 8 KiB window, and CHR forced to RAM. The declared 256 KiB PRG and 128 KiB CHR were implemented as 8 KiB each; connecting three wires takes block memory from 666,061 to 3,680,717 bits and timing still closes at all four corners. NOTHING COULD HAVE CAUGHT IT: simulation cannot, because `emu.sv` is not in the testbench file list and the harness drives those ports itself, so all 142 gates exercised a correctly-configured cartridge; and Quartus DID say so three times, in messages that cite an INSTANCE path rather than a file and are absent from the "0 errors, N warnings" tally, so the existing checker read 0 of 125. Two gates close that -- one fails on an unconnected pin of any module this repository declares, the other pins the warning SET rather than its count -- and both are demonstrated to fail by mutation. The `hps_io` tie-off audit that followed raised nine `T-MISTER-*` tickets, and annotating each with a blocker turned "none of these landed" into a measurement: not one is blocked on EFFORT, so the list is the rung-6 agenda rather than a backlog. `T-MISTER-SAVE` was attempted and refuted -- every save route terminates in `hps_io`, which no gate here instantiates. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.11 "Exposure"** (2026-09-02) — a picture is a gate the ladder did not have. All 141 co-simulation gates THEN IN THE SUITE were green (it ends this release at 142, the one it added) and TWO OF SIX commercial games rendered wrong -- a CHR-RAM write was taking the shared-pin composite address built for FETCHES instead of `v`, so the layout was right and the tiles were scrambled. The split is exactly CHR-ROM against CHR-RAM, which named the mechanism before any tracing, and a CONTROL says it is not v2.6.10's regression: the pre-M10K-fix RTL differs by the IDENTICAL 16,565 pixels, so the defect dates from the cartridge landing in v2.6.9. It was not UNREACHED -- the DUT asserts `chr_wr` 9,600 times in the Battletoads run -- it was UNCOMPARED: only THREE of the 141 gates compare a framebuffer, all three ship CHR-ROM, every other gate is CPU-side, and AccuracyCoin, the widest gate in the suite, is CHR-ROM too. The rung-7 gates' own comment says what they are for -- "these gates are about BANKING and nothing else" -- and it was accurate, and it was the whole coverage. Six commercial titles now render byte-identically to the oracle over all 61,440 pixels, published as a montage built by a script that REFUSES to publish a tile that differs from the oracle. The v2.6.10 bitstream carries the defect and a published version is immutable, so the corrected `.rbf` ships here. The same release finds EIGHT release leads describing v2.6.10 with v2.6.9's summary, and v2.6.9 gone from the lineage entirely, with `docs/STATUS.md` naming v2.6.10 under the codename "Abeyance" -- every existing check passing CORRECTLY, because they pin the version TOKEN and the token was right. Prose cannot be audited; an ORDERING can, so two gates are added and both are demonstrated to fail by mutation. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.10 "Inference"** (2026-09-01) — the cartridge meets the synthesiser. Five cartridge boards verified across 141 co-simulation gates had **never been through Quartus**, and Analysis & Synthesis refused the design: `chr` was written from TWO `always_ff` blocks, which cannot infer as one M10K, so 128 KB of CHR stayed in flip-flops -- **1,048,576 registers against roughly 166,000**. Simulation cannot ask this question: Verilator accepts both forms without complaint. It is v2.6.6's finding one layer out -- that release established that an M10K read is REGISTERED, this one that a correctly registered memory still will not infer with two writers. The fitter was also throttling itself under Auto Fit while the `.qsf` carried no optimisation assignments at all: at full effort **all six seeds close** where two had failed, so the effort settings move the whole distribution across zero and the seed only picks where in it you land -- and the project had been pinned to seed 4, the WORST of the six. Pinned at seed 3, +0.531 ns setup and +0.099 ns hold, byte-identical across two independent compiles. The bitstream v2.6.9 could not produce ships here. Built on **v2.6.9 "Abeyance"** (2026-08-31) — an exclusion hides improvement as well as regression, and both denied co-simulation streams close. 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", and 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. Taking it from the latch makes the stream IDENTICAL on all 357,361 overlapping cycles and all 88 checkpoints, and the local is now dead and deleted. The phrase "by design" is what 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, because the PLANNED mechanism was refuted by its own mutation pass: an allowance by checkpoint index cannot work on a rolling hash, since 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. A per-cycle nine-field comparator with a scoped allowance costs ONE cycle of coverage instead of 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. Built on **v2.6.8 "Arrears"** (2026-08-31) — the gates the previous release fixed and never widened. A deny list is an assertion about the THING UNDER TEST, so v2.6.7 changing both the DUT and the harness re-opened every exclusion -- and nothing re-measured one. FOUR of the six denied checkpoint streams were passing (`irqlat048`, `ppuvbl023`, `ppuvbl024`, `ppuvbl025`), and THREE of those were not run by the suite at all, so removing them from the deny list 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 all 5,062,680 overlapping cycles now match, with the window DERIVED from the manifest. CAVEAT C4 CLOSES by demonstration: nestest raises `nmi_line` on 3,592 cycles and had no nine-field comparison at all, so wiring it makes an `o.nmi_line` mutation CAUGHT at checkpoint 28 of 1,237 while the bus gate passes the identical run. Suite 128 passed, 0 failed, 0 skipped; nine-field comparisons 51 -> 57. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no board is attached, confirmed by checking. Built on **v2.6.7 "Detent"** (2026-08-30) — the bitstream becomes a published release artifact and a one-cycle disagreement is pinned 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, which produced one and withheld it because no hardware had run it: the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one, and the caution moves from an absence into a disclosure naming what the ladder cannot reach by construction (the PPU gate compares the pre-palette index and the APU gate per-channel integer levels, so the palette, the video timing constants, the audio absolute level and its band-limiting all sit downstream of every gate). The build is REPRODUCIBLE and that is now measured rather than argued -- a from-scratch compile and an incremental one produce a byte-identical bitstream -- which is also how v2.6.6's published slack figures came to be WITHDRAWN: no corner of a clean rebuild reproduces them, the innocent explanation (a different timing corner) was checked first and refuted, and the correct pair is +0.108 ns setup and +0.042 ns hold at the binding corner. THE RELEASE GATE WAS READING THE WRONG CORNER -- Slow 100C is not the binding one on this design, so a bitstream failing at Slow -40C would have passed while the gate reported three times the real margin -- and the checker that reads it was wrong twice before mutation found both: it first extracted ZERO rows from both summary tables and reported that as "no negative slack", then, once fixed, reported FOURTEEN clocks from a report emptied of its data, having run past the closing rule into the next tables. Caveat C2 splits in two. The first residual was a TRACE OBSERVATION POINT -- the harness built its record after eight of a CPU cycle's twelve master clocks while the oracle reads at end-of-cycle, and the frame-counter interrupt asserts on the final edge -- and closing it took checkpoint comparisons from 3 to 11 and failures at one checkpoint from 30 to 3. The second is REAL, and the FIRST fix for it was REFUTED in a way that found the right one: moving all four effects of the write one cycle later to match the oracle drops blargg from 11/11 to 4 of 11, one of the seven being the ROM written to probe exactly that timing. Read as a measurement that PROVES the sequencer's maturation is correctly placed, which leaves only the other effects the same write schedules -- so separating ONLY the interrupt clear lands it at write+3, the documented cycle, while the frame counter's zeroing stays put. Checkpoint streams go from 11 identical to 52 of 58 and the suite from 87 to 122, with blargg still 11/11 and the bus still matching on all 2,680,239 overlapping cycles. The checkpoint gate is registered over 51 comparisons and STATES ITS BLIND SPOT: not one gated golden ever raises an NMI, so it cannot catch an nmi_line defect, and the attempt to close that hole found a FOURTH divergence cluster that three goldens had been hiding inside a "26 skipped" tally line. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.6 "Chassis"** (2026-08-29) — the console becomes a MiSTer core -- `sys/` vendored byte-identical against `Template_MiSTer@3ea1134c` (57 files, 0 content differences), a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 into a Cyclone V bitstream with 0 errors, timing CLOSED, and a warning count taken from 111 to THREE -- all three inside the vendored framework or Quartus's own megafunction, none of them citing this project's RTL (worst setup +0.086 ns and worst hold +0.096 ns at the binding corners, seed 3 -- v2.6.7 also withdraws the +0.363/+0.245 v2.6.6 published, which a clean rebuild of that configuration reproduces at no corner, TNS 0.000 on every clock; the console's own clock +13.514 ns at 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, because the cartridge memories had to be rewritten: an M10K read is REGISTERED, so 40 KiB of asynchronously-read cartridge was 393,216 registers against roughly 166,000 available, under a comment claiming it inferred block RAM from the source style alone, and the README had stated the correct rule since v2.4.3. 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, because the mixer output is unipolar with silence at zero and the framework maps unsigned silence to -32768, and two OSD scanline options did nothing because VGA_SL was tied to zero. And a convention enforced by a glob has no error message: sys_top.sdc groups the core 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 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. Built on **v2.6.5 "Muster"** (2026-08-29) — 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, where the same gate read 5 of 146 at the version's start. A muster is a roll call where every name is called AND answered, which is the two-clause acceptance exactly. Five PPU defects close the last six differing entries and four were invisible to every gate that existed when the version opened: the background shift registers' RELOAD and their shift clock need SEPARATE gates (with one shared gate the serial-in test was not merely failing but ARITHMETICALLY UNREACHABLE, since reload dots are absolute and the reload discards the low seven bits, so a serial-in one can never reach bit 7 on any alignment — and modelling both structures reproduces BOTH measured shifter values); the sprite X counters are NOT gated on rendering, which AccuracyCoin states outright and the ROM that states it passes either way, because it expects no hit at X=254 and a sprite shoved 18 dots right is also off the line; the PPUADDR second-write v-copy is DELAYED, as the wiki says inside the write sequence itself, swept 1 to 4 dots against a control at 8 and 12 that fails; and the pre-render line CLEARS secondary OAM, without which scanline 0 draws what scanline 239 left — no sprite can ever render on scanline 0, because OAM Y is one less than the display row, and a sprite-0 probe over the full 134 M-cycle battery found 24 hits with four of them there; and the octal latch holding across the read dot, which is verified by exactly ONE gate and was unverifiable until the v-copy delay landed, the two composing the hybrid address together and neither producing it alone. A DIAGNOSIS IS RETRACTED: the residual was read as a two-dot CPU/PPU alignment error from comparing dot spans across two instruments, and at the committed alignment the two consoles execute identical pc, bus_addr and bus_access for 1,695,131 cycles while a two-dot shift moves the first fork back to 593,228 and takes the differing share from 5.13% to 66.80%. The oracle changes on the default path, so AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff are VERIFIED, not asserted. Built on **v2.6.4 "Rubric"** (2026-08-26) — OAM DMA lands and all nine AccuracyCoin disagreements close, every rule that closed the last three stated by the test ROM and by neither nesdev page — and then the gate that certified them is measured to cover 88 of 146 entries. The emulation core is unchanged. Built on **v2.6.3 "Mainspring"** (2026-08-25) — the DUT runs on one master clock, and four enables that were never enabling — plus AccuracyCoin end to end and a status vector that names its disagreements by test. The emulation core is unchanged. Built on **v2.6.2 "Witness"** (2026-08-24) — rung 4 closes: blargg APU battery 11/11 on the co-simulation DUT, six defects no self-written gate could see, and a suite that had been asserting nothing for five minor releases. The emulation core is unchanged. Built on **v2.6.1 "Interleave"** (2026-08-24) — the DMC and its DMA cycle steal in the MiSTer co-simulation DUT, cycle-exact on the bus. The emulation core is unchanged. Built on **v2.6.0 "Assay"** (2026-08-24) — the triangle, the noise channel and the sweep unit **in the MiSTer co-simulation DUT** — and an audit of how much of the APU was fitted to the oracle rather than derived from documentation. The emulation core is unchanged. Built on **v2.5.9 "Overture"** (2026-08-24) — rung 4 opens: the two pulse channels, the frame counter, and four ROM defects the stimulus measurement found first. Built on **v2.5.8 "Blanking"** (2026-08-24) — VBlank, NMI and the PPUSTATUS race close rung 3 — and both fixes were deletions. Built on **v2.5.7 "Collimation"** (2026-08-24) — sprite rendering closes exact — the phase was wrong by two dots, and every window was compensating. Built on **v2.5.6 "Vestige"** (2026-08-23) — Sprite evaluation closes: all 59,993 overlapping cycles match, nine of nine behavioural mutants caught and two proved inert (announced as seven of eight at the cut), and the fix is a byte index that outlives the walk that set it. Built on **v2.5.5 "Raster"** (2026-08-23) — the first full frame, and three blind spots in the stimulus that fed it. Built on **v2.5.4 "Escapement"** (2026-08-23) — the background fetch pipeline, and an access two dots early that five gates could not see. Built on **v2.5.3 "Hysteresis"** (2026-08-23) — toggling rendering takes effect three dots after the write, and four instruments to prove it. Built on **v2.5.2 "Dormant"** (2026-08-23) — the 2C02 register file, and a gate that passed while testing nothing. Built on **v2.5.1 "Retrace"** (2026-08-23) — the interrupt sweep closes rung 2, and a gate reported a pass it could not have earned. Built on **v2.5.0 "Rungwork"** (2026-08-23) — the 6502 rung, and the two gates it cannot reach. Built on **v2.4.9 "Plumbline II"** (2026-08-23) — the bus half of rung 2, and what it found the day it existed. Built on **v2.4.8 "Palimpsest"** (2026-08-23) — read-modify-write, and a gate that cannot see its own subject. Built on **v2.4.7 "Keystone"** (2026-08-23) — the stack closes, and a dead line proves itself dead. Built on **v2.4.6 "Abacus"** (2026-08-22) — the core learns arithmetic. Built on **v2.4.5 "Compass"** (2026-08-22) — the core reaches memory, and chooses. Built on **v2.4.4 "Ignition"** (2026-08-22) — the first real RTL -- the 6502's eight-cycle reset and the implied opcode group, matching the oracle on all seven CPU fields (29 +> **Current release: v3.0.1** (2026-10-07) — **"Mortar"**, a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** (2026-09-25) — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** (2026-09-25) — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** (2026-09-25) — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** (2026-09-24) — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** (2026-09-24) — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** (2026-09-24) — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** (2026-09-23) — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** (2026-09-23) — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** (2026-09-23) — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** (2026-09-23) — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** (2026-09-20) — the access does not increment, it pulses the load already there. A `$2007` access during rendering pulses the rendering pipeline's existing load rather than computing a private increment, and resolving against that same value is what closed the CHR-during-rendering divergence in the MiSTer sibling: `chrram-live` went from **32,861 of 61,440 differing pixels**, red for eight releases, to "All 61440 pixels match", and `chrram-fetch` from 21,941 to 18. Eleven prior variants had been measured correctly and the conclusion drawn from them was wrong — they swept what the `$2007` arm computes and never what it computes it from. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction** and were re-run anyway. The bitstream is re-cut at fitter seed 4 (+0.421 ns setup / +0.112 ns hold) and **no hardware has run it**. The board session is now **v2.9.2** and the hardware-verified core **v3.0.0**, after three audit lines ([ADR 0041](adr/0041-hardware-release-is-v3.0.0.md)). Built on **v2.6.22 "Rigging"** — the instruments for the board, built before the board. **No hardware has run any bitstream**; the session with the board (then planned as "Shakedown"; since ADR 0041, v2.9.2) is the hardware half and this is the non-hardware half of it, cut separately so that name keeps meaning what it says. AccuracyCoin now reads back from hardware as BYTES rather than as a photograph — a mirror ROM copies its result vector into the battery-backed save window, with a byte budget that proves the patch moved nothing and a simulation control that proves it changed no answer. The catalog turned out to have **149 rows and 144 results**: five `Power On State` rows share upstream's omit-sentinel, and counting them made the headline a function of *when the run was sampled*. **144/144 and nestest 0-diff are unchanged** — the count stopped depending on the sampling instant. And the `46199ae4` re-sync turned out to have been half-done: three goldens described a ROM no longer in the tree, which no gate could see because `rom_sha256` was written into every manifest and compared to nothing. Built on **v2.6.21 "Steward"** — the board arrives, and the core is not ready for it. Battery saves exist for the first time; the CHR-during-rendering gate the retrospective audit asked for is added and is **RED at 32,861 of 61,440 pixels**, with the obvious fix measured insufficient at 715 recovered; two gates existed that nothing ran; and the deploy loop the runbook prescribed was built, which found two defects in its own spec. The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.20 "Telltale"** — the counter had no reader, and two knobs turned out to be one decision. The FPGA core's OAM2 address counter was write-only for a release, so `$2004`'s post-fetch rest value was a hardcoded index 0; making it observable took the DUT to **AccuracyCoin 149 of 149** and the co-simulation ladder to **150 of 150, 0 failed**. Restoring the increment window v2.6.19 had narrowed then broke sprite rendering by six pixels, because the window and the deleted dot-257 latch are ONE decision with two self-consistent answers and only one of them is the hardware's. The shipped configuration restores the latch: window 256, the dot-257 freeze latch back and consumed by sprite fetch, and `$2004` reading the live counter. A CHR-RAM write gate was added. **v2.6.21 corrects this sentence: it did NOT close the coverage the audit named.** The audit named writes *while rendering is enabled*, and PROGRAM53 writes with `PPUMASK = 0` and renders afterwards. The real case is gated at v2.6.21 and is RED -- 32,861 of 61,440 pixels. The emulation core is unchanged, so **144/144 and nestest 0-diff hold by construction**. Built on **v2.6.19 "Accession"** — the DUT absorbs two releases of oracle behaviour, and the seed rule catches something for the first time. The FPGA core implements the OAM2 address counter it never had and stops acting on a `$2001` change that lands during dot 256 -- AccuracyCoin on the DUT **148 of 149**, the ladder **147 of 147**. The fitter pin is re-derived over ten seeds and stays at 3. (The contemporaneous claim that seed 1 stopped closing is withdrawn -- that sweep recompiled in place and was order-dependent; seed 1 closes, and seed 8 is the one that does not.) The emulation core is unchanged, so **AccuracyCoin 144/144 and nestest 0-diff hold by construction**. Built on **v2.6.18** (2026-09-12) — **"Errata"**, the recorded cause was wrong in three ways, and the last AccuracyCoin entry closes at 144/144 -- a `$2001` change during dot N must not act on dot N, at the dot-256 vertical increment and the dot-339 OAM2 reset. Built on **v2.6.17** (2026-09-11) — **"Terminus"**, the accuracy battery grows to 144 assigned tests and one of the new ones names the dots a write lands on -- this core is two dots early, measured twice on two independent `$2001` writes, and moving it is refuted by the gate this version wrote in advance, so the documented compensation stays and 143 of 144 ships. Built on **v2.6.16 "Interlock"** (2026-09-04) — the arbiter's numbers describe a stimulus, not the console -- and the console meets the CPU's deadline at zero margin, on every one of 240,303 requests, where the gate said it missed by four. Built on **v2.6.15 "Warrant"** (2026-09-04) — the claims v2.7.0 will make become checkable, and the instrument pays the oracle back. Built on **v2.6.14 "Docket"** (2026-09-03) — 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 the list in `to-dos/mister/contribution-checklist.md` decides whether the core is ready -- and it had 30 boxes, 16 unticked, and FOURTEEN OF THOSE SIXTEEN saying nothing at all about why. That ambiguity is the defect: an unticked box with no reason cannot be told apart from work outstanding, work blocked outside this repository, and WORK ALREADY DONE AND NEVER TICKED, and the third case occurred five times -- the provenance CI job, the SPDX sweep, the firewall statement, the AccuracyCoin vector and the preservation-value case were all true and all unticked, so the list reported the project as further from submission than it is by a fifth of its own length. ONE BOX COULD NEVER HAVE BEEN TICKED HONESTLY: it asked that `docs/provenance.md` state "that no NES core was ever opened", and that same document's section Do not self-certify forbids exactly that class of finished claim, so satisfying the box required writing the one sentence the provenance rules exist to prevent -- a wrong requirement rather than a missing tick, and only reading every item found it. RE-MEASURING THE TICKED HALF found two more, which is v2.6.9's lesson in a different document: `RustyNES.sdc` still said "there is exactly one core clock", true of v2.6.3 and false since v2.6.13, which added a 4x SDRAM clock and a phase-shifted pin clock that the shipped timing report names alongside it; and the `.qsf` entry said all 109 pin assignments come from `sys/sys.tcl`, where 109 is what that script supplies before stopping short of the I/O board, so a core must also source one of two 36-assignment variants -- 145 in total, from two scripts. THE ALARMING READING OF THE FIRST WAS CHECKED BEFORE IT WAS WRITTEN DOWN: it looks as though the framework's `set_clock_groups -exclusive` might be cutting the console-to-SDRAM crossing and leaving ADR 0039's safety argument unfalsifiable, and it is not -- exclusive cuts paths BETWEEN groups, all three outputs match one glob, and v2.6.13's own -24.769 ns measurement of that crossing is only observable because those paths are analysed. THE NAMING DIVERGENCE IS MEASURED RATHER THAN PARAPHRASED, from `Main_MiSTer/file_io.cpp` instead of the wiki: `get_display_name` searches for the literal underscore-two-zero and TRUNCATES THE DISPLAY NAME THERE, taking the rest as a datecode, and `DirentComp` groups by that truncated name -- so a version-named bitstream makes every release a SEPARATE CORE ENTRY, named for the version rather than the core, ordered alphabetically, which puts v2.6.9 after v2.6.13. The fix is one line and is NOT taken here, because version-naming is a maintainer decision with a stated rationale and reversing it is not an audit's call. THE TASK BOARD HAD THE SAME DEFECT AND ONE ROW WORSE: four delivered items never ticked, a hardware row still naming a version seven releases past, and an SDRAM row whose precondition -- "after a board exists" -- v2.6.13 simply did not follow, having accepted the controller against a behavioural model written from the datasheet instead; that is rung 7's own recorded lesson repeating one row below where it is written, since the blocker applied to hardware ACCEPTANCE rather than to building the thing. A CLAIM v2.6.13 SHIPPED IS RETRACTED: answering a review finding, I wrote that MMC1 was the sixth approved family and not yet implemented, and the cartridge decodes mapper 1 at three sites with a registered gate green since rung 7 opened -- asserted from memory inside a reply correcting somebody else's reading of the same line. The gate that keeps all this true asserts a SHAPE rather than a judgement and is demonstrated by five mutations, one of which proves the continuation-line folding load-bearing by producing nine false violations without it. The bitstream is BYTE-IDENTICAL to v2.6.13's, which is the point: the only sibling change is a comment, and an identical artifact demonstrates it. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.13 "Slack"** (2026-09-03) — 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, all 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 a single figure read off the fetch structure and never measured; `nes_top`'s `CHR_LAT` sweep asks the console directly, and there are THREE answers: a background or sprite fetch tolerates 28 cycles and uses 17, the PPUDATA 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 PPUDATA port could never fit, half its budget being the crossing, so it leaves the shared bus entirely: `ppu2c02` gains `BUFFER_HANDSHAKE` and fills its read buffer through a port of its own on the arbiter, affordable because the CPU does not read that buffer until its next PPUDATA 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; `ppu-misc-2007-stress` passed off-die throughout while the two CNROM gates read a zero byte where the oracle reads one, one and two. The fix REMOVES the address rather than correcting it: `cart.sv` already publishes the translation for the fetch path, so the request carries none and 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. The control and the code differed by a single cycle, which 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 that 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, and a variable holding its previous value on every live path is a latch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue ("outside BUFFER_HANDSHAKE neither signal ever moves", which is true and is exactly the condition). 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 the off-die failures exactly -- closed with a stamp-file prerequisite, demonstrated by mutation. THE OPEN ROW: accesses no longer auto-precharge, a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM is respected by an early close in idle, and two defects came out of the rewrite -- re-entering idle with the request still asserted issued every access TWICE, and subtracting one from CAS the way the other waits are computed broke every read to all zeroes, CAS being "data appears at cycle N" rather than a command-to-command gap. Two MiSTer tickets close: `sdram_sz` is consumed VALIDITY BIT FIRST, so `absent` is deliberately not `!present` and a power-on all zeroes cannot read as "no board" (gated exhaustively over all 65,536 values), and `status_menumask` is computed rather than tied off, greying Reset whenever the console is already held in reset. 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 SDRAM add-on while rung 7's five mapper families fit on the die at 468 of 553 blocks. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.12 "Groundwork"** (2026-09-02) — the bitstream was an NROM-only console. Rung 7 landed five mapper families and 142 co-simulation gates verify them, and the layer that turns that RTL into a bitstream was never told: `rtl/emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND -- mapper 0 for EVERY cartridge, `prg_8k_count = 0` collapsing PRG to an 8 KiB window, and CHR forced to RAM. The declared 256 KiB PRG and 128 KiB CHR were implemented as 8 KiB each; connecting three wires takes block memory from 666,061 to 3,680,717 bits and timing still closes at all four corners. NOTHING COULD HAVE CAUGHT IT: simulation cannot, because `emu.sv` is not in the testbench file list and the harness drives those ports itself, so all 142 gates exercised a correctly-configured cartridge; and Quartus DID say so three times, in messages that cite an INSTANCE path rather than a file and are absent from the "0 errors, N warnings" tally, so the existing checker read 0 of 125. Two gates close that -- one fails on an unconnected pin of any module this repository declares, the other pins the warning SET rather than its count -- and both are demonstrated to fail by mutation. The `hps_io` tie-off audit that followed raised nine `T-MISTER-*` tickets, and annotating each with a blocker turned "none of these landed" into a measurement: not one is blocked on EFFORT, so the list is the rung-6 agenda rather than a backlog. `T-MISTER-SAVE` was attempted and refuted -- every save route terminates in `hps_io`, which no gate here instantiates. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on **v2.6.11 "Exposure"** (2026-09-02) — a picture is a gate the ladder did not have. All 141 co-simulation gates THEN IN THE SUITE were green (it ends this release at 142, the one it added) and TWO OF SIX commercial games rendered wrong -- a CHR-RAM write was taking the shared-pin composite address built for FETCHES instead of `v`, so the layout was right and the tiles were scrambled. The split is exactly CHR-ROM against CHR-RAM, which named the mechanism before any tracing, and a CONTROL says it is not v2.6.10's regression: the pre-M10K-fix RTL differs by the IDENTICAL 16,565 pixels, so the defect dates from the cartridge landing in v2.6.9. It was not UNREACHED -- the DUT asserts `chr_wr` 9,600 times in the Battletoads run -- it was UNCOMPARED: only THREE of the 141 gates compare a framebuffer, all three ship CHR-ROM, every other gate is CPU-side, and AccuracyCoin, the widest gate in the suite, is CHR-ROM too. The rung-7 gates' own comment says what they are for -- "these gates are about BANKING and nothing else" -- and it was accurate, and it was the whole coverage. Six commercial titles now render byte-identically to the oracle over all 61,440 pixels, published as a montage built by a script that REFUSES to publish a tile that differs from the oracle. The v2.6.10 bitstream carries the defect and a published version is immutable, so the corrected `.rbf` ships here. The same release finds EIGHT release leads describing v2.6.10 with v2.6.9's summary, and v2.6.9 gone from the lineage entirely, with `docs/STATUS.md` naming v2.6.10 under the codename "Abeyance" -- every existing check passing CORRECTLY, because they pin the version TOKEN and the token was right. Prose cannot be audited; an ORDERING can, so two gates are added and both are demonstrated to fail by mutation. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.10 "Inference"** (2026-09-01) — the cartridge meets the synthesiser. Five cartridge boards verified across 141 co-simulation gates had **never been through Quartus**, and Analysis & Synthesis refused the design: `chr` was written from TWO `always_ff` blocks, which cannot infer as one M10K, so 128 KB of CHR stayed in flip-flops -- **1,048,576 registers against roughly 166,000**. Simulation cannot ask this question: Verilator accepts both forms without complaint. It is v2.6.6's finding one layer out -- that release established that an M10K read is REGISTERED, this one that a correctly registered memory still will not infer with two writers. The fitter was also throttling itself under Auto Fit while the `.qsf` carried no optimisation assignments at all: at full effort **all six seeds close** where two had failed, so the effort settings move the whole distribution across zero and the seed only picks where in it you land -- and the project had been pinned to seed 4, the WORST of the six. Pinned at seed 3, +0.531 ns setup and +0.099 ns hold, byte-identical across two independent compiles. The bitstream v2.6.9 could not produce ships here. Built on **v2.6.9 "Abeyance"** (2026-08-31) — an exclusion hides improvement as well as regression, and both denied co-simulation streams close. 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", and 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. Taking it from the latch makes the stream IDENTICAL on all 357,361 overlapping cycles and all 88 checkpoints, and the local is now dead and deleted. The phrase "by design" is what 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, because the PLANNED mechanism was refuted by its own mutation pass: an allowance by checkpoint index cannot work on a rolling hash, since 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. A per-cycle nine-field comparator with a scoped allowance costs ONE cycle of coverage instead of 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. Built on **v2.6.8 "Arrears"** (2026-08-31) — the gates the previous release fixed and never widened. A deny list is an assertion about the THING UNDER TEST, so v2.6.7 changing both the DUT and the harness re-opened every exclusion -- and nothing re-measured one. FOUR of the six denied checkpoint streams were passing (`irqlat048`, `ppuvbl023`, `ppuvbl024`, `ppuvbl025`), and THREE of those were not run by the suite at all, so removing them from the deny list 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 all 5,062,680 overlapping cycles now match, with the window DERIVED from the manifest. CAVEAT C4 CLOSES by demonstration: nestest raises `nmi_line` on 3,592 cycles and had no nine-field comparison at all, so wiring it makes an `o.nmi_line` mutation CAUGHT at checkpoint 28 of 1,237 while the bus gate passes the identical run. Suite 128 passed, 0 failed, 0 skipped; nine-field comparisons 51 -> 57. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no board is attached, confirmed by checking. Built on **v2.6.7 "Detent"** (2026-08-30) — the bitstream becomes a published release artifact and a one-cycle disagreement is pinned 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, which produced one and withheld it because no hardware had run it: the MiSTer distribution mechanism reads that path out of the REPOSITORY, so an empty `releases/` describes an undistributable core rather than a cautious one, and the caution moves from an absence into a disclosure naming what the ladder cannot reach by construction (the PPU gate compares the pre-palette index and the APU gate per-channel integer levels, so the palette, the video timing constants, the audio absolute level and its band-limiting all sit downstream of every gate). The build is REPRODUCIBLE and that is now measured rather than argued -- a from-scratch compile and an incremental one produce a byte-identical bitstream -- which is also how v2.6.6's published slack figures came to be WITHDRAWN: no corner of a clean rebuild reproduces them, the innocent explanation (a different timing corner) was checked first and refuted, and the correct pair is +0.108 ns setup and +0.042 ns hold at the binding corner. THE RELEASE GATE WAS READING THE WRONG CORNER -- Slow 100C is not the binding one on this design, so a bitstream failing at Slow -40C would have passed while the gate reported three times the real margin -- and the checker that reads it was wrong twice before mutation found both: it first extracted ZERO rows from both summary tables and reported that as "no negative slack", then, once fixed, reported FOURTEEN clocks from a report emptied of its data, having run past the closing rule into the next tables. Caveat C2 splits in two. The first residual was a TRACE OBSERVATION POINT -- the harness built its record after eight of a CPU cycle's twelve master clocks while the oracle reads at end-of-cycle, and the frame-counter interrupt asserts on the final edge -- and closing it took checkpoint comparisons from 3 to 11 and failures at one checkpoint from 30 to 3. The second is REAL, and the FIRST fix for it was REFUTED in a way that found the right one: moving all four effects of the write one cycle later to match the oracle drops blargg from 11/11 to 4 of 11, one of the seven being the ROM written to probe exactly that timing. Read as a measurement that PROVES the sequencer's maturation is correctly placed, which leaves only the other effects the same write schedules -- so separating ONLY the interrupt clear lands it at write+3, the documented cycle, while the frame counter's zeroing stays put. Checkpoint streams go from 11 identical to 52 of 58 and the suite from 87 to 122, with blargg still 11/11 and the bus still matching on all 2,680,239 overlapping cycles. The checkpoint gate is registered over 51 comparisons and STATES ITS BLIND SPOT: not one gated golden ever raises an NMI, so it cannot catch an nmi_line defect, and the attempt to close that hole found a FOURTH divergence cluster that three goldens had been hiding inside a "26 skipped" tally line. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on **v2.6.6 "Chassis"** (2026-08-29) — the console becomes a MiSTer core -- `sys/` vendored byte-identical against `Template_MiSTer@3ea1134c` (57 files, 0 content differences), a top level, a clock, a palette, video sync and an audio mixer, compiled by Quartus 17.0.2 into a Cyclone V bitstream with 0 errors, timing CLOSED, and a warning count taken from 111 to THREE -- all three inside the vendored framework or Quartus's own megafunction, none of them citing this project's RTL (worst setup +0.086 ns and worst hold +0.096 ns at the binding corners, seed 3 -- v2.6.7 also withdraws the +0.363/+0.245 v2.6.6 published, which a clean rebuild of that configuration reproduces at no corner, TNS 0.000 on every clock; the console's own clock +13.514 ns at 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, because the cartridge memories had to be rewritten: an M10K read is REGISTERED, so 40 KiB of asynchronously-read cartridge was 393,216 registers against roughly 166,000 available, under a comment claiming it inferred block RAM from the source style alone, and the README had stated the correct rule since v2.4.3. 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, because the mixer output is unipolar with silence at zero and the framework maps unsigned silence to -32768, and two OSD scanline options did nothing because VGA_SL was tied to zero. And a convention enforced by a glob has no error message: sys_top.sdc groups the core 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 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. Built on **v2.6.5 "Muster"** (2026-08-29) — 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, where the same gate read 5 of 146 at the version's start. A muster is a roll call where every name is called AND answered, which is the two-clause acceptance exactly. Five PPU defects close the last six differing entries and four were invisible to every gate that existed when the version opened: the background shift registers' RELOAD and their shift clock need SEPARATE gates (with one shared gate the serial-in test was not merely failing but ARITHMETICALLY UNREACHABLE, since reload dots are absolute and the reload discards the low seven bits, so a serial-in one can never reach bit 7 on any alignment — and modelling both structures reproduces BOTH measured shifter values); the sprite X counters are NOT gated on rendering, which AccuracyCoin states outright and the ROM that states it passes either way, because it expects no hit at X=254 and a sprite shoved 18 dots right is also off the line; the PPUADDR second-write v-copy is DELAYED, as the wiki says inside the write sequence itself, swept 1 to 4 dots against a control at 8 and 12 that fails; and the pre-render line CLEARS secondary OAM, without which scanline 0 draws what scanline 239 left — no sprite can ever render on scanline 0, because OAM Y is one less than the display row, and a sprite-0 probe over the full 134 M-cycle battery found 24 hits with four of them there; and the octal latch holding across the read dot, which is verified by exactly ONE gate and was unverifiable until the v-copy delay landed, the two composing the hybrid address together and neither producing it alone. A DIAGNOSIS IS RETRACTED: the residual was read as a two-dot CPU/PPU alignment error from comparing dot spans across two instruments, and at the committed alignment the two consoles execute identical pc, bus_addr and bus_access for 1,695,131 cycles while a two-dot shift moves the first fork back to 593,228 and takes the differing share from 5.13% to 66.80%. The oracle changes on the default path, so AccuracyCoin 141/141 (RAM decoder) and nestest 0-diff are VERIFIED, not asserted. Built on **v2.6.4 "Rubric"** (2026-08-26) — OAM DMA lands and all nine AccuracyCoin disagreements close, every rule that closed the last three stated by the test ROM and by neither nesdev page — and then the gate that certified them is measured to cover 88 of 146 entries. The emulation core is unchanged. Built on **v2.6.3 "Mainspring"** (2026-08-25) — the DUT runs on one master clock, and four enables that were never enabling — plus AccuracyCoin end to end and a status vector that names its disagreements by test. The emulation core is unchanged. Built on **v2.6.2 "Witness"** (2026-08-24) — rung 4 closes: blargg APU battery 11/11 on the co-simulation DUT, six defects no self-written gate could see, and a suite that had been asserting nothing for five minor releases. The emulation core is unchanged. Built on **v2.6.1 "Interleave"** (2026-08-24) — the DMC and its DMA cycle steal in the MiSTer co-simulation DUT, cycle-exact on the bus. The emulation core is unchanged. Built on **v2.6.0 "Assay"** (2026-08-24) — the triangle, the noise channel and the sweep unit **in the MiSTer co-simulation DUT** — and an audit of how much of the APU was fitted to the oracle rather than derived from documentation. The emulation core is unchanged. Built on **v2.5.9 "Overture"** (2026-08-24) — rung 4 opens: the two pulse channels, the frame counter, and four ROM defects the stimulus measurement found first. Built on **v2.5.8 "Blanking"** (2026-08-24) — VBlank, NMI and the PPUSTATUS race close rung 3 — and both fixes were deletions. Built on **v2.5.7 "Collimation"** (2026-08-24) — sprite rendering closes exact — the phase was wrong by two dots, and every window was compensating. Built on **v2.5.6 "Vestige"** (2026-08-23) — Sprite evaluation closes: all 59,993 overlapping cycles match, nine of nine behavioural mutants caught and two proved inert (announced as seven of eight at the cut), and the fix is a byte index that outlives the walk that set it. Built on **v2.5.5 "Raster"** (2026-08-23) — the first full frame, and three blind spots in the stimulus that fed it. Built on **v2.5.4 "Escapement"** (2026-08-23) — the background fetch pipeline, and an access two dots early that five gates could not see. Built on **v2.5.3 "Hysteresis"** (2026-08-23) — toggling rendering takes effect three dots after the write, and four instruments to prove it. Built on **v2.5.2 "Dormant"** (2026-08-23) — the 2C02 register file, and a gate that passed while testing nothing. Built on **v2.5.1 "Retrace"** (2026-08-23) — the interrupt sweep closes rung 2, and a gate reported a pass it could not have earned. Built on **v2.5.0 "Rungwork"** (2026-08-23) — the 6502 rung, and the two gates it cannot reach. Built on **v2.4.9 "Plumbline II"** (2026-08-23) — the bus half of rung 2, and what it found the day it existed. Built on **v2.4.8 "Palimpsest"** (2026-08-23) — read-modify-write, and a gate that cannot see its own subject. Built on **v2.4.7 "Keystone"** (2026-08-23) — the stack closes, and a dead line proves itself dead. Built on **v2.4.6 "Abacus"** (2026-08-22) — the core learns arithmetic. Built on **v2.4.5 "Compass"** (2026-08-22) — the core reaches memory, and chooses. Built on **v2.4.4 "Ignition"** (2026-08-22) — the first real RTL -- the 6502's eight-cycle reset and the implied opcode group, matching the oracle on all seven CPU fields (29 > records, `RustyNES_MiSTer@7f092bd`). The oracle settled a question our own > prose could not: reset is EIGHT cycles, and `docs/cpu-6502.md` said both > seven and eight. The emulation core is untouched. @@ -1883,7 +1883,7 @@ without panicking (no `$6000` status protocol). | `vrc24test` | — | — | — | — | **Skipped (Track B1)**: link rot. AWJ's original forum attachment (id=10017 on forums.nesdev.org/viewtopic.php?p=203716) is auth-walled; the deletion is documented at archive.nes.science. No GitHub mirror found. | | `AccuracyCoin` | 1 | 1 | — | — | 100thCoin / Chris Siebert single-NROM accuracy battery (MIT license, 149 tests across 22 suites + 5 visual-only `Power On State` tests sharing `$03FF`; the v2.0.1 upstream re-sync grew the catalog from 144 to 146 rows / 139 to 141 assigned tests, adding the PPU "ALE + Read" and "Hybrid Addresses" tests, and the **2026-09 re-sync** to upstream `69c8860` grew it again to 149 rows / 144 assigned across 22 suites — two new pages, `Advanced Background Evaluation` and `Advanced Sprite Evaluation`, which re-home eleven existing PPU tests that had outgrown `PPU Misc.`, plus three genuinely new tests). Interactive (D-Pad menu); the harness presses `START` to "run all tests on the ROM" then takes two parallel measurements. **(1) Framebuffer decoder** reads the 10×16 on-screen result grid by exact-pixel colour (5-colour palette: `#64A0FF` = pass, `#4F1000` = fail, `#DC834C` = partial-pass, `#4C4C4C` = no-test / not-run, `#FFFFFF` = border); this is the legacy path and has a known grid-stride bug that under-samples by ~31 cells. **(2) RAM-direct decoder** reads each test's result byte from its fixed CPU-RAM address (catalogued from upstream `AccuracyCoin.asm` in `crates/rustynes-test-harness/src/accuracy_coin_catalog.rs` and `tests/roms/AccuracyCoin/SOURCE_CATALOG.tsv` — 149 `(suite, name, addr)` triples, regenerated by `scripts/accuracycoin-build/extract_catalog.py`) and decodes per-test pass/fail/error-code names + per-suite breakdowns. This is the authoritative path. **Current measured pass rate (RAM-direct): 100.00% (144/144)** on the default build — the two upstream PPU tests "ALE + Read" and "Hybrid Addresses" (briefly the only gaps at 139/141 after the v2.0.1 catalog re-sync) were **closed in v2.0.3** by promoting the 2-cycle-ALE PPU fetch model to the unconditional default. The `90.65%`, `84.17%` and the trajectory figures below are historical engine-lineage milestones (the pre-promotion v1.0.0-rc2 / Session-26 era), retained as history. Historical trajectory: `64.03%` (post-D2 baseline) → `67.63%` (post-D3, 7 6502 bus-pattern fixes) → `69.06%` (post-Phase-3 OAM DMA parity fix, +1 strict test flipped) → `69.78%` (post-FSM-fix recovery, +1 sprite-related sub-test flipped as a side-benefit of the `crates/rustynes-ppu/src/ppu.rs` dot-64 reset removal) → `76.98%` (post-Cascade-B DMC DMA scheduler, commit `9b0c81c` — closes all 8 tests in the `APU Registers and DMA tests` suite + 3 net elsewhere as side-benefits; +11 tests flipped) → `78.42%` (post-Cascade-A OAMADDR-during-rendering reset, commit `f29f7ca` — hardware-accurate per nesdev: OAMADDR is reset to 0 during dots 257-320 of every rendered scanline; +2 tests flipped — Sprite overflow behavior PASSES, Sprite 0 Hit advances from error 1 → error 13) → `79.14%` (post-session-7 OAMADDR-walks-during-eval + $4-aligned `$2004` write, commit `c230489` — closes `Address $2004 behavior` with code 16; +1 net flip) → `79.86%` (post-session-7 RMW ABS,X/Y unfixed-address dummy read, commit `32d5b18` — 18 RMW opcodes get the canonical cycle-4 unfixed-address dummy; flips `APU Tests :: Controller Clocking` and advances `Implied Dummy Reads` 2→3 + `Frame Counter IRQ` 6→7 via the SLO $4015,X bracket; +1 net flip) → `82.73%` (post-session-8 BG-pipeline cycle-9 reload + post-emit shift, commit `086ce4d` — fixes the long-standing 1-column BG pixel off-by-one identified in `docs/audit/cascade-a-investigation-2026-05-19.md`; flips `Sprite 0 Hit behavior` + `Sprite overflow behavior` + `Suddenly Resize Sprite` + `$2007 read w/ rendering`; +4 net flips, +2.87pp) → `83.45%` (post-session-24 Controller Strobing M2-low-defer write, Session-24 Phase 3 — deferred `$4016` commit buffer on `LockstepBus` mirrors Mesen2's `NesControlManager::ProcessWrites`; flips `APU Tests :: Controller Strobing` from `[error 4]` to PASS; +1 net flip) → **`84.17%` (post-session-26 Sprint 2 iter 5 Frame-Counter-IRQ split, 2026-05-23 — separates `FrameCounter::irq_flag` ($4015 bit 6 visibility) from `FrameCounter::irq_line_active` (CPU IRQ source driver) so Tests I/J/K/L/M/N/O all PASS without spuriously asserting the CPU IRQ line on inhibited frame-counter cycles; flips `APU Tests :: Frame Counter IRQ` from `[error 19]` to PASS; +1 net flip)**. Session-26 Sprint 2 iter 4 (APU Register Activation OAM-DMA chip-select gate) advanced the same suite's APU Register Activation entry internally from `[error 4]` to `[error 6]` but did not flip the catalog-headline metric. The previous `75.93%` headline reflected the framebuffer decoder's stride bug, not real accuracy. Strict floor in CI is **60%** — see `crates/rustynes-test-harness/tests/accuracycoin.rs::MIN_PASS_RATE`. the v0.9.x 80% target and the v1.0.0 90% gate were both cleared, and the default build now measures **100.00% (144/144)** — the master-clock core is the default, the former C1 + sub-cycle residuals are closed, and the two v2.0.1 PPU tests were closed in v2.0.3 (see "Accuracy residuals" below). **No open AccuracyCoin gap, as of v2.6.18.** The 2026-09 re-sync opened two, both in the new `Advanced Sprite Evaluation` page. `Misaligned OAM2 Address` ($0495) closed in v2.6.17 by modelling `OAM2Address` as a live counter through sprite fetch. `Frozen OAM2 Increment` ($0493) closed in **v2.6.18**, and the long diagnosis this row used to carry is **RETRACTED in all three of its claims** — it said a `$2001` enable on dot 256 took effect a dot early (all four writes the ROM names are in force from the START of the dot it names), it blamed test 2 or 3 (the failure was test **4**, the false-positive guard, and the ROM omits an `INC ) for in-RetroArch use. **Zero emulation-core changes** — the deterministic `#![no_std]` chip stack, save-state / TAS / netplay formats, and every golden vector are byte-identical to v2.2.3, so **AccuracyCoin holds 141/141 (100.00%)**, nestest 0-diff, by construction. The work is a libretro-completeness audit + metadata correction: the core is confirmed to inherit every v2.2.3 change automatically (the fast-dot-path default, the `PPU_SNAPSHOT_VERSION` 8 / APU v4 save-state schema handled transparently by the dynamic `snapshot_core_into` sizing, the `Mapper::mix_audio` i32 widening, the Zapper model, and the `mNNN_` mapper rename), and both buildbot cross-ABIs the GitHub gate models — `x86_64-pc-windows-gnu` and `aarch64-linux-android` — build clean. `rustynes_libretro.info` (the metadata RetroArch's core downloader reads) is corrected: **`disk_control` `false` → `true`** (the FDS multi-side Disk Control interface has been wired since the buildbot recipe landed, but was advertised as absent — the real fix), `display_version` `v1.0.0` → `v2.2.4`, and the mapper count `168` → `172`. Also: the reviewer-tooling standardization onto the shared Antigravity template rides along (`scripts/agy-review.sh` + workflow). Documented libretro follow-up: **core options** (region / overscan / palette / accuracy toggles) remain unexposed (`core_options = "false"` is accurate, not stale) — a deliberate future enhancement, not a v2.2.4 gap. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.2.4]` + `docs/libretro/`. -- **Release line since v2.1.0:** the v2.1.x **"Fathom"** accuracy line (v2.1.0 → v2.1.10) → **v2.2.0 "Capstone"** (the milestone cut closing the "deepen the existing project" run) → **v2.2.1** (housekeeping) → **v2.2.2 "Conduit"** (build / distribution / CI-integrity) → **v2.2.3 "Datum"** (performance appraisal + the last two Holy Mapperel residuals closed) → **v2.2.4 "Cartridge"** (the libretro/RetroArch distribution cut) → **v2.2.5 "Colophon"** → **v2.2.6 "Almanac"** → **v2.2.7 "Timbre II"** → **v2.2.8 "Aperture II"** → **v2.2.9 "Studio II"** → **v2.3.0 "Datum II"** → **v2.3.1 "Plumb Line"** → **v2.3.2 "Lucid"** → **v2.3.3 "Cadence"** → **v2.3.4 "Ledger"** → **v2.3.5 "Manifest"** → **v2.3.6 "Sounding"** → **v2.3.7 "Overtone"** → **v2.3.8 "Parallax"** → **v2.3.9 "Crucible"** → the **v2.4.x "Fabric"** co-simulation line (**v2.4.1 "Fabric"** → **v2.4.2 "Cairn"** → **v2.4.3 "Touchstone"** → **v2.4.4 "Ignition"** → **v2.4.5 "Compass"** → **v2.4.6 "Abacus"** → **v2.4.7 "Keystone"** → **v2.4.8 "Palimpsest"** → **v2.4.9 "Plumbline II"** → **v2.5.0 "Rungwork"** → **v2.5.1 "Retrace"** → **v2.5.2 "Dormant"** → **v2.5.3 "Hysteresis"** → **v2.5.4 "Escapement"** → **v2.5.5 "Raster"** → **v2.5.6 "Vestige"** → **v2.5.7 "Collimation"** → **v2.5.8 "Blanking"** → **v2.5.9 "Overture"** → **v2.6.0 "Assay"** → **v2.6.1 "Interleave"** → **v2.6.2 "Witness"** → **v2.6.3 "Mainspring"** → **v2.6.4 "Rubric"** → **v2.6.5 "Muster"** → **v2.6.6 "Chassis"** → **v2.6.7 "Detent"** (the bitstream becomes a published, reproducible release artifact and a one-cycle disagreement is attributed rather than fitted away) → **v2.6.8 "Arrears"** (the gates the previous release fixed and never widened: four of six denied checkpoint streams had been passing unnoticed, three of them not run by the suite at all, and nestest widens 19x and gains the nine-field comparison that closes the gate's stated `nmi_line` blind spot) → **v2.6.9 "Abeyance"** (an exclusion hides improvement as well as regression -- both denied co-simulation streams close, and the larger one was a defect in the HARNESS rather than the console, carried for seven releases behind the phrase "by design") → **v2.6.10 "Inference"** (the cartridge meets the synthesiser, and simulation could not have asked the question -- rung 7's five boards had never been through Quartus, and `chr` written from two separate `always_ff` blocks could not infer as an M10K, so 128 KB stayed in flip-flops: 1,048,576 registers against roughly 166,000. The fix is behaviourally invisible -- 141 gates unchanged -- and the release carries the bitstream the previous one could not produce) → **v2.6.11 "Exposure"** (a picture is a gate the ladder did not have -- all 141 co-simulation gates green and two of six commercial games rendering wrong, because only THREE of those gates compare a framebuffer and all three ship CHR-ROM, so a CHR-RAM write taking the shared-pin composite address built for fetches was reached 9,600 times and compared never)) -> **v2.6.12 "Groundwork"** (the bitstream was an NROM-only console -- `emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND and the fitted cartridge was mapper 0 with 8 KiB of PRG and 8 KiB of CHR against a declared 256 and 128; no gate could see it because `emu.sv` is not in the testbench file list, so all 142 gates exercised a correctly-configured cartridge, and Quartus said so three times in messages that cite an instance path rather than a file) -> **v2.6.13 "Slack"** (the cartridge outgrows the die -- an SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge written from the AS4C32M16SB-7 datasheet, and three consumers measured to three different budgets rather than the one figure the previous step assumed; the PPUDATA port leaves the shared bus through a handshake and that fix shipped a defect only a banked cartridge could see, the request carrying the raw PPU address where the mapper's translation was needed, so every banked-CHR board read bank zero; off the die the console passes 142 of 142 and it still ships on the die, because an off-die core cannot run without the add-on) -> **v2.6.14 "Docket"** (the submission checklist becomes auditable -- 30 boxes, 16 unticked and fourteen of those saying nothing about why, so an unticked box could not be told apart from work outstanding, work blocked elsewhere, and work already done and never ticked; the third case occurred five times. One box asked `docs/provenance.md` to state that no NES core was ever opened, which that document's own Do-not-self-certify section forbids, so it could only have been ticked by writing the sentence the provenance rules exist to prevent. Re-measuring the ticked half found two claims that had expired, the task board four delivered items never ticked and an SDRAM precondition the previous release did not follow, and its claim that MMC1 was unimplemented is retracted) → **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, because `Distribution_MiSTer`'s builder skips any file whose stem does not end in `_` plus eight digits, so an accepted core would appear in the Cores table and ship nothing with no error anywhere; two of the four R1/R2 residuals ADR 0002 closed turn out never to have been IRQ-timing residuals, resting on an assertion blargg WITHDREW in the successor ROM; `sys/` verbatim, the `.qsf`'s single seed table and the bitstream name all become checks instead of claims; the nine rung-1 gates run in CI against a PINNED oracle commit, so the accuracy evidence stops being a document describing a check a reader cannot run; `cpu_interrupts_v2` lands on the DUT as the first INDEPENDENT interrupt oracle, five of five; and `T-ORACLE-001`'s opening claim is retracted — RustyNES does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 has asserted all along) → **v2.6.16 "Interlock"** (the arbiter's numbers describe a stimulus, not the console: the gate's worst case issues a CHR and a PRG request on the same cycle and the running console never does — zero coincidences and a minimum separation of two over fifteen million SDRAM cycles — so both published figures are wrong about it in opposite directions, the CPU MEETING its deadline at 24 against 24 on all 240,303 requests where the gate said it missed by four, and the fetch reaching 23 against a derived 22 that a `CHR_LAT` sweep shows understates the real tolerance by at least nine cycles; the slot-scheduled arbiter this version was scoped around is therefore NOT built, and is still worth building because zero margin means one added cycle anywhere breaks the CPU) → **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, so pre-v9 `.rns` states no longer load. The third 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. `mask_for_skip_check` is PROVEN to be a compensation for the placement — under phi2 it needs one delay stage rather than two — 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 follow-up version the write-placement work had been scoped into 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 — so v2.6.17 carries both). v2.6.18 "Errata" closes the last AccuracyCoin entry on top of it, and v2.6.19 "Accession" carried both into the FPGA core, v2.6.20 "Telltale" made the OAM2 counter observable, v2.6.21 "Steward" gave the core battery saves and gated CHR writes during rendering, v2.6.22 "Rigging" built the instruments a board would need before the board existed, v2.6.23 "Pulse" closed the CHR-during-rendering divergence by making the `$2007` access pulse the rendering pipeline's load instead of computing its own, and v2.7.0 "Palisade", the first of the ADR 0041 audit line, made a corrupt save state fail at restore rather than one tick later, stopped pulse 1 muting on the `$4001 = $08` sweep idiom, and gave the save-state fuzz target the reach to find five defects the audit had not, v2.7.1 "Keepsake" stopped six cartridge boards writing an empty battery save and moved every user file the frontend writes onto the atomic writer, v2.7.2 "Bankroll" gave MMC1, MMC5 and Namco 163 the banks their boards have and made `$6000-$7FFF` read open bus on the 205 board variants with nothing there, v2.7.3 "Hearth" made the desktop keep battery saves, closed three ways around the Lua sandbox, gated script HTTP destinations, and brought audio back after a device change, v2.7.4 "Pocket" kept the Android and iOS apps alive through an internal error, gave both battery saves, paused them and released audio when they leave the screen, and made their saves atomic, v2.7.5 "Tally" closed the core audit's twelve performance proposals (eleven by measurement, one adopted: the audio buffer keeps its capacity), deprecated 18 dead bus methods and `ApuBus`, and closed the core and frontend ledgers, v2.7.6 "Recount" measured alone the six deletions v2.7.5 had bounded only together (five zero; the sixth, never reached by the benchmarks before, bounds at 0.2%), turned three redundant fast-path stores into an assertion, and shipped the contributed libretro buildbot fixes and targets, v2.8.0 "Bulkhead" opened the v2.8.x libretro + RTL audit line: the libretro core unwinds and contains a panic at its own boundary, keeps save states the right size with a Zapper, withdraws its memory maps on unload, loads through the standard `retro_game_info` via a vendored binding, and the save state carries the internal data bus, v2.8.1 "Gasket" closed the libretro audit: Four Score, four described ports (the list now NULL-terminated, where RetroArch had read past it), the Zapper unplugged, unclipped expansion audio, UNIF, and a Makefile that honours its callers, v2.8.2 "Solder" corrected the MiSTer core's on-die RTL against the oracle and the wiki (MMC3 acknowledge, SNROM PRG-RAM, the triangle and noise reload drop, the `$2002` latch) and found the audit's MMC1 finding inverted: the emulator was the one ignoring a reset, and v2.8.3 "Rivet" released every reset on the clock that uses it, cut the CPU by about 4% with two exact rewrites the fit report confirmed, and made the ladder run from a fresh checkout (164 of its 165 gates, a claim v2.8.4 found overstated), and v2.8.4 "Tether" closed the v2.8.x line: it found every off-die read would have been sampled a clock late on hardware (the SDRAM model and controller shared the error, and only the new SDRAM timing constraints could see it) and fixed both, pinned both builds at fitter seed 5 after a sweep of each, and made the ladder run all 165 gates from a clean checkout, and v2.9.0 "Survey" opened the v2.9.x line by re-running all four audits (139 rows re-verified, none regressed, 39 new findings fixed or dispositioned), including a Power Cycle that erased the battery save and an off-die MiSTer build that hung under `bootcore=`, v2.9.1 "Hone" found that the A/B tool had been timing the old code on both sides and re-measured what it had rejected, made a Vs. cabinet save-state about 9x faster, moved the off-die build's CHR into its own SDRAM bank, and pinned both builds at fitter seed 2 with two byte-identical clean compiles each, v2.9.2 "Candidate" triaged a fifth, 32-finding audit of both repositories (16 fixed, each finding with its evidence in its ledger), made save states keep the cartridge RAM of twelve board families that dropped it, fixed the MiSTer CPU losing an NMI raised inside a DMA, and cut the release-candidate bitstream pair for the SuperStation One session, v2.9.3 "Handset" moved every dependency to its newest release, answered all 244 review threads left open on PRs #7-#97 and fixed the ten findings that still held (mapper 28 rebuilt, a header editor that writes only what you change), and prepared the mobile device run, which the maintainer then moved after v3.0.0 with the board session, v2.9.4 "Plumb" recorded v3.0.0 as the API major with a release-candidate core (ADR 0043), made CI run the feature-gated tests, fuzz targets and a coverage floor it had only linted or never had, and corrected the records against the code, v2.9.5 "Caliper" gave every open accuracy item an outcome: blargg's `apu_test` frame-counter coincidence, the composite 2C02's odd-frame scanline-0 sprite pixel, OAM DMA filling the PPU I/O latch and KS7032's `$6000` window fixed red first, 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11), v2.9.6 "Roster" added seventeen mapper families from their NESdev pages (174 → 191), promoted GTROM to Curated with a modelled SST39SF040 whose saves persist, corrected mapper 4's NES 2.0 submappers (MMC6, NEC, MC-ACC, T9552), and re-baselined the local commercial suites, which had drifted unread since about v2.0.0, v2.9.7 "Tandem" brought the desktop's features to the web and phones (battery saves in the browser, Vs. DualSystem on web and mobile, FDS and NSF on mobile), made the release binaries the full native build, and fixed a PPU A12 defect real games exposed: the PPU reported one pulse per scanline where the console makes eight, so Acclaim's MC-ACC, the J.Y. ASIC and mapper 91 had counted eight times slow, v2.9.8 "Vanguard" carried v3.0.0's planned breaking changes ahead of it (a save identity that ignores the header, older save states and movies refused, movies and netplay that record the machine, the ADR 0042 API removals), booted every one of 748 staged dumps and fixed the defects that turned up, and made the game database's corrections reach every platform, v2.9.9 "Ballast", the release candidate for v3.0.0, re-ran all four audits and fixed what they found, raised the MMC3 IRQ by the NESdev rule (T-ORACLE-001) and gave the MMC5 the chip's CHR set selection (which fixed *Uchuu Keibitai SDF*), made audio exact across a save state, moved the MiSTer core's oracle pin onto it with the APU and PPU parity that opened, and cut the release-candidate bitstream pair at seed 6 (a seed-8 pair was withdrawn when the RTL changed after it), and v3.0.0 "Cornerstone", the current release and the API major, gathered every break since v2.x in one place, made movies and netplay carry a core timing epoch so a version that emulates differently is refused with a reason (ADR 0045), took the last struct-extensibility breaks, closed blargg's `4-scanline_timing` in the emulator and the MiSTer core (T-MMC3-BG-A12), and shipped that core as a release candidate, not hardware-verified. AccuracyCoin held **141/141** through v2.6.16, read **143/144 (99.31%)** at v2.6.17, where the upstream re-sync grew the battery to 144 assigned tests and `Frozen OAM2 Increment` was the single named failure, and has read **144/144 (100.00%)** since v2.6.18 closed it — and the number was never always *by construction*: v2.3.4, v2.3.7, v2.3.9, the rung-3 releases v2.5.4-v2.5.6 and v2.6.17 change the core, so for those it is **verified** rather than inherited, and saying which is which is the point. **Full per-release detail is in `CHANGELOG.md` and `docs/STATUS.md` (the single source of truth)** — the entries below (v2.1.0 "Fathom" was the prior anchor here; v2.0.8 → v2.0.1) are the older historical trail, retained rather than duplicated. +- **Release line since v2.1.0:** the v2.1.x **"Fathom"** accuracy line (v2.1.0 → v2.1.10) → **v2.2.0 "Capstone"** (the milestone cut closing the "deepen the existing project" run) → **v2.2.1** (housekeeping) → **v2.2.2 "Conduit"** (build / distribution / CI-integrity) → **v2.2.3 "Datum"** (performance appraisal + the last two Holy Mapperel residuals closed) → **v2.2.4 "Cartridge"** (the libretro/RetroArch distribution cut) → **v2.2.5 "Colophon"** → **v2.2.6 "Almanac"** → **v2.2.7 "Timbre II"** → **v2.2.8 "Aperture II"** → **v2.2.9 "Studio II"** → **v2.3.0 "Datum II"** → **v2.3.1 "Plumb Line"** → **v2.3.2 "Lucid"** → **v2.3.3 "Cadence"** → **v2.3.4 "Ledger"** → **v2.3.5 "Manifest"** → **v2.3.6 "Sounding"** → **v2.3.7 "Overtone"** → **v2.3.8 "Parallax"** → **v2.3.9 "Crucible"** → the **v2.4.x "Fabric"** co-simulation line (**v2.4.1 "Fabric"** → **v2.4.2 "Cairn"** → **v2.4.3 "Touchstone"** → **v2.4.4 "Ignition"** → **v2.4.5 "Compass"** → **v2.4.6 "Abacus"** → **v2.4.7 "Keystone"** → **v2.4.8 "Palimpsest"** → **v2.4.9 "Plumbline II"** → **v2.5.0 "Rungwork"** → **v2.5.1 "Retrace"** → **v2.5.2 "Dormant"** → **v2.5.3 "Hysteresis"** → **v2.5.4 "Escapement"** → **v2.5.5 "Raster"** → **v2.5.6 "Vestige"** → **v2.5.7 "Collimation"** → **v2.5.8 "Blanking"** → **v2.5.9 "Overture"** → **v2.6.0 "Assay"** → **v2.6.1 "Interleave"** → **v2.6.2 "Witness"** → **v2.6.3 "Mainspring"** → **v2.6.4 "Rubric"** → **v2.6.5 "Muster"** → **v2.6.6 "Chassis"** → **v2.6.7 "Detent"** (the bitstream becomes a published, reproducible release artifact and a one-cycle disagreement is attributed rather than fitted away) → **v2.6.8 "Arrears"** (the gates the previous release fixed and never widened: four of six denied checkpoint streams had been passing unnoticed, three of them not run by the suite at all, and nestest widens 19x and gains the nine-field comparison that closes the gate's stated `nmi_line` blind spot) → **v2.6.9 "Abeyance"** (an exclusion hides improvement as well as regression -- both denied co-simulation streams close, and the larger one was a defect in the HARNESS rather than the console, carried for seven releases behind the phrase "by design") → **v2.6.10 "Inference"** (the cartridge meets the synthesiser, and simulation could not have asked the question -- rung 7's five boards had never been through Quartus, and `chr` written from two separate `always_ff` blocks could not infer as an M10K, so 128 KB stayed in flip-flops: 1,048,576 registers against roughly 166,000. The fix is behaviourally invisible -- 141 gates unchanged -- and the release carries the bitstream the previous one could not produce) → **v2.6.11 "Exposure"** (a picture is a gate the ladder did not have -- all 141 co-simulation gates green and two of six commercial games rendering wrong, because only THREE of those gates compare a framebuffer and all three ship CHR-ROM, so a CHR-RAM write taking the shared-pin composite address built for fetches was reached 9,600 times and compared never)) -> **v2.6.12 "Groundwork"** (the bitstream was an NROM-only console -- `emu.sv` left `cart_mapper`, `cart_prg_16k_banks` and `cart_chr_8k_banks` unconnected, so Quartus tied all three to GND and the fitted cartridge was mapper 0 with 8 KiB of PRG and 8 KiB of CHR against a declared 256 and 128; no gate could see it because `emu.sv` is not in the testbench file list, so all 142 gates exercised a correctly-configured cartridge, and Quartus said so three times in messages that cite an instance path rather than a file) -> **v2.6.13 "Slack"** (the cartridge outgrows the die -- an SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge written from the AS4C32M16SB-7 datasheet, and three consumers measured to three different budgets rather than the one figure the previous step assumed; the PPUDATA port leaves the shared bus through a handshake and that fix shipped a defect only a banked cartridge could see, the request carrying the raw PPU address where the mapper's translation was needed, so every banked-CHR board read bank zero; off the die the console passes 142 of 142 and it still ships on the die, because an off-die core cannot run without the add-on) -> **v2.6.14 "Docket"** (the submission checklist becomes auditable -- 30 boxes, 16 unticked and fourteen of those saying nothing about why, so an unticked box could not be told apart from work outstanding, work blocked elsewhere, and work already done and never ticked; the third case occurred five times. One box asked `docs/provenance.md` to state that no NES core was ever opened, which that document's own Do-not-self-certify section forbids, so it could only have been ticked by writing the sentence the provenance rules exist to prevent. Re-measuring the ticked half found two claims that had expired, the task board four delivered items never ticked and an SDRAM precondition the previous release did not follow, and its claim that MMC1 was unimplemented is retracted) → **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, because `Distribution_MiSTer`'s builder skips any file whose stem does not end in `_` plus eight digits, so an accepted core would appear in the Cores table and ship nothing with no error anywhere; two of the four R1/R2 residuals ADR 0002 closed turn out never to have been IRQ-timing residuals, resting on an assertion blargg WITHDREW in the successor ROM; `sys/` verbatim, the `.qsf`'s single seed table and the bitstream name all become checks instead of claims; the nine rung-1 gates run in CI against a PINNED oracle commit, so the accuracy evidence stops being a document describing a check a reader cannot run; `cpu_interrupts_v2` lands on the DUT as the first INDEPENDENT interrupt oracle, five of five; and `T-ORACLE-001`'s opening claim is retracted — RustyNES does clock the MMC3 counter on the pre-render line, which `2-details` sub-test 8 has asserted all along) → **v2.6.16 "Interlock"** (the arbiter's numbers describe a stimulus, not the console: the gate's worst case issues a CHR and a PRG request on the same cycle and the running console never does — zero coincidences and a minimum separation of two over fifteen million SDRAM cycles — so both published figures are wrong about it in opposite directions, the CPU MEETING its deadline at 24 against 24 on all 240,303 requests where the gate said it missed by four, and the fetch reaching 23 against a derived 22 that a `CHR_LAT` sweep shows understates the real tolerance by at least nine cycles; the slot-scheduled arbiter this version was scoped around is therefore NOT built, and is still worth building because zero margin means one added cycle anywhere breaks the CPU) → **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, so pre-v9 `.rns` states no longer load. The third 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. `mask_for_skip_check` is PROVEN to be a compensation for the placement — under phi2 it needs one delay stage rather than two — 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 follow-up version the write-placement work had been scoped into 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 — so v2.6.17 carries both). v2.6.18 "Errata" closes the last AccuracyCoin entry on top of it, and v2.6.19 "Accession" carried both into the FPGA core, v2.6.20 "Telltale" made the OAM2 counter observable, v2.6.21 "Steward" gave the core battery saves and gated CHR writes during rendering, v2.6.22 "Rigging" built the instruments a board would need before the board existed, v2.6.23 "Pulse" closed the CHR-during-rendering divergence by making the `$2007` access pulse the rendering pipeline's load instead of computing its own, and v2.7.0 "Palisade", the first of the ADR 0041 audit line, made a corrupt save state fail at restore rather than one tick later, stopped pulse 1 muting on the `$4001 = $08` sweep idiom, and gave the save-state fuzz target the reach to find five defects the audit had not, v2.7.1 "Keepsake" stopped six cartridge boards writing an empty battery save and moved every user file the frontend writes onto the atomic writer, v2.7.2 "Bankroll" gave MMC1, MMC5 and Namco 163 the banks their boards have and made `$6000-$7FFF` read open bus on the 205 board variants with nothing there, v2.7.3 "Hearth" made the desktop keep battery saves, closed three ways around the Lua sandbox, gated script HTTP destinations, and brought audio back after a device change, v2.7.4 "Pocket" kept the Android and iOS apps alive through an internal error, gave both battery saves, paused them and released audio when they leave the screen, and made their saves atomic, v2.7.5 "Tally" closed the core audit's twelve performance proposals (eleven by measurement, one adopted: the audio buffer keeps its capacity), deprecated 18 dead bus methods and `ApuBus`, and closed the core and frontend ledgers, v2.7.6 "Recount" measured alone the six deletions v2.7.5 had bounded only together (five zero; the sixth, never reached by the benchmarks before, bounds at 0.2%), turned three redundant fast-path stores into an assertion, and shipped the contributed libretro buildbot fixes and targets, v2.8.0 "Bulkhead" opened the v2.8.x libretro + RTL audit line: the libretro core unwinds and contains a panic at its own boundary, keeps save states the right size with a Zapper, withdraws its memory maps on unload, loads through the standard `retro_game_info` via a vendored binding, and the save state carries the internal data bus, v2.8.1 "Gasket" closed the libretro audit: Four Score, four described ports (the list now NULL-terminated, where RetroArch had read past it), the Zapper unplugged, unclipped expansion audio, UNIF, and a Makefile that honours its callers, v2.8.2 "Solder" corrected the MiSTer core's on-die RTL against the oracle and the wiki (MMC3 acknowledge, SNROM PRG-RAM, the triangle and noise reload drop, the `$2002` latch) and found the audit's MMC1 finding inverted: the emulator was the one ignoring a reset, and v2.8.3 "Rivet" released every reset on the clock that uses it, cut the CPU by about 4% with two exact rewrites the fit report confirmed, and made the ladder run from a fresh checkout (164 of its 165 gates, a claim v2.8.4 found overstated), and v2.8.4 "Tether" closed the v2.8.x line: it found every off-die read would have been sampled a clock late on hardware (the SDRAM model and controller shared the error, and only the new SDRAM timing constraints could see it) and fixed both, pinned both builds at fitter seed 5 after a sweep of each, and made the ladder run all 165 gates from a clean checkout, and v2.9.0 "Survey" opened the v2.9.x line by re-running all four audits (139 rows re-verified, none regressed, 39 new findings fixed or dispositioned), including a Power Cycle that erased the battery save and an off-die MiSTer build that hung under `bootcore=`, v2.9.1 "Hone" found that the A/B tool had been timing the old code on both sides and re-measured what it had rejected, made a Vs. cabinet save-state about 9x faster, moved the off-die build's CHR into its own SDRAM bank, and pinned both builds at fitter seed 2 with two byte-identical clean compiles each, v2.9.2 "Candidate" triaged a fifth, 32-finding audit of both repositories (16 fixed, each finding with its evidence in its ledger), made save states keep the cartridge RAM of twelve board families that dropped it, fixed the MiSTer CPU losing an NMI raised inside a DMA, and cut the release-candidate bitstream pair for the SuperStation One session, v2.9.3 "Handset" moved every dependency to its newest release, answered all 244 review threads left open on PRs #7-#97 and fixed the ten findings that still held (mapper 28 rebuilt, a header editor that writes only what you change), and prepared the mobile device run, which the maintainer then moved after v3.0.0 with the board session, v2.9.4 "Plumb" recorded v3.0.0 as the API major with a release-candidate core (ADR 0043), made CI run the feature-gated tests, fuzz targets and a coverage floor it had only linted or never had, and corrected the records against the code, v2.9.5 "Caliper" gave every open accuracy item an outcome: blargg's `apu_test` frame-counter coincidence, the composite 2C02's odd-frame scanline-0 sprite pixel, OAM DMA filling the PPU I/O latch and KS7032's `$6000` window fixed red first, 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11), v2.9.6 "Roster" added seventeen mapper families from their NESdev pages (174 → 191), promoted GTROM to Curated with a modelled SST39SF040 whose saves persist, corrected mapper 4's NES 2.0 submappers (MMC6, NEC, MC-ACC, T9552), and re-baselined the local commercial suites, which had drifted unread since about v2.0.0, v2.9.7 "Tandem" brought the desktop's features to the web and phones (battery saves in the browser, Vs. DualSystem on web and mobile, FDS and NSF on mobile), made the release binaries the full native build, and fixed a PPU A12 defect real games exposed: the PPU reported one pulse per scanline where the console makes eight, so Acclaim's MC-ACC, the J.Y. ASIC and mapper 91 had counted eight times slow, v2.9.8 "Vanguard" carried v3.0.0's planned breaking changes ahead of it (a save identity that ignores the header, older save states and movies refused, movies and netplay that record the machine, the ADR 0042 API removals), booted every one of 748 staged dumps and fixed the defects that turned up, and made the game database's corrections reach every platform, v2.9.9 "Ballast", the release candidate for v3.0.0, re-ran all four audits and fixed what they found, raised the MMC3 IRQ by the NESdev rule (T-ORACLE-001) and gave the MMC5 the chip's CHR set selection (which fixed *Uchuu Keibitai SDF*), made audio exact across a save state, moved the MiSTer core's oracle pin onto it with the APU and PPU parity that opened, and cut the release-candidate bitstream pair at seed 6 (a seed-8 pair was withdrawn when the RTL changed after it), v3.0.0 "Cornerstone", the API major, gathered every break since v2.x in one place, made movies and netplay carry a core timing epoch so a version that emulates differently is refused with a reason (ADR 0045), took the last struct-extensibility breaks, closed blargg's `4-scanline_timing` in the emulator and the MiSTer core (T-MMC3-BG-A12), and shipped that core as a release candidate, not hardware-verified, and v3.0.1 "Mortar", the current release, unbanked *Famicom Yarou Vol.1*'s CHR-RAM (raising the emulation epoch to 2), reached the MiSTer core's odd-frame A12 exception with a new test ROM that found a one-cycle defect, moved the toolchain and the libretro buildbot to Rust 1.99, answered every bot review left since PR #1, recorded the shared Bisqwit NTSC pass as derived and moved the TriCNES source out of the repository, and wrote the plan to v4.0.0 (`to-dos/plans/v3.1-to-v4.0-line-plan.md`). AccuracyCoin held **141/141** through v2.6.16, read **143/144 (99.31%)** at v2.6.17, where the upstream re-sync grew the battery to 144 assigned tests and `Frozen OAM2 Increment` was the single named failure, and has read **144/144 (100.00%)** since v2.6.18 closed it — and the number was never always *by construction*: v2.3.4, v2.3.7, v2.3.9, the rung-3 releases v2.5.4-v2.5.6 and v2.6.17 change the core, so for those it is **verified** rather than inherited, and saying which is which is the point. **Full per-release detail is in `CHANGELOG.md` and `docs/STATUS.md` (the single source of truth)** — the entries below (v2.1.0 "Fathom" was the prior anchor here; v2.0.8 → v2.0.1) are the older historical trail, retained rather than duplicated. - **Historical detail — v2.0.8 "Harbor"** (2026-07-09) (the preceding release is v2.9.9 "Ballast"; see the release line above) — the eighth release of the **v2.0.x mobile-finalization train** and the **iOS release candidate** ("Harborlight"), the final release of the iOS finalization window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.7** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched). It stages the App Store scaffolding for v2.1.0: version-controlled **App Store Connect listing metadata** (`fastlane/metadata/ios/{en-US,es-ES}/`, mirroring the Android tree, files-only), a **dormant App Store `release` lane** in `fastlane/Fastfile` that stages the build + listing but **does not submit** (`submit_for_review: false`) and is **not** CI-wired (the interim channel stays **TestFlight**), and an **App-Review §4.7 self-audit** (no bundled/downloadable ROMs, ownership notice, searchable library, 4+ rating) in `docs/ios-v2.0.8-readiness.md`. Version bump (workspace `2.0.7 → 2.0.8`; iOS `MARKETING_VERSION → 2.0.8`). **No store submission** (that is v2.1.0); screenshots, real signing, the listing upload, and the App-Review submission are the **maintainer / v2.0.9 / v2.1.0** closeout. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.8]` + `docs/ios-v2.0.8-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. - **Earlier in the train:** **RustyNES v2.0.7 "Harbor"** (2026-07-09) — the seventh release of the **v2.0.x mobile-finalization train** and the **third iOS finalization release** ("Trim"), continuing the iOS window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.6** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched). It wires the **App Store submission floor** (Apple mandates the **iOS 26 SDK / Xcode 26** for every App Store Connect upload from **2026-04-28**, so the tag-gated iOS CI now selects the newest Xcode 26.x on the runner — a build-SDK pin, non-breaking fallback on older images), **reconciles the deployment target `iOS 15.0 → 17.0`** to match the code's real API floor (`NavigationStack` iOS 16 + `.topBarTrailing` iOS 17, unguarded at 12+ sites — the prior 15.0 was never buildable), and **re-audits `PrivacyInfo.xcprivacy`** against the v2.0.6 crash reporter (no new data type / required-reason API — local-only, backup-excluded, off by default). Version bump (workspace `2.0.6 → 2.0.7`; iOS `MARKETING_VERSION → 2.0.7`). **TestFlight-only** (App Store + AltStore PAL deferred to v2.1.0); on-device profiling + the Xcode-26 archive are a **maintainer / v2.0.9** step. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.7]` + `docs/ios-v2.0.7-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. - **Earlier in the train:** **RustyNES v2.0.6 "Harbor"** (2026-07-09) — the sixth release of the **v2.0.x mobile-finalization train** and the **second iOS finalization release** ("Parity"), continuing the iOS window (**v2.0.5 → v2.0.8**). A **host / iOS-only** cut: the cycle-accurate core is **unchanged and byte-identical to v2.0.5** (AccuracyCoin still **141/141, 100.00%**; nestest 0-diff; `#![no_std]` chip stack untouched), so no accuracy / save-state / determinism number moves. It adds a **new opt-in, privacy-first crash-reporting surface** (off by default — the iOS analogue of the Android v1.8.8 `CrashReporter`, closing the v1.9.9 iOS-applicable deferral): **Settings → Diagnostics** installs an uncaught-`NSException` handler that writes **local** crash logs the user can view + copy in-app — **nothing is uploaded**, so the "Data Not Collected" privacy label is unchanged (EN + ES); the handler re-checks the live opt-in at crash time so opting out stops new logs immediately. It also records the **feature-parity re-verification** of the v1.9.x host features (Game Center, CloudKit save sync, MFi controllers, capture / PiP, accessibility) against the unchanged v2.0.0 bridge surface. Version bump (workspace `2.0.5 → 2.0.6`; iOS `MARKETING_VERSION → 2.0.6`). **TestFlight-only** (App Store + AltStore PAL deferred to v2.1.0); on-device crash-capture verification is a **maintainer / v2.0.9** step. See `docs/STATUS.md` (single source of truth) + `CHANGELOG.md` `[2.0.6]` + `docs/ios-v2.0.6-readiness.md` + `to-dos/plans/v2.0.5-v2.0.8-ios-finalization-plan.md`. diff --git a/to-dos/plans/v3.0.1-mortar-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md index 2daa9d27..2da19abf 100644 --- a/to-dos/plans/v3.0.1-mortar-plan.md +++ b/to-dos/plans/v3.0.1-mortar-plan.md @@ -27,11 +27,11 @@ 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 | +| 1 | pending a quiet host | the runner waits on load and a `BUSY` handshake with every heavy job; on 2026-10-07 another session's QEMU kept the host above the 1.5 load threshold after the release gates. The result lands in this table, the CHANGELOG and `docs/performance.md` if it runs before the merge, or carries to v3.1.0 otherwise | | 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 first held on 1.96 via `RUSTUP_TOOLCHAIN`, then moved to 1.99 once test pipeline 119614 passed 15/15 (D5), 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 | +| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies, 77 resolves, 126 PR comments); afterwards 0 unresolved and 0 unanswered threads in both repositories | ## What the sweep is NOT From de90569b99a97542f82e8a3a8926886827c4dc1f Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 06:45:38 -0400 Subject: [PATCH 38/44] docs(agents): condense AGENTS.md, archiving the removed text verbatim AGENTS.md is loaded into every agent session, so its length is paid on every turn. Another agent session on this machine condensed it at 01:55 on 2026-10-07 and left the change uncommitted. The release cut (fa48dfc5) deliberately did not carry it: that commit staged AGENTS.md as HEAD plus the version-anchor edits only. The maintainer has now reviewed the change and directed it committed (2026-10-07). What changed in AGENTS.md: - per-region firewall paragraph: the dated incident narrative is condensed; the rule, the grep command and the escalation ladder remain; - "Timebase (v2.0.0)" release bullet: removed from the current-release block; - "(51 -> 172 families)": dropped from the release-history pointer (the count was stale; 191 now); - engine-lineage, hot-path and commit-body paragraphs: condensed; - "Exhaustive Documentation Sweeps" and "GitHub Wiki Initialization" bullets: removed. Every removed or shortened passage is reproduced verbatim in the new docs/history/AGENTS-archive.md, under its original line number (8 sections), so nothing is lost and each removal can be reviewed against the original. Not committed: docs/history/CLAUDE.local-archive.md, the same session's archive of CLAUDE.local.md. It is matched by a global gitignore rule, because CLAUDE.local.md is private session state. Checks: - markdownlint on both files: passes, after trimming one trailing blank line from the archive (MD012); - release_anchor, release_state_prose, contribution_checklist and provenance_record audits: all pass on the condensed file. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- AGENTS.md | 16 ++++++--------- docs/history/AGENTS-archive.md | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 docs/history/AGENTS-archive.md diff --git a/AGENTS.md b/AGENTS.md index a49cf998..e6fd5ca5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ - **NO OVER-ATTRIBUTION.** Do not tag a genuine oracle *comparison* ("matches Mesen2's behavior," "cross-checked against ares") as "derived from." Attribute real ports; leave genuinely-independent code independent. - **DO NOT SELF-CERTIFY.** Never assert "no third-party code is incorporated" / "license-clean" as a finished claim. Surface provenance status for human + expert review; state uncertainty. AI self-attestation of license compliance is not trustworthy — an outside NESdev reviewer, not the tooling, is what caught this. -- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions (2026-08-26).** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** **26 files** (at v2.7.1; the count is itself a snapshot — regenerate it, never quote this number) carry a `// Provenance:` header disclosing that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. This was found on the first task of v2.6.4: its headline job is fixing the five `SH`-group stores in the DUT, and `crates/rustynes-cpu/src/cpu.rs` line 3 discloses `SHA/SHX/SHY/SHS/TAS` as **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`) — so the single most relevant block of oracle source for that version is one that must not be read to write the RTL. Nothing in the tooling said so. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; regenerate the list with `grep -rln "^// Provenance:" crates` rather than trusting a snapshot. **The command used to read `crates/*/src/*.rs`, and that glob does not descend** — it silently missed `debugger/source_map.rs` and `bin/pgo_trainer.rs`, so it too reported a short list (24 against the real 26 at v2.7.1). `crates/rustynes-test-harness/tests/provenance_record_audit.rs` now walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. The ones that bite HDL work are `rustynes-cpu/src/cpu.rs` (SH group — rungs 1/5), **`rustynes-ppu/src/ppu.rs` (rungs 3/5 — the sprite-evaluation FSM, the OAM-data-bus model and the optional OAM-decay model from Mesen2 (the decay model was written at v2.1.4 and recorded only at v2.9.4), and the ALE/octal-latch address-multiplex and OAM-corruption behaviour from TriCNES)**, `rustynes-apu/src/blip.rs` (BLEP), **`rustynes-core/src/bus.rs` (DMA work — the OAM-DMA register-window read ported from TriCNES, and the DMA state modelled on its flags; header added 2026-09-28 at the maintainer's direction, after the v2.9.2 audit triage found the port disclosed only in a doc comment)**, and six mapper files (rung 7). **v2.9.9 added four (NC-17, maintainer 2026-10-04): `rustynes-apu/src/frame_counter.rs` (the lazy `$4015` clear and the PAL step table, from Mesen2) and `rustynes-apu/src/apu.rs` (the DMC-DMA state model, TriCNES / Mesen2) bite the sibling's APU parity work (the `$4017` rules); `rustynes-core/src/vs_dualsystem.rs` and `m019_namco163.rs` do not touch the RTL today.** **`ppu.rs` was missing from this sentence until 2026-09-19 and its absence was acted on**: the v2.6.22 CHR-during-rendering work read `ppu.rs` case 7 to form an RTL hypothesis, having checked the SENTENCE rather than running the command one line above it. The region read — the `$2007` PPUDATA write path — is outside all four disclosed regions, and the in-file note at `ppu.rs:777` re-states that scope, so nothing laundered; but the order was wrong, and it was an external reviewer that prompted the check rather than the process. **This sentence is a convenience, not the authority. The command is the authority, and the reason it exists is that a list of file names goes stale exactly the way the count above did — by ten files.** **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters. +- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions.** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** Files carrying a `// Provenance:` header disclose that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; list the files with `grep -rln "^// Provenance:" crates` (a glob such as `crates/*/src/*.rs` does not descend into subdirectories and silently misses files). **The command is the authority, not any list of file names or count: those go stale.** `crates/rustynes-test-harness/tests/provenance_record_audit.rs` walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters. Enforcement lives alongside the prose: `/ref-proj/` is gitignored/`.dockerignore`d/`.markdownlintignore`d and excluded from CodeRabbit; `deny.toml` gates dependency licenses; every derived file carries an SPDX + provenance header. A rule the tooling enforces beats a rule you are merely asked to follow. @@ -44,9 +44,7 @@ Enforcement lives alongside the prose: `/ref-proj/` is gitignored/`.dockerignore RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in pure Rust. The accuracy bar is Mesen2 / higan / ares: tight lockstep scheduling at PPU-dot resolution on a master-clock-precise timebase, sub-instruction PPU events visible to subsequent CPU code, and a lookup-table non-linear audio mixer with band-limited synthesis. The frontend is pure Rust (`winit` + `wgpu` + `cpal` + `egui`). -**Current release: v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here. - -- **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward. +**Current release: v3.0.1 "Mortar"** (2026-10-07) — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "Cornerstone"** (2026-10-06) — 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 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md)). **No hardware has run any MiSTer bitstream**: hardware verification moves to a later v3.x release (ADR 0043 supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0), and the SDRAM pin constraints stay provisional until the SuperStation One's memory is read. Suite counts, mapper matrix and per-release detail live in `docs/STATUS.md`, `CHANGELOG.md` and the GitHub releases; they are deliberately not duplicated here. The earlier release-by-release narrative and other condensed paragraphs are archived verbatim in `docs/history/AGENTS-archive.md`. - **Native Android app** — the **v1.8.0 → v1.8.9 "Android"** train (`crates/rustynes-mobile` UniFFI bridge + `crates/rustynes-android` JNI/NDK host + a Jetpack Compose app, ADR 0024): full on-device emulation, multi-touch + P1–P4 hardware controllers, wgpu `SurfaceView` rendering + the shared WGSL shader stack, save-states / battery SRAM, Lua, RetroAchievements, direct-IP + CGNAT/TURN room-code netplay, a box-art ROM library, and platform polish (adaptive / foldable / TV, Material You, capture / PiP / home-screen widget). Distributed as **GitHub-Releases sideload**; a free store listing is an unversioned later step with no monetization (ADR 0035, which superseded ADR 0025's v2.3.0 date; the `foss`/`play` flavour split remains). - **Native iOS / iPadOS app** — the **v1.9.0 → v1.9.9 "iOS" TestFlight train** (`crates/rustynes-ios` Metal + CoreAudio shim reusing `rustynes-mobile` verbatim → UniFFI-generated Swift, ADR 0026): a native SwiftUI shell over wgpu→Metal, multi-touch + GameController, the shader stack, TAS / HD-pack / palettes / per-game DB, Lua + RetroAchievements, LAN + room-code netplay, CloudKit save-state sync, accessibility + EN/ES i18n + ReplayKit + Game Center, and the v1.9.9 creator tools (Cheats, a FOSS-gated read-only debugger, a touch TAStudio piano-roll, foreign movie import, a host audio-depth DSP). Ships to **TestFlight**; a free App Store listing is an unversioned later step with no monetization (ADR 0035, which superseded ADR 0027's v2.3.0 date). Mobile ROM loading was iNES / NES 2.0 only until v2.9.7, which adds FDS (host-supplied BIOS), NSF and the Vs. DualSystem cabinet to the bridge (the Swift half uncompiled; run-sheet rows T1-T12). Readiness record: `docs/ios-v1.9.9-readiness.md`. @@ -70,9 +68,9 @@ Everything else about the release line is in `CHANGELOG.md`. --- -**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth (51 → **172 families**), ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. **AccuracyCoin holds 100% (139/139)** on every release including v2.0.0. Workspace baseline: edition 2024, Rust **1.96**, license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at . +**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth, ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. Workspace baseline: edition 2024, Rust **1.99** (see `rust-toolchain.toml`), license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at . -**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03, the base of the current v2.0.x "Harbor" line), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix. +**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix. ## Build / test / lint @@ -197,10 +195,10 @@ These cross-cutting decisions span multiple files. Reading individual chip docs - Branch names: `/`. - A chip-behavior change touches both the chip code and the chip's `docs/.md`. They drift apart easily; don't let them. - For accuracy work: pin the failing test ROM expectation first, then implement until it passes. -- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. The implemented core measures **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms** (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and ~4.46 / ~2.67 ms on the exact path (i9-10850K, 2026-09-23), which `docs/performance.md` records as knowingly accepted for the master-clock design. The stock `full_frame` benches select the exact path explicitly; from v2.2.3 to 2026-09-23 they silently measured the fast path, which `docs/performance.md` §"Current figures" corrects. That is ~23% of the 16.639 ms NTSC budget. The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column. (Until v2.7.5 this line said ">3% same-runner A/B bar", which contradicted the script; older records in `docs/performance.md` cite the bar that was in force when they were written.) +- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. `docs/performance.md` §"Current figures" holds the measured numbers and records them as knowingly accepted for the master-clock design (the stock `full_frame` benches select the exact path explicitly). The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column. - `unsafe` requires a `// SAFETY:` comment explaining the invariant. The chip stack is `#![no_std]` + `extern crate alloc;`; only `rustynes-frontend` and `rustynes-cheevos` (FFI) carry `unsafe`. - **Comprehensive rustdoc + comments (project rule).** Craft extensive `//!` crate/module preambles and `///` / `//` inline comments matching the quantity, quality, and technical depth of the existing `rustynes-*` crates — explain the *why* alongside the architectural detail, the memory-safety guarantees, and the lockstep-timing considerations. -- **Comprehensive commit bodies (project rule).** Commit message bodies are robust, comprehensive, and technically detailed: go beyond a summary to explain architectural impact, the mathematical implementation, memory constraints, and the deep technical specifics (the maintainer's house style; see `docs/guidelines`). +- **Comprehensive commit bodies (project rule).** Commit message bodies are robust, comprehensive, and technically detailed: go beyond a summary to explain architectural impact, the mathematical implementation, memory constraints, and the deep technical specifics (the maintainer's house style). - Code style: rustfmt defaults + the crate-level import grouping in `rustfmt.toml`; `.editorconfig` mandates UTF-8 / LF / a final newline and indentation of four spaces for Rust, two for Markdown / TOML / YAML. Justify any local `#[allow]`. ## Operating notes for Claude Code @@ -243,8 +241,6 @@ that is a reason to add a tenth — not a reason to grow this section back. - The v1.0.0 release + GitHub Pages/CI + post-release record is in `docs/v1.0.0-synthesis-handoff-2026-06-13.md` — read it before touching CI, Pages, or release tooling. Full per-release history is in `CHANGELOG.md`. - **Markdownlint is a CI gate** (pre-commit, pinned `markdownlint-cli v0.49.1`). The pin was v0.39.0 until the v2.6.3 dependency refresh, held because the newer local binary reported rules the pin lacked — chiefly **MD060** (`table-column-style`), which was therefore NOT gated. That is now measured and resolved: MD060's inferred default reads this corpus as style `compact` and reports **1,936 findings across 122 files** and nothing else, so `.markdownlint.json` pins `MD060` to the style actually in use (`leading_and_trailing`), which measures **zero** and rewrites no document. It IS a gate now. Still verify with `pre-commit run markdownlint --all-files` rather than the bare binary — the pin and the local build can drift apart again. `.markdownlint.json` also keeps `MD013`/`MD033`/`MD041` disabled by design (long technical tables, the README HTML banner/``, the HTML-led README). `.markdownlintignore` exempts `ref-docs/`, `ref-proj/` (the reference-emulator clone, now removed from disk but kept in the ignore lists as a firewall guard so it can never re-enter the tree — see the MOST IMPORTANT RULE section above), the vendored `tricnes/` + upstream READMEs, and the frozen `docs/archive/` + `to-dos/archive/` trees — don't lint or reformat those. - **RetroAchievements client identity:** the RA HTTP User-Agent (how RA authenticates/identifies/allowlists the client) is `RustyNES/ rcheevos/` — the `RA_USER_AGENT` const in `crates/rustynes-cheevos/src/http.rs`; the rcheevos version auto-syncs from the vendored `rc_version.h` via `build.rs` (`RCHEEVOS_VERSION`). Keep the leading `RustyNES/` token (a regression test guards it). -- **Exhaustive Documentation Sweeps:** When tasked with generating comprehensive project documentation or wikis, always recursively list and read the contents of `docs/`, `ref-docs/`, and `to-dos/` to ensure no deep technical knowledge is missed. -- **GitHub Wiki Initialization:** When assisting with GitHub Wiki deployments for the first time, instruct the user to click "Create the first page" in the GitHub UI to provision the `.wiki.git` repository. If the Wiki is cloned locally inside the main repository, ensure its folder (e.g., `RustyNES.wiki/`) is added to `.gitignore`. - **Symlinked Agent Configs:** Ensure symlinked agent files (like `GEMINI.md` -> `AGENTS.md`) are explicitly removed from `.gitignore` so they are correctly tracked by version control. <<< MC-PROJECT-END >>> diff --git a/docs/history/AGENTS-archive.md b/docs/history/AGENTS-archive.md new file mode 100644 index 00000000..b825a4d6 --- /dev/null +++ b/docs/history/AGENTS-archive.md @@ -0,0 +1,37 @@ +# History archive for AGENTS.md + +## Archived from AGENTS.md on 2026-10-07 + +Paragraphs removed or condensed in the live file, reproduced verbatim (original line numbers in the labels). The live file keeps the rule in each case. + +### Original line 39: provenance paragraph (file-by-file snapshot and incident narrative) + +- **THE FIREWALL IS PER-REGION, NOT PER-REPO — the oracle's own source has exceptions (2026-08-26).** "RustyNES's code is ours, so it is readable" is true of the repository and **not uniformly true of every block in it.** **26 files** (at v2.7.1; the count is itself a snapshot — regenerate it, never quote this number) carry a `// Provenance:` header disclosing that a REGION of them is derived from a GPL reference emulator, and those regions are **black boxes for HDL purposes even though the file is ours** — writing SystemVerilog from them launders the original expression into the DUT *through* us, which is precisely what ADR 0037 exists to prevent. This was found on the first task of v2.6.4: its headline job is fixing the five `SH`-group stores in the DUT, and `crates/rustynes-cpu/src/cpu.rs` line 3 discloses `SHA/SHX/SHY/SHS/TAS` as **derived from Mesen2's `SyaSxaAxa`** (`Core/NES/NesCpu.h`) — so the single most relevant block of oracle source for that version is one that must not be read to write the RTL. Nothing in the tooling said so. **Before reading oracle source to inform sibling/HDL work, `grep -n "Provenance:" `**; regenerate the list with `grep -rln "^// Provenance:" crates` rather than trusting a snapshot. **The command used to read `crates/*/src/*.rs`, and that glob does not descend** — it silently missed `debugger/source_map.rs` and `bin/pgo_trainer.rs`, so it too reported a short list (24 against the real 26 at v2.7.1). `crates/rustynes-test-harness/tests/provenance_record_audit.rs` now walks the whole tree and fails if a header and its §1 row in `docs/originality-and-provenance.md` disagree in either direction. The ones that bite HDL work are `rustynes-cpu/src/cpu.rs` (SH group — rungs 1/5), **`rustynes-ppu/src/ppu.rs` (rungs 3/5 — the sprite-evaluation FSM, the OAM-data-bus model and the optional OAM-decay model from Mesen2 (the decay model was written at v2.1.4 and recorded only at v2.9.4), and the ALE/octal-latch address-multiplex and OAM-corruption behaviour from TriCNES)**, `rustynes-apu/src/blip.rs` (BLEP), **`rustynes-core/src/bus.rs` (DMA work — the OAM-DMA register-window read ported from TriCNES, and the DMA state modelled on its flags; header added 2026-09-28 at the maintainer's direction, after the v2.9.2 audit triage found the port disclosed only in a doc comment)**, and six mapper files (rung 7). **v2.9.9 added four (NC-17, maintainer 2026-10-04): `rustynes-apu/src/frame_counter.rs` (the lazy `$4015` clear and the PAL step table, from Mesen2) and `rustynes-apu/src/apu.rs` (the DMC-DMA state model, TriCNES / Mesen2) bite the sibling's APU parity work (the `$4017` rules); `rustynes-core/src/vs_dualsystem.rs` and `m019_namco163.rs` do not touch the RTL today.** **`ppu.rs` was missing from this sentence until 2026-09-19 and its absence was acted on**: the v2.6.22 CHR-during-rendering work read `ppu.rs` case 7 to form an RTL hypothesis, having checked the SENTENCE rather than running the command one line above it. The region read — the `$2007` PPUDATA write path — is outside all four disclosed regions, and the in-file note at `ppu.rs:777` re-states that scope, so nothing laundered; but the order was wrong, and it was an external reviewer that prompted the check rather than the process. **This sentence is a convenience, not the authority. The command is the authority, and the reason it exists is that a list of file names goes stale exactly the way the count above did — by ten files.** **The escalation ladder, maintainer-directed, in order — exhaust each rung before the next:** (1) vendored public documentation; (2) **the open Internet** — the vendored wiki is PARTIAL, documenting `SHX`/`SHY` in full and carrying nothing on `SHA`/`TAS`, which one web search supplied; (3) **black-box comparison** — a per-cycle golden diff needs no source at all and usually resolves faster, because at that point the question is "which cycle differs", not "what is the rule"; (4) the derived oracle source, **last resort**. Rung 4 is permitted — the licences are compatible, both repos being GPL-3.0-or-later — **but the existing attributions live in the ORACLE, and the sibling is a separate repository**, so reading it obliges declaring the derivation there too: a site comment, the sibling's provenance doc, `NOTICE`, and an ADR 0037 amendment naming who authorised it. Never silently. On v2.6.4 rungs 1-3 were sufficient and the escalation went unspent — worth knowing, because the pull toward rung 4 is strongest exactly when the DUT and the oracle disagree, which is the moment this rule matters. + +### Original line 47: release paragraph + +**Current release: v3.0.0 "Cornerstone"** (2026-10-06) — 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"** (2026-10-04) — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** (2026-10-02) — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** (2026-09-30) — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** (2026-09-30) — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** (2026-09-29) — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** (2026-09-29) — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** (2026-09-29) — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. The mobile device runs and the SuperStation One board session move after v3.0.0 (maintainer, 2026-09-29). Built on **v2.9.2 "Candidate"** (2026-09-28) — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** (2026-09-27) — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** (2026-09-26) — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** (2026-09-26) — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** (2026-09-25) — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). v2.9.2 triaged a fifth, AI-written audit of both repositories (`docs/audits/v2.9.2-full-audit-report.md`, 32 findings) in its ledger, `docs/audits/v2.9.2-full-audit-disposition.md`: 16 fixed red-first, 2 changed but verifiable only on a device (the Swift halves of AUD-10/14), and the rest refuted, declined or deferred with evidence. The sweep written for AUD-02 found that save states dropped the cartridge RAM of twelve board families; `every_board_snapshot_carries_cartridge_ram` now round-trips the RAM of all 174 mapper ids. It changes emulation behaviour in one deliberate place: an unmapped `$4020-$FFFF` read now updates the CPU's internal data bus (AUD-03). Opposing-direction cancel (AUD-08/09/10) is on by default, by the maintainer's decision (2026-09-28). The MiSTer CPU's /NMI edge detector ran on the DMA-stalled enable and lost an NMI raised inside a DMA (AUD-24, gate `dmanmi074`). v2.9.1 fixed `scripts/perf/ab_check.sh` (both sides had built into one target directory) and re-measured the old rejections; the dead NMI detector's per-dot call (−4.1% to −4.7% on palette frames) is removed in v2.9.8, which carries ADR 0042's removals (its 2026-10-01 amendment). The libretro core is built with `panic = "unwind"`, exercised by a C-ABI harness (`crates/rustynes-libretro/src/abi_tests.rs`), and patches a vendored `rust-libretro-sys` (`vendor/`). **AccuracyCoin 144/144 and nestest 0-diff** hold on the v2.9.2 tree, and the full `--features test-roms` suite passes 2,867 tests. v2.7.4's mobile changes, and v2.9.2's Swift, have a device checklist (`docs/mobile-v2.7.4-device-checklist.md`), consolidated at v2.9.3 into `docs/mobile-v2.9.3-run-sheet.md` with an emulator pre-run; the device run itself moved after v3.0.0 (maintainer, 2026-09-29). The co-simulation is **173 passed, 0 failed, 1 expected failure** on-die and **174 / 0 / 1** off-die (`USE_SDRAM=1`), each ONE frozen-worktree ladder run with nothing skipped; both bitstreams compile at fitter seed 2, chosen from eight per build at one build date (on-die +0.510 ns setup / +0.108 ns hold, off-die +0.390 / +0.081; the SDRAM read is +0.447 setup / +1.184 hold), and two clean compiles of each are byte-identical. The SDRAM pin constraints stay provisional until the SuperStation One's memory is read. **No hardware has run any bitstream**. The board session was planned on v2.9.2's bitstreams (`tools/stage_board_kit.sh` in the sibling stages what it needs); the maintainer moved it after v3.0.0 (2026-09-29). That settled what v3.0.0 ships: [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) makes it the API major with a release-candidate core, and hardware verification moves to a later v3.x release (it supersedes [ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md)'s hardware-verified v3.0.0). Per-release detail lives in `CHANGELOG.md` and the GitHub releases; it is deliberately not duplicated here. + +### Original line 49: Timebase (v2.0.0) bullet + +- **Timebase (v2.0.0)** — the scheduler substrate is rewritten from a five-counter dot-lockstep model to a single canonical cycle counter, every CPU cycle clocked in two halves (`start_cycle` / `end_cycle`) with any bus access split between them, and the PPU caught up to each half (ADR 0002 / ADR 0029), now the *only* scheduler path. This is a MAJOR-boundary breaking change (ADR 0003): `.rns` save-state and `.rnm` movie format epochs bump (ADR 0028) — a pre-v2.0.0 `.rns` slot now fails to load with a clear error instead of silently misinterpreting stale bytes. Landed across five betas + rc.1 (PRs #217–223). Also new: core-level **Vs. `DualSystem`** dual-console support (`Emu::Dual`, `crates/rustynes-core`) for the four Vs. arcade cabinet boards — core-and-test-harness-only, frontend wiring deferred. The R1/R2 MMC3 IRQ-timing residual is by-design-deferred beyond this release with a mechanism-level finding recorded in ADR 0002 (not closed, not silently dropped). **AccuracyCoin now measures 141/141 (100.00%)**: the v2.0.1 upstream AccuracyCoin re-sync grew the catalog to 146 rows / 141 assigned tests and briefly opened two new PPU gaps ("ALE + Read" $0491, "Hybrid Addresses" $0492), which **v2.0.3** closed by promoting the 2-cycle-ALE PPU fetch model to the unconditional default (both experimental flags retired; additive `PPU_SNAPSHOT_VERSION` v5 tail). AccuracyCoin held 100% (139/139) throughout the v2.0.0 betas and final cut, dipped to 139/141 under the v2.0.1 re-sync, and is back to a full 141/141 from v2.0.3 onward. + +### Original line 73: release history paragraph + +**Release history → `CHANGELOG.md`.** The full per-release detail — features, the mapper-count growth (51 → **172 families**), ADRs, and PR trains for **v1.0.0 → v2.0.0** (plus the documentary engine-lineage stages v0.9.0–v0.9.7) — lives in `CHANGELOG.md` (the single source of truth for user-visible change), the per-release GitHub Releases, and `to-dos/plans/`. Every release through v1.10.0 was **additive / off-by-default**, so with new features off those builds stayed byte-identical; **v2.0.0 was RustyNES's first designated breaking release** (ADR 0003; v2.9.8 carried v3.0.0's breaks early, see above) — the one-clock, every-cycle-bus-access scheduler (ADR 0002 / ADR 0029) is now the *only* path, and the old PPU-dot lockstep model is retired. **AccuracyCoin holds 100% (139/139)** on every release including v2.0.0. Workspace baseline: edition 2024, Rust **1.96**, license **GPL-3.0-or-later** (RustyNES is a derivative work of GPL emulators — Mesen2 GPLv3, puNES/FCEUX/Nestopia GPLv2-or-later; relicensed in v2.2.9 per ADR 0036, credited in `docs/originality-and-provenance.md` + `NOTICE`), author **DoubleGate**; the WebAssembly / GitHub Pages build is live at . + +### Original line 75: engine-lineage paragraph + +**Engine-lineage versioning (read carefully).** The core descends from an accuracy program whose internal "v1.x / v2.x" milestones are folded into RustyNES stages v0.9.0–v0.9.7 → the v1.0.0 production cut. Read deep-narrative "v2.0" anchors from before 2026-07-03 (the master-clock refactor, old ADRs / audit logs under `docs/`) as **upstream engine lineage**, never as RustyNES release versions — that engine-lineage v2.0 work shipped as the v1.0.0 production core (2026-06-13) and is a *different* thing from RustyNES's own **v2.0.0 "Timebase"** release (2026-07-03, the base of the current v2.0.x "Harbor" line), which replaces that same dot-lockstep scheduler with the one-clock model. `docs/STATUS.md` is the authoritative per-suite pass-count + mapper matrix. + +### Original line 200: hot-path / frame-cost bullet (dated benchmark figures) + +- Hot paths (`Cpu::tick`, `Ppu::tick`, mapper register access): no allocations, prefer fixed arrays, profile (`cargo bench` + `perf record`) before adding abstractions. **On the frame-cost number:** the `≤ 2 ms/frame headless` figure in `docs/performance.md` is a **design-phase aspiration** (written before the cycle-accurate core existed, for 2018-era Skylake) — it is NOT a live gate. The implemented core measures **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms** (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and ~4.46 / ~2.67 ms on the exact path (i9-10850K, 2026-09-23), which `docs/performance.md` records as knowingly accepted for the master-clock design. The stock `full_frame` benches select the exact path explicitly; from v2.2.3 to 2026-09-23 they silently measured the fast path, which `docs/performance.md` §"Current figures" corrects. That is ~23% of the 16.639 ms NTSC budget. The dominant costs are work the accuracy model *requires* — `cpu_clock` is APU BLEP synthesis + the non-linear mixer (mixer ceiling measured ≤1.9%), and `Ppu::tick` is the per-dot lockstep loop — and the obvious levers were already measured and **rejected** (`emit_pixel` bounds-check elision was *slower*; the SIMD blitter was *slower*). Do not "optimize toward 2 ms" by trading away accuracy; the real-world multiplier on frame cost is **run-ahead**, not the per-frame core cost. Any optimization must be **byte-identical**, and must pass the adoption rule in `scripts/perf/ab_check.sh`: **the bar is evidence quality, not effect size** (maintainer decision, v2.3.1) — a consistent, reproduced, statistically clean gain is adoptable even below 3%. What is not negotiable is the second independent run (a single run has already produced a p = 0.00 result on all four workloads that was pure artifact), and a mixed-sign result across workloads is a rejection, not an average. Read the A/B/A order-bias control before the candidate column. (Until v2.7.5 this line said ">3% same-runner A/B bar", which contradicted the script; older records in `docs/performance.md` cite the bar that was in force when they were written.) + +### Original line 246: Exhaustive Documentation Sweeps bullet + +- **Exhaustive Documentation Sweeps:** When tasked with generating comprehensive project documentation or wikis, always recursively list and read the contents of `docs/`, `ref-docs/`, and `to-dos/` to ensure no deep technical knowledge is missed. + +### Original line 247: GitHub Wiki Initialization bullet + +- **GitHub Wiki Initialization:** When assisting with GitHub Wiki deployments for the first time, instruct the user to click "Create the first page" in the GitHub UI to provision the `.wiki.git` repository. If the Wiki is cloned locally inside the main repository, ensure its folder (e.g., `RustyNES.wiki/`) is added to `.gitignore`. From a5f7aea36c9af589867cf741ede8d609b9944e90 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 07:16:20 -0400 Subject: [PATCH 39/44] fix(ci): the NDK install step and the handbook build, both red on #590 #590's first ready-mode run had two failures. Both came from this release's own changes, which no earlier run had exercised. 1. Android, NDK cross-build + UniFFI bindings: "Resolve NDK" exited 1 after `yes: standard output: Broken pipe`. v3.0.1 replaced the runner's preinstalled NDK with an `sdkmanager --install "ndk;30.0.16248370"` step under `set -euo pipefail`, and fed it the licence answers with `yes | sdkmanager`. Once sdkmanager finishes and closes the pipe, `yes` exits with EPIPE (or is killed by SIGPIPE: 141). Under pipefail that status fails the pipeline even when the install succeeded. sdkmanager's own output goes to /dev/null, so the log showed nothing else. Reproduced locally: `set -euo pipefail; yes | head -1` exits 141. Fix in all three Resolve NDK steps: `(yes || true) | sdkmanager ...`. Only yes's status is absorbed. Checked: - a succeeding installer gives rc 0; - `(yes || true) | false` still gives rc 1; - the existing `test -x .../clang` check still fails the step if the NDK is not on disk. Not run before: the heavy Android jobs only run on a ready PR, and this is the first ready run since the NDK move (4aae8c13). 2. Pages, build demo + docs: `mkdocs build --strict` aborted on two warnings. They came from docs/history/AGENTS-archive.md (de90569b), whose verbatim AGENTS.md paragraphs keep repo-root links (`docs/adr/0043-...`, `docs/adr/0041-...`); from inside docs/history/ those links point nowhere. The archive is engineering-only material, the same class as `agents/`, `archive/` and `audits/`, which the published handbook already excludes. `history/` joins `exclude_docs`, so the archive stays verbatim and the rewriting hook has nothing to do. A local `mkdocs build --strict` with the CI's pinned mkdocs-material range now builds with no warning. The run also logs one INFO-level performance.md anchor message; it is older than this change and not a strict-mode failure. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .github/workflows/android.yml | 18 +++++++++++++++--- mkdocs.yml | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index c96114c7..cb9f0f71 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -141,7 +141,11 @@ jobs: run: | set -euo pipefail sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" - yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null + # `yes` answers the licence prompt. Under `pipefail` its own exit + # status (EPIPE / SIGPIPE once sdkmanager closes the pipe, 141 here) + # failed the step even when the install succeeded, so it is absorbed; + # sdkmanager's status, and the clang check below, still decide. + (yes || true) | "$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; } @@ -223,7 +227,11 @@ jobs: run: | set -euo pipefail sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" - yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null + # `yes` answers the licence prompt. Under `pipefail` its own exit + # status (EPIPE / SIGPIPE once sdkmanager closes the pipe, 141 here) + # failed the step even when the install succeeded, so it is absorbed; + # sdkmanager's status, and the clang check below, still decide. + (yes || true) | "$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; } @@ -293,7 +301,11 @@ jobs: run: | set -euo pipefail sdk="${ANDROID_SDK_ROOT:-$ANDROID_HOME}" - yes | "$sdk/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null + # `yes` answers the licence prompt. Under `pipefail` its own exit + # status (EPIPE / SIGPIPE once sdkmanager closes the pipe, 141 here) + # failed the step even when the install succeeded, so it is absorbed; + # sdkmanager's status, and the clang check below, still decide. + (yes || true) | "$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; } diff --git a/mkdocs.yml b/mkdocs.yml index dc0b6a9f..25fd564a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -183,6 +183,7 @@ nav: exclude_docs: | adr/ agents/ + history/ audits/ archive/ audit/ From 0f902095ba6186dce6e3613e99135c2ee4718d7a Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 07:16:52 -0400 Subject: [PATCH 40/44] docs(agents): block-buffer 0.10.4 is locked but never compiled; Socket scores Records the v3.0.1 investigation of two Socket findings on #590 and main: GHSA-qwgh-2vcv-g2f7 against block-buffer 0.10.4 (locked only as an optional dependency of ratatui's unused termwiz backend; 0 compiled paths in every build; no upstream fix exists), and the supply-chain scores of tokio, libc and objc2 (fresh releases by their usual maintainers; libc's first Trusted Publishing release; tokio wasm-only). Commands and numbers are in the bullets. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- docs/agents/dependencies.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/agents/dependencies.md b/docs/agents/dependencies.md index e9a8ea1d..208bda08 100644 --- a/docs/agents/dependencies.md +++ b/docs/agents/dependencies.md @@ -15,3 +15,5 @@ - **The egui 0.36 / wgpu 30 bump is blocked on ONE upstream RELEASE, not on us — re-measured 2026-09-18.** `chore/egui-0.36-wgpu-30-blocked` is the ONLY copy of that migration: `main` is on `egui = "0.35"` / `wgpu = "29"`, so **deleting the branch destroys the work**. The blocker was never this project's code. `egui::DroppedFile` declares `bytes()` under `#[cfg(not(target_arch = "wasm32"))]` while `egui-winit`'s `NativeFile` implements it unconditionally, so the dependency itself fails to compile for wasm32 with `E0407`, and RustyNES ships a wasm demo. **Upstream fixed it**: PR #8516 "Fix egui-winit compilation on wasm32", merged **2026-09-07** into `main`, gating `mod dropped_file;` behind `cfg(not(target_arch = "wasm32"))`. **It is in no published version** — 0.36.2 shipped **2026-09-08**, the day AFTER that merge, without it. Both ends were verified by COMPILING a throwaway crate carrying this project's exact feature set (`default-features = false`, `links`/`wayland`/`x11`): 0.36.2 fails with the identical `E0407`, and rev `da7169ed` finishes clean. **The unblock condition is therefore a version, not an investigation**: the first published `egui-winit` whose `src/lib.rs` gates `mod dropped_file;` — expect 0.36.3 or 0.37. Four traps around this, every one of which cost a wrong conclusion here. **The fix is in `lib.rs`, NOT in the file the error cites** — `dropped_file.rs` still carries the unconditional `impl` on upstream `main`, so reading the file the compiler points at reads as "still broken" when it is fixed one level up. **`emilk/egui`'s default branch is `main`**, and a stale `master` ref also exists and answers `raw.githubusercontent.com` requests, so fetching `master` silently describes the wrong tree. **A `[patch.crates-io]` to that rev is SILENTLY IGNORED** against a `0.36.2` requirement, because the git rev declares `0.36.1` and does not satisfy `^0.36.2` — cargo drops the patch and builds the registry copy while the error still names the registry path; depend on the git rev directly, or require `"0.36"`, when probing. And **using the rev at all is refused by this project's own policy**: `deny.toml` sets `unknown-git = "deny"` with `allow-git = []`, and `Cargo.lock` carries zero git-sourced packages, so landing it early means widening the supply-chain policy — a maintainer decision, examined on 2026-09-18 and deliberately NOT taken in favour of waiting for the release. When it lands: the branch was cut at v2.3.2 against a `main` now at v2.6.20, so **rebase before trusting it**, and remember the bump is atomic — `egui-wgpu` 0.36 requires wgpu 30, so egui/egui-wgpu/egui-winit/wgpu/naga move together or the graph carries wgpu 29 and 30 at once and every device/queue handoff fails to unify. The five API deltas the branch already solved are enumerated in its commit body (`git log origin/chore/egui-0.36-wgpu-30-blocked`). - **lz4_flex 0.14+ requires the crate's own `alloc` feature explicitly** for `compress_prepend_size`/`decompress_size_prepended` (used by `rewind.rs`/`zwinder.rs`) — it split real no_std support into an `alloc`-vs-`std` distinction that didn't exist in 0.13. A `cargo build --workspace` will NOT catch a missing `alloc` feature here because `rustynes-core`'s own default-on `std` feature implies it via cargo's feature unification; only a standalone `cargo build -p rustynes-core --target thumbv7em-none-eabihf --no-default-features` (the exact CI `no_std build` job) will. Run that command locally before pushing any bump that touches this dependency. +- **A SCANNER'S "VULNERABLE DEPENDENCY" MAY BE A PACKAGE NO BUILD COMPILES: CHECK THE GRAPH, NOT THE LOCKFILE (v3.0.1).** Socket flagged GHSA-qwgh-2vcv-g2f7 (block-buffer, "panic corrupts inline buffer position", fixed only in 0.12.1) against `block-buffer 0.10.4` on `main`, marked "Not fixable". Both halves were right, and it still does not apply. The lockfile chain is `ratatui 0.30 -> ratatui-termwiz -> termwiz 0.23.3 -> sha2 0.10 -> digest 0.10 -> block-buffer 0.10.4`. Cargo locks a dependency's OPTIONAL dependencies whether or not any feature enables them (ratatui's `ratatui-termwiz`, `ratatui-termina` and `serde` are all locked), and nothing here enables ratatui's termwiz backend: `help-tui` takes ratatui's defaults (crossterm). `cargo tree -e normal,build | grep -c termwiz` is 0 for the default frontend, `--features full`, the wasm32 target and the whole workspace, and `cargo deny check advisories` passes. No upstream fix exists: termwiz's newest release (0.23.3, 2025-03-20) still requires `sha2 ^0.10`, which no patched block-buffer satisfies. Dismiss it in the scanner as not compiled; re-check if a feature ever enables a ratatui backend other than crossterm. +- **SOCKET'S SUPPLY-CHAIN SCORE IS NOT A VULNERABILITY SCORE (v3.0.1).** On #590 it scored tokio 1.53.2 at 58, libc 0.2.190 at 78 (-9) and objc2 0.6.5 at 79, each with Vulnerability, Maintenance and License at 100. Those releases were 2-5 days old, and published by their usual maintainers (Darksonn, madsmtm). libc 0.2.190 was the crate's first crates.io Trusted Publishing release, published by a GitHub workflow rather than a person. tokio showed no delta, so its 58 is unchanged from 1.53.1. The specific alerts are only visible in the logged-in dashboard (the public package pages return 403 to scripts). Exposure: tokio enters only through `wasm-bindgen-futures` on wasm32 and is never compiled natively; objc2 only on Apple targets; libc everywhere (alsa, cpal, directories). Holding a crate back over a fresh-release score contradicts taking every dependency at its newest, and buys nothing a vulnerability score would show. From eecf8eaf382b8d59ebfed88ed12c43706e2bb5c2 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 07:28:20 -0400 Subject: [PATCH 41/44] docs: remove the 1.96 split everywhere it was still stated as current 31758270 moved the libretro buildbot to Rust 1.99 and gave every crate the workspace floor. It left the split described as current in places the first sweep missed (`git grep 1.96`, not run then). Copilot (#590, #591, #593), CodeRabbit (#591) and agy (#590, #591, #593) each reported subsets. Fixed: - the seven libretro-path Cargo.toml files: the "builds this crate on Rust 1.96.0" comment is deleted, since the inherited workspace value is the fact; - .cargo/config.toml (the MSRV-aware resolver note); - ARCHITECTURE.md's tree line; - CONTRIBUTING.md's MSRV bullet; - docs/build-and-tooling.md; - docs/dev/BUILD.md, whose RUSTUP_TOOLCHAIN=1.96.0 command would now be rejected by cargo; - docs/dev/STYLE_GUIDE.md; - docs/benchmarks.md; - the webOS comment in .gitlab-ci.yml, plus a header on its #91899 post-mortem saying which two details have moved since (the pin value, and `rustup toolchain install ... --target` in place of `rustup target add`; Copilot on #592); - the bus.rs reborrow comment. The `&mut *self.ram` reborrow is KEPT: iterating `&mut Box<[T; N]>` needs 1.97+, both forms compile on the 1.99 floor, and changing working code buys nothing (agy suggested removing it). Kept: every 1.96 reference that is dated history (CHANGELOG entries, the 2026-07-20 buildbot post-mortem, rust-toolchain.toml's account of the split, the plans). A second `git grep -E "1\.96"` after this change shows only those. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .cargo/config.toml | 4 ++-- .gitlab-ci.yml | 14 ++++++++++---- ARCHITECTURE.md | 4 ++-- CONTRIBUTING.md | 2 +- crates/rustynes-apu/Cargo.toml | 1 - crates/rustynes-core/Cargo.toml | 1 - crates/rustynes-core/src/bus.rs | 7 ++++--- crates/rustynes-cpu/Cargo.toml | 1 - crates/rustynes-gamedb/Cargo.toml | 1 - crates/rustynes-libretro/Cargo.toml | 1 - crates/rustynes-mappers/Cargo.toml | 1 - crates/rustynes-ppu/Cargo.toml | 1 - docs/benchmarks.md | 2 +- docs/build-and-tooling.md | 2 +- docs/dev/BUILD.md | 2 +- docs/dev/STYLE_GUIDE.md | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 7f34c53b..d989c50d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -22,8 +22,8 @@ rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=getrandom_backend=\"wasm_js\""] # 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 +# Every crate inherits the workspace's rust-version = "1.99" (seven libretro-path +# crates declared "1.96" for one day in v3.0.1); `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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e50ccb2..09b97189 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,12 +30,18 @@ # --------------------------------------------------------------------------- # Why every job carries a `before_script` (pipeline #91899 post-mortem) # --------------------------------------------------------------------------- +# (History, 2026-07-20. The mechanism still holds; two details moved since: +# the pin is whatever `rust-toolchain.toml` says (1.99.0 at v3.0.1), and since +# v3.0.1 each job runs `rustup toolchain install ${RUSTUP_TOOLCHAIN} --profile +# minimal --target ${RUST_TARGET}`, which installs the pinned toolchain WITH +# its target in one step, where this record says `rustup target add`.) +# # The first buildbot run passed only `libretro-build-linux-x64`; the other # nine died with `E0463: can't find crate for core`. Root cause is entirely # on our side, not the build images: # -# This repo pins its toolchain in `rust-toolchain.toml` (`channel = -# "1.96.0"`). The build image ships its own default toolchain with every +# This repo pinned its toolchain in `rust-toolchain.toml` (`channel = +# "1.96.0"` then). The build image ships its own default toolchain with every # libretro cross target pre-provisioned — but our pin makes rustup download # and install a SEPARATE, pristine 1.96.0 toolchain on the first `cargo` # invocation, and that toolchain carries only the host `rust-std` plus the @@ -360,8 +366,8 @@ libretro-build-tvos-arm64: # Built in libretro's webOS image, which has its own rustup, libclang and the # LG webOS SDKs. The template's `before_script` sources the SDK environment, # so it is kept via `!reference`, and the #91899 `rustup target add` follows -# it: the `channel` pin makes rustup fetch a pristine 1.96.0 here too, without -# the targets the image installed on its own default toolchain. +# it: the pinned toolchain (`RUSTUP_TOOLCHAIN`) is a pristine install here too, +# without the targets the image installed on its own default toolchain. libretro-build-webos-armv7a: extends: - .libretro-rust-webos-armv7a-default diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 33d2d21f..c23c5491 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,7 +1,7 @@ # RustyNES Architecture **Document Version:** 2.1.0 -**Last Updated:** 2026-08-30 +**Last Updated:** 2026-10-07 **Applies to:** RustyNES v3.0.1 (the scheduling model is v2.0.0 "Timebase" onward) This document fixes the high-level architecture of RustyNES. The per-subsystem specs under `docs/` (`cpu-6502.md`, `ppu-2c02.md`, `apu-2a03.md`, `mappers.md`, `scheduler.md`) take these decisions as given and elaborate one chip each. After reading this you should know the workspace shape, the scheduling model, the public boundary, and the load-bearing invariants. The canonical, always-current architecture spec is [`docs/architecture.md`](docs/architecture.md); this file is the top-level companion. @@ -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.99; libretro path 1.96) +├── Cargo.toml # Workspace definition (edition 2024, MSRV 1.99 for every crate) ├── crates/ │ ├── rustynes-core/ # Glue: Nes struct, run loop, scheduler, Bus, │ │ # save state, region config. Re-exports chip crates. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95c2ad02..b19cb96f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. **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. +- **Edition:** Rust 2024. **Toolchain:** 1.99 (pinned in `rust-toolchain.toml`). **MSRV:** 1.99 for every crate. The libretro buildbot uses the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`), and CI fails if the two ever differ, so move both in one change. - 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/crates/rustynes-apu/Cargo.toml b/crates/rustynes-apu/Cargo.toml index cdf6b839..ad1c9df4 100644 --- a/crates/rustynes-apu/Cargo.toml +++ b/crates/rustynes-apu/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-apu" description = "Cycle-accurate Ricoh 2A03 APU (Audio Processing Unit) for the NES" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-core/Cargo.toml b/crates/rustynes-core/Cargo.toml index 83635cd2..1039df0a 100644 --- a/crates/rustynes-core/Cargo.toml +++ b/crates/rustynes-core/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-core" description = "Cycle-accurate NES emulator core: scheduler, bus, save state, public Nes facade" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-core/src/bus.rs b/crates/rustynes-core/src/bus.rs index 759c858b..ae2de772 100644 --- a/crates/rustynes-core/src/bus.rs +++ b/crates/rustynes-core/src/bus.rs @@ -1260,9 +1260,10 @@ impl SystemBus { // Byte 3 (bits 24-31) — extracted without a truncating cast. s.to_le_bytes()[3] }; - // `&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. + // `&mut *self.ram` reborrows the array: iterating `&mut Box<[T; N]>` + // directly needs Rust 1.97+. The crate's floor is 1.99, so either form + // compiles today; the reborrow dates from v3.0.1's one-day split, when + // the libretro buildbot briefly built this crate on 1.96. for byte in &mut *self.ram { *byte = next(); } diff --git a/crates/rustynes-cpu/Cargo.toml b/crates/rustynes-cpu/Cargo.toml index 9e22b5a0..dbd11541 100644 --- a/crates/rustynes-cpu/Cargo.toml +++ b/crates/rustynes-cpu/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-cpu" description = "Cycle-accurate Ricoh 2A03 (6502 derivative) CPU core for the NES" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-gamedb/Cargo.toml b/crates/rustynes-gamedb/Cargo.toml index fa3c958e..e5d407f6 100644 --- a/crates/rustynes-gamedb/Cargo.toml +++ b/crates/rustynes-gamedb/Cargo.toml @@ -11,7 +11,6 @@ name = "rustynes-gamedb" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-libretro/Cargo.toml b/crates/rustynes-libretro/Cargo.toml index 60fd7170..9fb7d833 100644 --- a/crates/rustynes-libretro/Cargo.toml +++ b/crates/rustynes-libretro/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-libretro" description = "Cycle-accurate NES emulator libretro core wrapper" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-mappers/Cargo.toml b/crates/rustynes-mappers/Cargo.toml index 56f8fc2a..9bf50c06 100644 --- a/crates/rustynes-mappers/Cargo.toml +++ b/crates/rustynes-mappers/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-mappers" description = "Cartridge file format parsing and mapper implementations for the NES" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/crates/rustynes-ppu/Cargo.toml b/crates/rustynes-ppu/Cargo.toml index 1f3c46b7..9b96a974 100644 --- a/crates/rustynes-ppu/Cargo.toml +++ b/crates/rustynes-ppu/Cargo.toml @@ -3,7 +3,6 @@ name = "rustynes-ppu" description = "Cycle-accurate Ricoh 2C02 PPU (Picture Processing Unit) for the NES" version.workspace = true edition.workspace = true -# The libretro buildbot builds this crate on Rust 1.96.0 (`.gitlab-ci.yml`). rust-version.workspace = true license.workspace = true authors.workspace = true diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 31c47bd8..b73c64ee 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -52,7 +52,7 @@ changing. (See the Phase-7/F "Option X" note in `rustynes-core/Cargo.toml`.) - **Host:** Intel Core i9-10850K @ 3.60 GHz (10C/20T, Comet Lake), CachyOS Linux, `powersave` cpufreq governor. -- **Toolchain:** rustc 1.86.0 (the toolchain pinned when these were measured; the pin is 1.96 at v3.0.0), release +- **Toolchain:** rustc 1.86.0 (the toolchain pinned when these were measured; the pin is 1.99 since v3.0.1), release profile `opt-level = 3`, `lto = "thin"`, `codegen-units = 1`, `panic = "abort"`, `overflow-checks = false` (the `bench` profile inherits `release`). diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md index 12df0751..1256ac55 100644 --- a/docs/build-and-tooling.md +++ b/docs/build-and-tooling.md @@ -5,7 +5,7 @@ ## Toolchain - **Rust edition**: 2024. -- **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.) +- **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate. The libretro buildbot builds on the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`, which CI's `libretro-cross` checks against `rust-toolchain.toml`); v3.0.1 held its seven crates at 1.96 for one day, until a test pipeline showed the build image no longer passed `-C ar`. (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). diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 4eba091f..f63f8d18 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -21,7 +21,7 @@ ### Required -- **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`. +- **Rust** 1.99.0 (pinned in `rust-toolchain.toml`; the channel auto-installs). The libretro buildbot uses the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`; CI fails if it differs from the pin). 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.) diff --git a/docs/dev/STYLE_GUIDE.md b/docs/dev/STYLE_GUIDE.md index 68b9ce73..9b5626c3 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.workspace = true # 1.99; the seven libretro-path crates declare "1.96" +rust-version.workspace = true # 1.99, inherited by every crate ``` ### Formatting From 61e9a42f7f9f8fb1510ead1431cfa1c27d56cd4e Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 07:30:10 -0400 Subject: [PATCH 42/44] fix: review round 1 on #590-#593 -- three gates that could pass wrongly Bot findings from the v3.0.1 release PR and its CodeRabbit slices. Each code fix below was pinned by a test that failed before it; the mutation results are listed per item. - list_unresolved_threads.py (Copilot #590, #592). This is the bot-closeout merge gate, and three inputs could still reach its "0 unresolved thread(s)" all-clear: - a TRUNCATED list: the query asks for `first:100` and nothing checked `pageInfo`. The payload must now carry `reviewThreads.pageInfo.hasNextPage`, the script refuses if it is missing, and refuses if it is true. The README query selects it. - an open thread with an EMPTY comment list, which was skipped (only a partial query can produce one); - an open thread with no `id`, which crashed with a KeyError instead of a named error. Selftest +4 cases. They were red before (the first two, and these two printed FAIL). Mutation: disabling the more-pages refusal fails "a list with more pages is refused" (caught). - nes_golden_export.rs (Copilot #591). On an IRQ-trace overflow the CSV was already withheld, but an irq.csv and ckpt.bin from a PREVIOUS successful run with the same stem survived beside this run's obs.bin and read as its output. Both are now removed before the refusal (a missing file is fine; any other error is reported). The test pre-creates both stale files. It was red before ("an irq.csv ... was left on disk") and passes now: 8/8 bin tests. - release_anchor_audit.rs (Copilot #592). The release-title check refused `**`, `__`, backticks, `~~` and `](`, but not a PAIRED single delimiter: `(an *important* fix)` and `(an _important_ fix)` render literally in the plain-text GitHub release title. A new `has_emphasis_pair` refuses `*` or `_` that opens emphasis (not after a word character, not before space) and is later closed (not after space, not before a word character). `3*4`, `snake_case`, `2 * 3 * 4` and `major_version` stay allowed. Red before (left: None, right: Some("*")); 16/16 pass now, the real CHANGELOG header included. - Records: - NOTICE's TriCNES entry still said, in two places, that the source is in the tree (Copilot #593). - oracle-tooling-setup section 2a was titled "In-repo" and gave no destination for restoring the harness. It now gives a tested command that extracts straight to ~/reference-oracles (`git archive 416fe7d7^: | tar -x -C `), never into the working tree. - ARCHITECTURE.md's Last Updated date (in the previous commit). - "Artefact" -> "Artifact" in the hardware plan; the docs use "artifact" 302 times to 34. Declined or refuted, with the evidence in the replies: - agy's "ios/project.yml missing from MANIFESTS" (blocking): it is at bump_release.py:77. - agy's misplaced cosim doc comment: it is already on rn_write_observables. - agy's LICENSES count mismatch: 338 committed .nes and 33 sub-test ROMs, counted; the old 328 and 26 were stale on main. - agy's as_chunks stability doubt: stable since 1.88, and CI builds it. - The bump_release regex and gsub suggestions: the patterns are strict on purpose and fail closed. Gates: fmt; clippy on rustynes-test-harness and rustynes-cosim; both pr-review selftests; ruff; markdownlint. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- NOTICE | 8 ++-- .../src/bin/nes_golden_export.rs | 24 +++++++++-- .../tests/release_anchor_audit.rs | 41 +++++++++++++++++-- docs/tooling/oracle-tooling-setup.md | 7 +++- scripts/pr-review/README.md | 13 +++--- scripts/pr-review/list_unresolved_threads.py | 22 +++++++++- .../list_unresolved_threads_selftest.py | 26 ++++++++++++ .../plans/v3.x-hardware-verification-plan.md | 2 +- 8 files changed, 124 insertions(+), 19 deletions(-) diff --git a/NOTICE b/NOTICE index a76f6891..3989ca47 100644 --- a/NOTICE +++ b/NOTICE @@ -128,8 +128,9 @@ Incorporated third-party components (permissively licensed, GPL-compatible) 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. + one records what was derived from, the other what was in the tree until the + v3.0.1 removal, and this entry previously gave one id for both after the + vendored copy moved on. Copyright (c) 2025 Chris Siebert -- MIT Note (v2.2.6 -> resolved v2.3.0): v2.2.6 disclosed that the octal-latch / hybrid-address *timing* had been calibrated to TriCNES's per-dot behavior @@ -144,7 +145,8 @@ Incorporated third-party components (permissively licensed, GPL-compatible) current behavior is therefore documentation/oracle-derived -- it matches the NESdev-documented delayed-`CopyV`-during-render timing and is pinned by the MIT AccuracyCoin test ROM plus an exact-141/141 CI gate. TriCNES (MIT) remains - the original cross-reference; its source is vendored with attribution. See + the original cross-reference; its source was vendored with attribution until + v3.0.1 and now lives outside the repository. See docs/originality-and-provenance.md sec. 4 and ADR 0030. * egui-winit 0.36.2 -- winit integration for egui, vendored with a three-site diff --git a/crates/rustynes-cosim/src/bin/nes_golden_export.rs b/crates/rustynes-cosim/src/bin/nes_golden_export.rs index d76fd832..33f0dc87 100644 --- a/crates/rustynes-cosim/src/bin/nes_golden_export.rs +++ b/crates/rustynes-cosim/src/bin/nes_golden_export.rs @@ -438,8 +438,19 @@ fn write_irq_artifacts(o: &mut Oracle, base: &Path, interval: u64) -> (usize, us } // Refuse rather than emitting a short stream: a hash over a trace that // dropped records covers fewer cycles than it claims, and the DUT would - // be blamed for our truncation. - Err(e) => panic!(" ERROR: {e}"), + // be blamed for our truncation. First remove any CSV and checkpoints a + // previous run left under this stem (v3.0.1): they look complete, and + // beside this run's `obs.bin` they would read as its output. + Err(e) => { + for stale in [suffixed(base, "irq.csv"), suffixed(base, "ckpt.bin")] { + match std::fs::remove_file(&stale) { + Ok(()) => eprintln!(" removed stale {}", stale.display()), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} + Err(err) => eprintln!(" WARNING: could not remove {}: {err}", stale.display()), + } + } + panic!(" ERROR: {e}") + } } } @@ -1143,16 +1154,23 @@ mod tests { )); std::fs::create_dir_all(&dir).expect("temp dir"); let base = dir.join("overflow"); + // A previous, SUCCESSFUL run with the same stem (v3.0.1, Copilot on + // #591): its complete-looking CSV and checkpoints must not survive a + // failed rerun, or they read as that run's output. + std::fs::write(suffixed(&base, "irq.csv"), b"stale csv").expect("stale csv"); + std::fs::write(suffixed(&base, "ckpt.bin"), b"stale ckpt").expect("stale ckpt"); 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 ckpt_exists = suffixed(&base, "ckpt.bin").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" + "an irq.csv (truncated, or a previous run's) was left on disk" ); + assert!(!ckpt_exists, "a previous run's ckpt.bin was left on disk"); } /// Pinned against an independently-known digest, not against our own output. diff --git a/crates/rustynes-test-harness/tests/release_anchor_audit.rs b/crates/rustynes-test-harness/tests/release_anchor_audit.rs index e15b7f1e..b5216996 100644 --- a/crates/rustynes-test-harness/tests/release_anchor_audit.rs +++ b/crates/rustynes-test-harness/tests/release_anchor_audit.rs @@ -643,13 +643,42 @@ 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. +/// A lone `*` or `_` stays allowed: both occur in ordinary prose (`3*4`, +/// `snake_case`). A PAIR that opens and closes emphasis (`*word*`, `_word_`) is +/// refused. Strikethrough (`~~`), a link (`](`) and the single-delimiter pair +/// were added in v3.0.1; until then only `**`, `__` and a backtick were. fn title_markdown_marker(theme: &str) -> Option<&'static str> { - ["**", "__", "`", "~~", "]("] + if let Some(marker) = ["**", "__", "`", "~~", "]("] .into_iter() .find(|marker| theme.contains(marker)) + { + return Some(marker); + } + [('*', "*"), ('_', "_")] + .into_iter() + .find(|&(d, _)| has_emphasis_pair(theme, d)) + .map(|(_, marker)| marker) +} + +/// True when `d` opens emphasis (not after a word character, not before space +/// or another `d`) and a later `d` closes it (not after space, not before a +/// word character), which is the shape a Markdown renderer treats as emphasis. +fn has_emphasis_pair(theme: &str, d: char) -> bool { + let chars: Vec = theme.chars().collect(); + let word = |c: char| c.is_alphanumeric() || c == '_'; + (0..chars.len()).any(|i| { + let opens = chars[i] == d + && i.checked_sub(1).is_none_or(|p| !word(chars[p])) + && chars + .get(i + 1) + .is_some_and(|&n| !n.is_whitespace() && n != d); + opens + && (i + 2..chars.len()).any(|j| { + chars[j] == d + && !chars[j - 1].is_whitespace() + && chars.get(j + 1).is_none_or(|&n| !word(n)) + }) + }) } #[test] @@ -657,8 +686,12 @@ 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("](")); + // A PAIRED single delimiter is emphasis too (v3.0.1, Copilot on #592). + assert_eq!(title_markdown_marker("(an *important* fix)"), Some("*")); + assert_eq!(title_markdown_marker("(an _important_ fix)"), Some("_")); // Ordinary prose stays allowed. assert_eq!(title_markdown_marker("(the API major_version, 3*4)"), None); + assert_eq!(title_markdown_marker("(snake_case and 2 * 3 * 4)"), None); } /// Anchors that quote a codename must quote the CHANGELOG's codename. diff --git a/docs/tooling/oracle-tooling-setup.md b/docs/tooling/oracle-tooling-setup.md index 38d686cb..df3cbb6a 100644 --- a/docs/tooling/oracle-tooling-setup.md +++ b/docs/tooling/oracle-tooling-setup.md @@ -72,7 +72,7 @@ stood here. A grep target survives a re-vendor; a line number does not. checklist, answer keys (`$0477` DMC+OAM `04 03 04 03 04 03 02 01…`). - `ref-docs/tricnes-vs-rustynes-accuracy-roadmap-2026-06-02.md` — the roadmap. -### 2a. In-repo buildable instrumented harness (the per-cycle cross-diff oracle) +### 2a. The buildable instrumented harness, outside the repository (the per-cycle cross-diff oracle) The cross-diff oracle used for the DMA-tail / Program-M work is a **trimmed, instrumented TriCNES built from source** (TriCNES is MIT — Chris Siebert 2025). It was vendored in this repo until @@ -82,7 +82,10 @@ v3.0.1 and now lives outside it: (instrumented with the per-cycle window logger; 88 lines against upstream), `Program.cs`, `6502Documentation.cs`, `mappers/` (all 11 `Mapper_*.cs`, **required to build**), `tricnes-harness.csproj`, `LICENSE`. Build with `dotnet build -c Release` (.NET 10 SDK). On a - machine without it, restore the tree from this repository's history before the v3.0.1 removal. + machine without it, extract it from this repository's history straight to that OUTSIDE path, + never into the working tree (guardrails section 3a): `mkdir -p ~/reference-oracles/TriCNES-rustynes-harness + && git archive 416fe7d7^:crates/rustynes-test-harness/golden/tricnes/tricnes-harness-src | tar -x + -C ~/reference-oracles/TriCNES-rustynes-harness`. - `~/reference-oracles/TriCNES/` — a clone of `github.com/100thCoin/TriCNES` at `94f1b117`, for reference and re-trimming. - Cross-diff driver: `scripts/tricnes_xdiff.py` (+ the ad-hoc helpers under `scripts/diag/`, salvaged diff --git a/scripts/pr-review/README.md b/scripts/pr-review/README.md index ab1f2695..bb20f176 100644 --- a/scripts/pr-review/README.md +++ b/scripts/pr-review/README.md @@ -12,10 +12,13 @@ gh api graphql -f query=' query($owner:String!,$repo:String!,$pr:Int!){ repository(owner:$owner,name:$repo){ pullRequest(number:$pr){ - reviewThreads(first:100){ nodes{ - id isResolved isOutdated path line - comments(first:1){ nodes{ databaseId body author{login} } } - }} + reviewThreads(first:100){ + pageInfo{ hasNextPage endCursor } + nodes{ + id isResolved isOutdated path line + comments(first:1){ nodes{ databaseId body author{login} } } + } + } } } }' -F owner=doublegate -F repo=RustyNES -F pr=325 \ @@ -28,7 +31,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. | +| `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)`, and so must a list that does not carry `pageInfo.hasNextPage` or says more pages exist (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 ae256cd1..e27915fc 100644 --- a/scripts/pr-review/list_unresolved_threads.py +++ b/scripts/pr-review/list_unresolved_threads.py @@ -63,6 +63,21 @@ def main() -> None: "GraphQL response has no reviewThreads.nodes list " "(check that the query selects reviewThreads { nodes { ... } })" ) + # A TRUNCATED list must fail closed too (v3.0.1, Copilot on #590): the query + # asks for `first:100`, and a PR with more threads used to print an + # all-clear for page one while later pages held open threads. The payload + # must say whether more pages exist, and this gate refuses if they do. + page = (pr.get("reviewThreads") or {}).get("pageInfo") + if not isinstance(page, dict) or not isinstance(page.get("hasNextPage"), bool): + raise SystemExit( + "GraphQL response has no reviewThreads.pageInfo.hasNextPage " + "(select it, so a truncated thread list cannot read as complete)" + ) + if page["hasNextPage"]: + raise SystemExit( + "more review threads than one page: refusing a partial count " + "(raise first:, or page with after: endCursor and check each page)" + ) shown = 0 for i, thread in enumerate(threads): # A partial node used to die on a bare KeyError/TypeError traceback; @@ -71,11 +86,16 @@ def main() -> None: raise SystemExit(f"review thread #{i} has no boolean isResolved") if thread["isResolved"]: continue + if not thread.get("id"): + raise SystemExit(f"review thread #{i} has no id") 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") + # A real review thread always has a comment, so an empty list means a + # partial query (`comments(first:0)`) -- skipping it would let an open + # thread reach the all-clear (v3.0.1, Copilot on #592). if not comments: - continue + raise SystemExit(f"review thread #{i} ({safe(thread.get('id'))}) has no comments") c = comments[0] if not isinstance(c, dict) or c.get("databaseId") is None: raise SystemExit( diff --git a/scripts/pr-review/list_unresolved_threads_selftest.py b/scripts/pr-review/list_unresolved_threads_selftest.py index ac06591b..3dd34b9f 100644 --- a/scripts/pr-review/list_unresolved_threads_selftest.py +++ b/scripts/pr-review/list_unresolved_threads_selftest.py @@ -40,6 +40,9 @@ def run(doc) -> subprocess.CompletedProcess: def pr(review_threads) -> dict: + """A payload; a thread list gets a complete single page unless it says otherwise.""" + if isinstance(review_threads.get("reviewThreads"), dict): + review_threads["reviewThreads"].setdefault("pageInfo", {"hasNextPage": False}) return {"data": {"repository": {"pullRequest": review_threads}}} @@ -90,7 +93,30 @@ def main() -> None: refused(pr({"reviewThreads": {"nodes": None}}), "no reviewThreads.nodes list"), ) + # Truncation: a list that does not say it is complete is refused. + check( + "a list without pageInfo is refused, not counted", + refused(pr({"reviewThreads": {"nodes": [], "pageInfo": None}}), "no reviewThreads.pageInfo.hasNextPage"), + ) + check( + "a list with more pages is refused, not counted", + refused( + pr({"reviewThreads": {"nodes": [thread("T1", resolved=True)], "pageInfo": {"hasNextPage": True}}}), + "more review threads than one page", + ), + ) + # Partial nodes: a named error, never a bare KeyError traceback. + noid = thread("T1") + del noid["id"] + check( + "an open node without an id is refused by name", + refused(pr({"reviewThreads": {"nodes": [noid]}}), "has no id"), + ) + check( + "an open node with an EMPTY comment list is refused, not skipped", + refused(pr({"reviewThreads": {"nodes": [thread("T1", comments=[])]}}), "has no comments"), + ) check( "a node without isResolved is refused by name", refused(pr({"reviewThreads": {"nodes": [{"id": "T1"}]}}), "has no boolean isResolved"), diff --git a/to-dos/plans/v3.x-hardware-verification-plan.md b/to-dos/plans/v3.x-hardware-verification-plan.md index 4a21752d..8c7754df 100644 --- a/to-dos/plans/v3.x-hardware-verification-plan.md +++ b/to-dos/plans/v3.x-hardware-verification-plan.md @@ -51,7 +51,7 @@ Written for v3.0.0 and restated for D29's position. By the time this release runs, the off-die build has been the headline since v3.3.0 (D4), and both builds carry the v3.2.0-v3.8.0 features. -| Artefact | Status at the hardware release | +| Artifact | Status at the hardware release | | --- | --- | | **Off-die `.rbf`** (the SS1's 128 MB SDRAM) | **The headline since v3.3.0 (D4).** MemTest, its own Strand B rows and HW-O6's real SDRAM constraints passed | | **On-die `.rbf`** (`USE_SDRAM_CART = 1'b0`) | **The "lite" build.** Every bring-up strand A–F that applies to it passed on the SuperStation One | From f1c3c47b0603579ca8cd0db8d89c04659cf3c895 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 08:48:50 -0400 Subject: [PATCH 43/44] fix(ci): run the toolchain-equality check for .gitlab-ci.yml-only PRs Review round 2 on the v3.0.1 slices (CodeRabbit on #592). 1. libretro-cross holds the new check that .gitlab-ci.yml's RUSTUP_TOOLCHAIN equals rust-toolchain.toml's channel. But its path filter (`libretro`) did not list .gitlab-ci.yml, so a PR changing only that file -- exactly the change the check guards -- skipped the job and passed. It now lists .gitlab-ci.yml. Two inputs the libretro core compiles were also missing from the filter: - crates/rustynes-gamedb, one of the seven libretro-path crates; - vendor/, which holds the patched rust-libretro-sys. Both are added; actionlint passes. 2. list_unresolved_threads.py checked only one end of the page range. A payload fetched with `after:` can be the LAST page: it has `hasNextPage: false` while earlier pages hold open threads, and it still printed the all-clear. The gate now requires `hasPreviousPage` and refuses if it is missing or true; the README query selects it. Two new selftest cases were red before and pass now (13 checks in all). Declined on the same review: the request to delete the TriCNES trees and turn the README back to I/O-only. The trees are deleted in the release PR (#590, 416fe7d7), not in this slice, so that half is a cross-slice finding. The consultation terms are the maintainer's decision D7 (2026-10-07), recorded in the guardrails' section 3a. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- .github/workflows/ci.yml | 10 +++++++++- scripts/pr-review/README.md | 4 ++-- scripts/pr-review/list_unresolved_threads.py | 10 ++++++++++ .../list_unresolved_threads_selftest.py | 19 ++++++++++++++++++- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e5ac172..5c0ac339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,9 +220,17 @@ jobs: - '.github/workflows/ci.yml' - '.github/actions/**' - '.github/scripts/**' - # rustynes-libretro -> core, cpu, ppu, apu, mappers + # rustynes-libretro -> core, cpu, ppu, apu, mappers, gamedb, plus the + # vendored rust-libretro-sys. `.gitlab-ci.yml` too (v3.0.1): it holds + # the buildbot's RUSTUP_TOOLCHAIN, which this job's first step checks + # against rust-toolchain.toml -- a PR changing only that file must + # still run the check (CodeRabbit on #592). gamedb and vendor/ were + # missing: both are compiled into the core. libretro: - *shared + - '.gitlab-ci.yml' + - 'vendor/**' + - 'crates/rustynes-gamedb/**' - 'crates/rustynes-libretro/**' - 'crates/rustynes-core/**' - 'crates/rustynes-cpu/**' diff --git a/scripts/pr-review/README.md b/scripts/pr-review/README.md index bb20f176..b22a68ab 100644 --- a/scripts/pr-review/README.md +++ b/scripts/pr-review/README.md @@ -13,7 +13,7 @@ gh api graphql -f query=' repository(owner:$owner,name:$repo){ pullRequest(number:$pr){ reviewThreads(first:100){ - pageInfo{ hasNextPage endCursor } + pageInfo{ hasNextPage hasPreviousPage endCursor } nodes{ id isResolved isOutdated path line comments(first:1){ nodes{ databaseId body author{login} } } @@ -31,7 +31,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)`, and so must a list that does not carry `pageInfo.hasNextPage` or says more pages exist (added v3.0.1). Run it after editing that script. | +| `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)`, and so must a list that does not carry `pageInfo.hasNextPage` and `hasPreviousPage`, or says another page exists before or after it (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 e27915fc..db981f64 100644 --- a/scripts/pr-review/list_unresolved_threads.py +++ b/scripts/pr-review/list_unresolved_threads.py @@ -78,6 +78,16 @@ def main() -> None: "more review threads than one page: refusing a partial count " "(raise first:, or page with after: endCursor and check each page)" ) + # The other end too (CodeRabbit on #592): a page fetched with `after:` can + # be the LAST page, with `hasNextPage: false`, while earlier pages hold + # open threads. Only a payload that starts at the first page is complete. + if not isinstance(page.get("hasPreviousPage"), bool): + raise SystemExit( + "GraphQL response has no reviewThreads.pageInfo.hasPreviousPage " + "(select it, so a later page cannot read as the whole list)" + ) + if page["hasPreviousPage"]: + raise SystemExit("this thread list is not the first page: refusing a partial count") shown = 0 for i, thread in enumerate(threads): # A partial node used to die on a bare KeyError/TypeError traceback; diff --git a/scripts/pr-review/list_unresolved_threads_selftest.py b/scripts/pr-review/list_unresolved_threads_selftest.py index 3dd34b9f..94080c34 100644 --- a/scripts/pr-review/list_unresolved_threads_selftest.py +++ b/scripts/pr-review/list_unresolved_threads_selftest.py @@ -42,7 +42,9 @@ def run(doc) -> subprocess.CompletedProcess: def pr(review_threads) -> dict: """A payload; a thread list gets a complete single page unless it says otherwise.""" if isinstance(review_threads.get("reviewThreads"), dict): - review_threads["reviewThreads"].setdefault("pageInfo", {"hasNextPage": False}) + review_threads["reviewThreads"].setdefault( + "pageInfo", {"hasNextPage": False, "hasPreviousPage": False} + ) return {"data": {"repository": {"pullRequest": review_threads}}} @@ -106,6 +108,21 @@ def main() -> None: ), ) + check( + "a FINAL page with earlier pages is refused, not counted", + refused( + pr({"reviewThreads": {"nodes": [], "pageInfo": {"hasNextPage": False, "hasPreviousPage": True}}}), + "not the first page", + ), + ) + check( + "a page without hasPreviousPage is refused", + refused( + pr({"reviewThreads": {"nodes": [], "pageInfo": {"hasNextPage": False}}}), + "no reviewThreads.pageInfo.hasPreviousPage", + ), + ) + # Partial nodes: a named error, never a bare KeyError traceback. noid = thread("T1") del noid["id"] From 36c15ea3675475138fe59eb35607daeec670ee90 Mon Sep 17 00:00:00 2001 From: DoubleGate Date: Wed, 7 Oct 2026 09:18:17 -0400 Subject: [PATCH 44/44] docs: review round 3 (CodeRabbit on slice C, #593) -- 14 findings, 14 fixed All 14 inline findings held. The review body had no outside-diff, nitpick or duplicate section. Toolchain: - docs/build-and-tooling.md's Channel bullet still told readers the libretro buildbot runs 1.96.0, the floor is checked on every PR, and a pin bump is a one-line edit. Round 1 (eecf8eaf) fixed the MSRV bullet one line above and missed this one. It now says the buildbot uses the same toolchain and that both files move together (CI fails otherwise), and gives the 1.96 exception as history. - docs/agents/libretro.md: the `-C ar` bullet is marked HISTORICAL, pointing to the "UNDID THE SPLIT" bullet for the current state. Measurements: - docs/scheduler.md and docs/performance.md called both frame costs "on the shipped fast dot path". Only nestest (~3.95 ms) is. flowing_palette (~2.65 ms) is rendering-disabled, so its `_fast` variant's guard bails and never enters that path; the bench source says so (full_frame.rs:125-126). It is now labelled as the control it is. Records: - CHANGELOG [3.0.1] Verification links docs/STATUS.md. - OVERVIEW: v2.0.0 is "the first designated breaking release", not "the one" (v3.0.0 is MAJOR too). - VERSION-PLAN's MAJOR rule: the new-deliverable trigger applies when that release is DESIGNATED MAJOR (ADR 0043 allows a minor; D1/D29 place it at the end of v3.9.x). - The v3.0.1 plan separates its reply count from its resolve count: 279 replies = 153 thread replies + 126 PR comments; 77 resolves. External facts, checked before editing: - ADR 0035 and the v3.9.0 plan, Android developer verification. It started 2026-09-30 in four countries for apps distributed through the participating stores, and goes to certified devices worldwide in 2027. ADB and the "advanced flow" still install an unregistered app. The account paths: an individual gives photo ID and proof of address; an organisation gives organisation documents; limited distribution covers up to 20 devices without a government ID. Which path to take stays an open maintainer decision. (developer.android.com developer-verification guide; Help Net Security.) - v3.9.0, iOS signing: iPhone and iPad uploads require the iOS/iPadOS 27 SDK from April 2027, as Apple announced on 2026-09-09; Xcode 27 is the tool that provides it. The plan previously called the date an inference. - v3.4.0, hosting: "well inside the free 1,000 GB" had no forecast behind it. It is replaced by the capacity it implies: about 45,000 relayed player-hours a month at the inferred 22 MB per player-hour, with the allowance shared with Realtime SFU. The gate now measures the first month's traffic. - v3.4.0, privacy: RetroAchievements' privacy-policy requirement and F-Droid's NonFreeNet anti-feature are now separate items. The anti-feature does not itself require a policy. Also: - v4.0.0, T-API-ENUMS now covers rustynes-netplay (NetMessage among its enums), which rustynes-core does not re-export. - The v2.0.x plan: the blank line splitting two blockquotes (MD028) is now `>`. Checks: markdownlint (pre-commit) on the linted files; the pinned 0.49.1 on copies of the new plans, which reports only two MD049 errors older than this change, in the ignored v2.0.x plan's lines 95-96; release_anchor 14/0, release_state_prose 16/0, contribution_checklist 9/0, each run on its own. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014qfTKi2M3swo7qnwvYCkDj --- CHANGELOG.md | 3 ++- OVERVIEW.md | 2 +- VERSION-PLAN.md | 2 +- docs/adr/0035-rustynes-is-permanently-non-commercial.md | 5 +++-- docs/agents/libretro.md | 2 +- docs/build-and-tooling.md | 2 +- docs/performance.md | 5 +++-- docs/scheduler.md | 5 +++-- to-dos/plans/v2.0.x-mobile-finalization-plan.md | 2 +- to-dos/plans/v3.0.1-mortar-plan.md | 2 +- to-dos/plans/v3.4.0-plan.md | 4 ++-- to-dos/plans/v3.9.0-plan.md | 4 ++-- to-dos/plans/v4.0.0-plan.md | 2 +- 13 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba07dda3..3c0046cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,7 +141,8 @@ hardware-verified**. The maintainer's decisions are in - `cargo test --release --workspace --features test-roms --no-fail-fast`: 3,234 passed, 0 failed, 11 ignored on the release tree (v3.0.0: 3,223 / 0 / 11). `cargo test --workspace`: 2,886 / 0 / 7; the cosim crate 54 / 0. - AccuracyCoin 144/144, nestest 0-diff. + AccuracyCoin 144/144, nestest 0-diff. The per-suite counts and the mapper + matrix are in [`docs/STATUS.md`](docs/STATUS.md). - The local commercial suites (`--features test-roms,commercial-roms`): `external_real_games` 60/0, `external_extended` 137/0, `external_coverage` 6/0. The one moved baseline is *Famicom Yarou Vol.1* (T-GA23C-CHRRAM), which diff --git a/OVERVIEW.md b/OVERVIEW.md index 06fa56f0..3bc8b90c 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -22,7 +22,7 @@ RustyNES is the **definitive NES emulator for the modern era** — combining cycle-perfect accuracy with a complete contemporary feature set and the safety guarantees of Rust. It is more than an emulator: it is a platform for NES preservation, competitive online play, tool-assisted speedrunning, and homebrew development. -As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the one deliberate breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. +As of **v1.0.0**, that vision was realized: RustyNES clears the Mesen2 / higan / ares accuracy bar, ships a polished desktop application and a browser build, and supports the full platform surface — netplay, achievements, TAS movies, a debugger, FDS, and arcade (Vs. / PlayChoice-10) hardware. Since then the additive v1.x line added three more platforms (native Android, iOS / iPadOS, and a Libretro / RetroArch core), **v2.0.0 "Timebase"** replaced the scheduler substrate with the one-clock / every-cycle-bus-access model (ADR 0029 — the first designated breaking release), and the v2.1.x → v2.3.x lines deepened accuracy, presentation, and analysis tooling. The current release is **v3.0.1 "Mortar"** — a maintenance release: one game's graphics fixed, the MiSTer core's last MMC3 rule exception tested, Rust 1.99 everywhere, every unanswered bot review answered, and the plan to v4.0.0. Built on **v3.0.0 "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. Built on **v2.9.9 "Ballast"** — the release candidate for v3.0.0: the audits re-run, MMC3 and MMC5 by their documentation, audio exact across save states, and the MiSTer core moved onto it. Built on **v2.9.8 "Vanguard"** — the preparation release for v3.0.0: v3.0.0's breaking changes landed early (a save identity that ignores the header, old states and movies refused, movies and netplay that record the machine, the API removals), every staged game was booted and the defects found were fixed, and the game database's corrections reach every platform. Built on **v2.9.7 "Tandem"** — the desktop's features on the web and on phones, the release binaries built with every native feature, and a PPU A12 fix found by real games: Acclaim's MC-ACC games, the J.Y. ASIC and mapper 91 now count at their documented rates. Built on **v2.9.6 "Roster"** — seventeen mapper families written from their NESdev pages (174 → 191), GTROM promoted to Curated with a modelled flash chip whose saves persist, mapper 4's NES 2.0 submappers corrected (MMC6, NEC, MC-ACC, T9552), and the local commercial suites re-baselined after drifting unread since about v2.0.0. Built on **v2.9.5 "Caliper"** — every open accuracy item measured, then fixed or closed: four fixes red first (the `apu_test` frame-counter coincidence, the composite 2C02 scanline-0 sprite glitch, OAM DMA filling the PPU I/O latch, KS7032 at `$6000`), 49 unreferenced test ROMs gated, the MMC3 M2-edge filter lever tried and refuted, and a save-state epoch (`PPU_SNAPSHOT_VERSION` 11). Built on **v2.9.4 "Plumb"** — the records made true, and CI made to run what it only linted: v3.0.0 decided as the API major with a release-candidate core (ADR 0043), CI now running 63 feature-gated tests it never ran, the eight fuzz targets and a 70% line-coverage floor, the mapper tiers, store status and deferred-features catalogue corrected against the code, and the OAM-decay model recorded as derived from Mesen2. Built on **v2.9.3 "Handset"** — the old review threads closed and the mobile run prepared: every dependency moved to its newest release (egui 0.36 with wgpu 30, rcheevos 12.5.0), all 244 review threads left unanswered on PRs #7-#97 answered and the ten findings that still held fixed (Action 53 multicarts rebuilt to the NESdev spec, and a ROM header editor that no longer rewrites bytes you did not edit or saves mappers from 16 up as the wrong mapper), and the Android unit tests and the iOS renderer added to CI. Built on **v2.9.2 "Candidate"** — the full audit acted on, and the release-candidate pair: all 32 findings of a fifth audit have a verdict and 16 are fixed, save states keep the cartridge RAM of twelve board families they used to drop, the MiSTer core no longer loses an NMI raised inside a DMA, and both bitstreams are cut for the SuperStation One session. Built on **v2.9.1 "Hone"** — what the optimisation bars measure, and what clears them: the A/B tool had been timing the old code on both sides of every code comparison and is fixed, a two-screen Vs. cabinet saves about 9x faster, the off-die MiSTer build keeps CHR in its own SDRAM bank, and both bitstreams are pinned at fitter seed 2 and rebuild byte-identically. Built on **v2.9.0 "Survey"** — every audit re-checked, and the SuperStation One surveyed: a Power Cycle no longer erases your save, the off-die MiSTer build boots without the menu core, and 39 new audit findings are fixed or dispositioned. Built on **v2.8.4 "Tether"** — the MiSTer core's SDRAM build, made trustworthy: its controller now reads data on the edge the memory presents it (every off-die read would have been wrong on hardware, and only the new SDRAM timing constraints could see it), the power-up sequence and CAS-latency-3 reads follow the datasheet, the arbiter can no longer return the wrong byte or lose a write, the off-die bitstream builds from a script, both builds are swept and pinned at fitter seed 5, and the co-simulation ladder runs all 165 gates from a clean checkout. Built on **v2.8.3 "Rivet"** — the MiSTer core's reset, area and comments, measured: every reset is released on the clock that uses it and the timing analysis now checks each release, the CPU is about 4% smaller by two exact rewrites the fit report confirmed, four false comments are corrected, and the co-simulation ladder runs from a fresh checkout (164 of its 165 gates; the last needs a hand-built ROM no generator produces). Built on **v2.8.2 "Solder"** — the MiSTer core's on-die RTL, corrected against the oracle and the wiki: an MMC3 IRQ acknowledge is no longer lost to a same-edge counter clock, SNROM's battery RAM obeys its CHR-line enable, the triangle and noise drop a reload landing on a length clock, a `$2002` read leaves the byte it returned on the data bus, and the emulator's MMC1 no longer ignores a reset written on the cycle after another write. Built on **v2.8.1 "Gasket"** — the libretro core fits the frontends around it: four-player games work through a Four Score option, a controller works again after its port leaves the Zapper, RetroArch no longer reads past the core's input-descriptor list, expansion audio no longer clips, the core declares UNIF images, and the Makefile honours PREFIX, platform=win, DEBUG and CARGO_TARGET_DIR. Built on **v2.8.0 "Bulkhead"** — the libretro core stops a fault at its own boundary: an internal error no longer closes RetroArch, save states survive plugging in a Zapper, closing a game withdraws its memory maps, the core loads from any libretro frontend, and the save state now carries the 2A03 internal data bus. Built on **v2.7.6 "Recount"** — the v2.7.5 deletions measured one at a time: the six performance proposals v2.7.5 bounded only together were each measured alone, where the benchmarks reach them: five are zero, and the sixth, the pulse sweep-mute check, bounds at about 0.2%, and the cheap byte-identical way to take it measured slower; the fast render path now asserts a rendering-history invariant it used to re-write; and the libretro buildbot builds macOS again and gains 32-bit Windows, 32-bit Linux and webOS targets. Built on **v2.7.5 "Tally"** — every audit claim closed with a measurement or a reason: the core audit's twelve performance proposals were closed, eleven of them by measurement, and one adopted (the audio buffer keeps its capacity between frames); 18 dead bus methods and the unused ApuBus trait are deprecated; and the core and frontend ledgers have no open row. Built on **v2.7.4 "Pocket"** — the mobile apps survive what a phone does to them: an internal error no longer closes the Android or iOS app, battery saves persist on both, the apps pause and give up audio when they should, and saves are written so a dying phone keeps the last good one. Built on **v2.7.3 "Hearth"** — the desktop and web frontends keep what they are given: battery saves persist on the desktop, a Lua script can no longer hang or exhaust the emulator, script HTTP cannot reach local services by default, and audio survives a device change. Built on **v2.7.2 "Bankroll"** — cartridge memory, every bank a cartridge has and nothing it has not: MMC1 reaches SUROM / SXROM, MMC5 banks its PRG-RAM, Namco 163 selects nametables, and `$6000-$7FFF` reads open bus where a board has nothing there. Built on **v2.7.1 "Keepsake"** — six cartridge boards now hand RetroArch their battery save instead of an empty one, every user file the frontend writes is written atomically, and three mapper files are now recorded as derived from Mesen2 and puNES. Built on **v2.7.0 "Palisade"** — a corrupt or hand-edited save state now fails at restore with a typed error instead of crashing the emulator one tick later, pulse 1 no longer mutes on the `$4001 = $08` sweep idiom, and the save-state fuzz target can finally reach what it exists to find. Built on **v2.6.23 "Pulse"** — the access does not increment, it pulses the load already there: the CHR-during-rendering gate closes at 61,440 of 61,440 pixels, and **no hardware has run any bitstream** — built on **v2.6.22 "Rigging"**, the instruments for the board, built before the board: AccuracyCoin reads back from hardware as bytes rather than as a photograph, the catalog turns out to carry 149 rows and 144 results so the headline no longer depends on when the run was sampled (at the same 144/144), and a `rom_sha256` recorded in every golden manifest and compared to nothing is now rung 0 — built on **v2.6.21 "Steward"**, which brought battery saves, the CHR-during-rendering gate RED at 32,861 of 61,440 pixels, and the deploy loop the runbook prescribed and **v2.6.20 "Telltale"**, itself on **v2.6.19 "Accession"** and **v2.6.18 "Errata"**, built on **v2.6.17 "Terminus"**. The never-tagged v2.4.0 "Concordance" shipped inside **v2.4.1 "Fabric"** — this sentence had attached that fact to whichever release was current, carried forward by three mechanical version bumps, and said it of v2.4.2, v2.4.3 and v2.4.4 in turn. > RustyNES's emulation core descends from an extensively-documented accuracy program. Where this and related docs reference deep "v1.x"/"v2.x" engine narrative, read it as upstream engine lineage (engineering history), not as RustyNES release versions. Two distinct "v2.0"s exist and must not be conflated: the engine-lineage v2.0 master-clock work shipped as RustyNES **v1.0.0**, while RustyNES's own **v2.0.0 "Timebase"** (2026-07-03) is the later release that *replaced* that same scheduler. The current release is **v3.0.1**. diff --git a/VERSION-PLAN.md b/VERSION-PLAN.md index 43b70541..4cff5b92 100644 --- a/VERSION-PLAN.md +++ b/VERSION-PLAN.md @@ -19,7 +19,7 @@ v1.0.0 is the **production cut that integrates the cycle-accurate emulation engi MAJOR.MINOR.PATCH[-PRERELEASE] ``` -- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md): the first hardware-verified FPGA core). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below). +- **MAJOR** — an incompatible break of the public Rust API (`rustynes-core` and the chip-crate types it re-exports), or the first release of a new deliverable class when that release is designated MAJOR ([ADR 0041](docs/adr/0041-hardware-release-is-v3.0.0.md) named the first hardware-verified FPGA core; [ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) lets it be a minor instead, and its 2026-10-07 amendments place it at the end of v3.9.x). Now at `3`: **v3.0.0 "Cornerstone"** was MAJOR by the first trigger and shipped an unverified release-candidate core; the hardware-verified core is a later v3.x release, numbered after the board session and no later than v4.0.0 ([ADR 0043](docs/adr/0043-v3-is-the-api-major-and-a-release-candidate-core.md) and its 2026-10-07 amendment). **A format break alone is not a MAJOR trigger since 2026-10-07** (maintainer decision; see "Breaking-change policy" below). - **MINOR** — features (new mappers, new frontend features, new platforms). May carry a documented format break. - **PATCH** — bug fixes and accuracy refinements. May carry a documented format break when the fix needs one (v3.0.1 raised `EMULATION_EPOCH`). - **PRERELEASE** — `-alpha.N` / `-beta.N` / `-rc.N` when stabilizing a future minor/major. diff --git a/docs/adr/0035-rustynes-is-permanently-non-commercial.md b/docs/adr/0035-rustynes-is-permanently-non-commercial.md index 3c8262ed..21a094d6 100644 --- a/docs/adr/0035-rustynes-is-permanently-non-commercial.md +++ b/docs/adr/0035-rustynes-is-permanently-non-commercial.md @@ -80,8 +80,9 @@ v4.0.0's final development, test and release activities.** That covers: -- Android developer verification, which also applies to sideloaded apps and is - global from 2027; +- Android developer verification, which covers the ordinary installation path + (from 2026-09-30 in four countries, worldwide on certified devices in 2027); + ADB and Android's "advanced flow" can still install an unregistered app; - iOS signing, so that TestFlight uploads run; - the Google Play, F-Droid or IzzyOnDroid, and App Store listings. diff --git a/docs/agents/libretro.md b/docs/agents/libretro.md index 36f81077..cd6a9dea 100644 --- a/docs/agents/libretro.md +++ b/docs/agents/libretro.md @@ -19,7 +19,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). +- **(HISTORICAL, until 2026-09-03; the current state is the "UNDID THE SPLIT" bullet below.) The libretro build image injected `-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; 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. - **AND v3.0.1 UNDID THE SPLIT, the same day (2026-10-07).** The research for the v3.1 roadmap found that `libretro-infrastructure/libretro-build-rust` removed the `-C` usage from the build image on 2026-09-03 (`841f3619`, "Remove -C usage as no longer supported by rust"; the rebuilt image's own pipeline passed 2026-09-23). A throwaway branch, `test/libretro-rust-1.99`, set only `RUSTUP_TOOLCHAIN: "1.99.0"`; its pipeline 119614 passed all 15 jobs, `osx-x64`, `osx-arm64`, `ios-arm64` and `tvos-arm64` included. So the pin now equals `rust-toolchain.toml` again, the seven crates inherit the workspace `rust-version`, and `libretro-cross` FAILS if the two toolchains differ, so neither can drift alone. Two lessons: the hazard lived in someone else's image, so re-check it there before planning around it; and since the image now separates its `-C link-arg` flags properly, those link arguments reach the linker for the first time. That is harmless on 119614, but it is the first thing to suspect if an Apple link changes behaviour. - **`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`. diff --git a/docs/build-and-tooling.md b/docs/build-and-tooling.md index 1256ac55..ebb2bffd 100644 --- a/docs/build-and-tooling.md +++ b/docs/build-and-tooling.md @@ -6,7 +6,7 @@ - **Rust edition**: 2024. - **MSRV (minimum supported Rust version)**: 1.99, the pinned toolchain, for every crate. The libretro buildbot builds on the same toolchain (`RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml`, which CI's `libretro-cross` checks against `rust-toolchain.toml`); v3.0.1 held its seven crates at 1.96 for one day, until a test pipeline showed the build image no longer passed `-C ar`. (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. +- **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. **The libretro buildbot uses the same toolchain**: `RUSTUP_TOOLCHAIN` in `.gitlab-ci.yml` (which outranks the file) must equal the pin, and CI's `libretro-cross` job fails if the two differ, so a pin move edits both files in one change. There is no `toolchain:` version literal anywhere in `.github/`. (Until v3.0.1 the buildbot was a 1.96.0 exception, because its build image passed `-C ar`, a hard error from Rust 1.97; the image dropped the flag on 2026-09-03, and `rust-toolchain.toml` keeps the two-line fix should it return.) - **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). diff --git a/docs/performance.md b/docs/performance.md index 95913f23..acaa9ce9 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -16,8 +16,9 @@ Set quantitative performance targets, identify expected hot paths, and lay out t > **These are DESIGN-PHASE targets, written before the cycle-accurate core > existed — they are aspirations, not gates.** The frame-cost row in particular > was never met and is knowingly accepted: the implemented core measures -> **~3.95 ms** (`nes_run_frame_nestest_fast`) / **~2.65 ms** -> (`nes_run_frame_flowing_palette_fast`) on the shipped fast dot path, and +> **~3.95 ms** (`nes_run_frame_nestest_fast`) on the shipped fast dot path and +> **~2.65 ms** (`nes_run_frame_flowing_palette_fast`, a rendering-disabled +> control: the fast path's guard bails, so it never enters that path), and > ~4.46 / ~2.67 ms on the exact path, on a 2020 desktop (i9-10850K; see > "Current figures" below, measured 2026-09-23). The gate that > actually runs in CI is the **relative, same-runner regression check** (§CI diff --git a/docs/scheduler.md b/docs/scheduler.md index f3d520c9..8945f39e 100644 --- a/docs/scheduler.md +++ b/docs/scheduler.md @@ -157,8 +157,9 @@ 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.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, +> core measures **~3.95 ms** (nestest) on the shipped fast dot path, ~23% of the +> 16.639 ms NTSC budget, and **~2.65 ms** on flowing palette, a rendering-disabled +> control whose fast-path variant never enters that path (its guard bails) (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 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 12718489..f1864d6c 100644 --- a/to-dos/plans/v2.0.x-mobile-finalization-plan.md +++ b/to-dos/plans/v2.0.x-mobile-finalization-plan.md @@ -8,7 +8,7 @@ > 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 diff --git a/to-dos/plans/v3.0.1-mortar-plan.md b/to-dos/plans/v3.0.1-mortar-plan.md index 2da19abf..16d1366e 100644 --- a/to-dos/plans/v3.0.1-mortar-plan.md +++ b/to-dos/plans/v3.0.1-mortar-plan.md @@ -31,7 +31,7 @@ The first patch on v3.0.0 "Cornerstone"; the codename is "Mortar" (maintainer, | 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 first held on 1.96 via `RUSTUP_TOOLCHAIN`, then moved to 1.99 once test pipeline 119614 passed 15/15 (D5), every crate and action, Android, web, Docker, pre-commit); sibling `e77050d` (runner pinned, Verilator 5.020 → 5.032 under review) | -| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies, 77 resolves, 126 PR comments); afterwards 0 unresolved and 0 unanswered threads in both repositories | +| 5 | done | 290 ledger rows → 473 verdicts (140 fixed, 133 declined, 80 still valid, 55 refuted, 36 obsolete, 28 no findings, 1 open); the 80 fixed by four worktree agents and cherry-picked; 279 replies posted (153 thread replies and 126 PR comments), plus 77 threads resolved; afterwards 0 unresolved and 0 unanswered threads in both repositories | ## What the sweep is NOT diff --git a/to-dos/plans/v3.4.0-plan.md b/to-dos/plans/v3.4.0-plan.md index 492170c2..fd98d5fc 100644 --- a/to-dos/plans/v3.4.0-plan.md +++ b/to-dos/plans/v3.4.0-plan.md @@ -10,9 +10,9 @@ A slot in [`v3.1-to-v4.0-line-plan.md`](v3.1-to-v4.0-line-plan.md), drafted on | # | Item | Gate | | --- | --- | --- | -| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), well inside the free 1,000 GB a month | +| 1 | NET-04 hosted signalling (`T-HOSTED-NETPLAY`): a Cloudflare Worker with Durable Objects for rooms, and Cloudflare TURN with short-lived credentials the Worker mints. The room protocol is ported from the existing `signaling_server` | A live two-browser session through the hosted lobby; a relayed session across two NATs; credentials expire. NES rollback traffic is about 22 MB per player-hour (an inference), so the free 1,000 GB a month (shared with Cloudflare Realtime SFU) covers roughly 45,000 relayed player-hours; only sessions that fall back to TURN use it. No usage forecast exists, so the gate records the first month's measured traffic against that figure | | 2 | RA-01, the browser RetroAchievements proxy (`T-RA-PROXY`), on the same account; `RA_PROXY_BASE` set | A live browser login and unlock with a real account (RA-02) | -| 3 | A privacy policy, which both RA's compliance page and F-Droid's `NonFreeNet` anti-feature need | Published, and linked from the README and the apps | +| 3 | A privacy policy, which RetroAchievements' compliance requirements call for. Separately, F-Droid's `NonFreeNet` anti-feature (it marks dependence on a non-free network service and does not itself require a policy) is declared on the F-Droid build if the RA integration meets that definition | The policy published, and linked from the README and the apps; the F-Droid anti-feature decision recorded with its reason | | 4 | RA hardcore compliance (`T-RA-HARDCORE`): Lua and TAS playback blocked in hardcore (as well as cheats, rewind, slowdown, frame advance, state loading, memory editors and the debugger); offline unlocks queued; the User-Agent format checked against RA's `Name/v1.0.0 (OS) core/v0.5.0`; `docs/ra-integration-request.md` refreshed (it still says v1.8.8 and MIT/Apache) | An audit of `session_policy.rs` against the full block list, each block pinned by a test; then the application sent (the process is unverified: the forum post returned 403) | | 5 | NET-01, native 3-4 player netplay over the existing `mesh_net.rs` (`T-NETPLAY-MESH-NATIVE`); MOB-09 for mobile follows | A 4-peer loopback session stays in sync over N frames; the desktop UI reaches it | diff --git a/to-dos/plans/v3.9.0-plan.md b/to-dos/plans/v3.9.0-plan.md index 33222986..f041ebff 100644 --- a/to-dos/plans/v3.9.0-plan.md +++ b/to-dos/plans/v3.9.0-plan.md @@ -35,8 +35,8 @@ and without monetisation, ADR 0035). | # | Item | Gate | | --- | --- | --- | -| 5 | **Android identity.** Developer verification applies to sideloads too: enforced in four countries from 2026-09-30, and global from 2027. The limited-distribution account covers 20 devices; wider distribution needs registration with a government ID. Which path is the maintainer's decision, taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" | -| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them; rebuild with the then-required Xcode (27 from about April 2027, an inference) | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process | +| 5 | **Android identity.** Developer verification began on 2026-09-30 in Brazil, Indonesia, Singapore and Thailand, for apps distributed through the participating stores (Google Play, Samsung Galaxy Store, Xiaomi GetApps and others), and extends to certified devices worldwide in 2027. An unregistered app can still be installed with ADB or Android's new "advanced flow". The account paths differ: an individual developer gives a government photo ID and proof of address, an organisation gives organisation documents, and a limited-distribution account covers up to 20 devices without a government ID. Which path is the maintainer's decision, still open and taken at this release | The chosen path recorded in `docs/android.md`; a signed APK installs on a certified device without the "advanced flow" | +| 6 | **iOS signing (MOB-02).** No signing secrets were visible at drafting, so TestFlight uploads may never have run. Provision them. From April 2027, App Store Connect accepts iPhone and iPad uploads only if built with the iOS / iPadOS 27 SDK or later (Apple, announced 2026-09-09); Xcode 27, released 2026-09-14, is the tool that provides it | A TestFlight build uploaded by CI; the 90-day expiry noted in the release process | ## The hardware release (the last v3.9.x): scope and gates diff --git a/to-dos/plans/v4.0.0-plan.md b/to-dos/plans/v4.0.0-plan.md index 463204ac..1738c128 100644 --- a/to-dos/plans/v4.0.0-plan.md +++ b/to-dos/plans/v4.0.0-plan.md @@ -29,7 +29,7 @@ Format breaks no longer need one. | # | Item | Gate | | --- | --- | --- | -| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums | +| 1 | `T-API-ENUMS`: every remaining public enum in `rustynes-core` and its re-exports, and in `rustynes-netplay` (which `rustynes-core` does not re-export; `NetMessage` among them), `#[non_exhaustive]`, from v3.9.x's trial branch | Every crate, feature combination and wasm build compiles; rustdoc `-D warnings`; the no_std build; a CHANGELOG migration note listing the enums | | 2 | Release notes restating every break since v3.0.0: the format bumps of v3.1.0, v3.3.0 and v3.5.0, every epoch rise, the enums | Built from the CHANGELOG's entries and every `!` commit and `BREAKING CHANGE:` footer since v3.0.0, so none is missed | | 3 | MiSTer parity: save states, cheats, PAL/Dendy, FDS with expansion audio, the Zapper, Four Score, and mapper families covering the incumbent's licensed-library list | Each feature gated in the ladder; the parity table in `submission-case.md` with the re-measured incumbent number | | 4 | Bitstreams: both builds (off-die the headline since v3.3.0, D4). If the RTL is unchanged since the hardware release, ship the pair the board verified, byte for byte; a recompile at a new date is a different bitstream from the one tested. If the RTL changed, sweep at the release date and say the result is unverified on the board | Two clean compiles of each byte-identical (or the verified pair's md5s); md5s verified by download from both releases |