test(nes): consolidate and simplify inline subsystem unit tests

This commit is contained in:
2026-08-27 07:16:26 +02:00
parent f028213c11
commit 57f349fa17
35 changed files with 6856 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
const std = @import("std");
const common = @import("../common.zig");
const Mirroring = common.Mirroring;
// ponytail: Mapper 28 (Action 53)
pub const Action53 = @This();
selected_reg: u2 = 0,
regs: [4]u8 = [_]u8{0} ** 4,
mirroring_bit: u1 = 0,
prg_page_0: usize = 0,
prg_page_1: usize = 0xffff, // resolves to last 16KB bank at init
mirroring_mode: Mirroring = .vertical,
pub fn init(initial_mirroring: Mirroring) Action53 {
return .{
.mirroring_mode = initial_mirroring,
};
}
pub fn cpuRead(self: *const Action53, address: u16, prg_rom: []const u8, prg_ram: []const u8) ?u8 {
if (address >= 0x6000 and address <= 0x7fff) {
const offset = address - 0x6000;
if (offset < prg_ram.len) return prg_ram[offset];
return 0;
}
if (address >= 0x8000 and address <= 0xffff) {
const total_16k = @max(1, prg_rom.len / 0x4000);
const bank = if (address < 0xc000)
self.prg_page_0 % total_16k
else
(if (self.prg_page_1 == 0xffff) total_16k - 1 else self.prg_page_1 % total_16k);
const offset = (bank * 0x4000) + (address & 0x3fff);
if (offset < prg_rom.len) return prg_rom[offset];
return 0;
}
return null;
}
pub fn cpuWrite(self: *Action53, address: u16, value: u8, prg_ram: []u8) bool {
if (address >= 0x5000 and address <= 0x5fff) {
self.selected_reg = @truncate(((value & 0x80) >> 6) | (value & 0x01));
return true;
}
if (address >= 0x6000 and address <= 0x7fff) {
const offset = address - 0x6000;
if (offset < prg_ram.len) {
prg_ram[offset] = value;
return true;
}
return false;
}
if (address >= 0x8000 and address <= 0xffff) {
if (self.selected_reg <= 1) {
self.mirroring_bit = @truncate((value >> 4) & 0x01);
} else if (self.selected_reg == 2) {
self.mirroring_bit = @truncate(value & 0x01);
}
self.regs[self.selected_reg] = value;
self.updateState();
return true;
}
return false;
}
fn updateState(self: *Action53) void {
var mirroring_val: u8 = self.regs[2] & 0x03;
if ((mirroring_val & 0x02) == 0) {
mirroring_val = self.mirroring_bit;
}
self.mirroring_mode = switch (mirroring_val) {
0 => .single_screen_lower,
1 => .single_screen_upper,
2 => .vertical,
3 => .horizontal,
else => unreachable,
};
const game_size: usize = (self.regs[2] & 0x30) >> 4;
const prg_size: usize = (self.regs[2] & 0x08) >> 3;
const slot_select: usize = (self.regs[2] & 0x04) >> 2;
var prg_select: usize = self.regs[1] & 0x0f;
const outer_prg_select: usize = @as(usize, self.regs[3]) << 1;
if (prg_size != 0) {
const outer_mask = [_]usize{ 0x1fe, 0x1fc, 0x1f8, 0x1f0 };
const inner_mask = [_]usize{ 0x01, 0x03, 0x07, 0x0f };
const switchable = (outer_prg_select & outer_mask[game_size]) | (prg_select & inner_mask[game_size]);
const fixed = (outer_prg_select & 0x1fe) | slot_select;
if (slot_select != 0) {
self.prg_page_0 = switchable;
self.prg_page_1 = fixed;
} else {
self.prg_page_0 = fixed;
self.prg_page_1 = switchable;
}
} else {
prg_select <<= 1;
const outer_and = [_]usize{ 0x1fe, 0x1fc, 0x1f8, 0x1f0 };
const inner_and = [_]usize{ 0x01, 0x03, 0x07, 0x0f };
self.prg_page_0 = (outer_prg_select & outer_and[game_size]) | (prg_select & inner_and[game_size]);
self.prg_page_1 = (outer_prg_select & outer_and[game_size]) | ((prg_select | 1) & inner_and[game_size]);
}
}
pub fn ppuRead(self: *const Action53, address: u16, chr_rom: []const u8, chr_ram: []const u8, chr_is_ram: bool) ?u8 {
if (address < 0x2000) {
const chr_select: usize = self.regs[0] & 0x03;
if (chr_is_ram) {
const total_8k = @max(1, chr_ram.len / 0x2000);
const offset = ((chr_select % total_8k) * 0x2000) + address;
if (offset < chr_ram.len) return chr_ram[offset];
} else if (chr_rom.len > 0) {
const total_8k = @max(1, chr_rom.len / 0x2000);
const offset = ((chr_select % total_8k) * 0x2000) + address;
if (offset < chr_rom.len) return chr_rom[offset];
}
return 0;
}
return null;
}
pub fn ppuWrite(self: *Action53, address: u16, value: u8, chr_ram: []u8, chr_is_ram: bool) bool {
if (address < 0x2000 and chr_is_ram) {
const chr_select: usize = self.regs[0] & 0x03;
const total_8k = @max(1, chr_ram.len / 0x2000);
const offset = ((chr_select % total_8k) * 0x2000) + address;
if (offset < chr_ram.len) {
chr_ram[offset] = value;
return true;
}
}
return false;
}
pub fn mirroring(self: *const Action53) Mirroring {
return self.mirroring_mode;
}
pub fn irqLine(_: *const Action53) bool {
return false;
}
pub fn notifyPpuAddress(_: *Action53, _: u16) void {}
test "action53 register write" {
var a = Action53.init(.vertical);
var ram: [0x2000]u8 = undefined;
_ = a.cpuWrite(0x5000, 0x80, &ram); // select supervisor reg 2 (mode)
_ = a.cpuWrite(0x8000, 0x01, &ram); // 1-screen upper
try std.testing.expectEqual(Mirroring.single_screen_upper, a.mirroring());
}