// PPUCTRL // // VPHB SINN pub const Control = @This(); raw: u8 = 0, pub fn baseNametable(self: Control) u2 { return @truncate(self.raw & 0x03); } pub fn baseNametableAddress(self: Control) u16 { return 0x2000 + (@as(u16, self.baseNametable()) * 0x400); } pub fn vramIncrement(self: Control) u8 { return if ((self.raw & 0x04) != 0) 32 else 1; } pub fn sprite8x8PatternAddress(self: Control) u16 { return if ((self.raw & 0x08) != 0) 0x1000 else 0x0000; } /// Computes pattern table address for a sprite tile (supports both 8x8 and 8x16 modes). pub fn spritePatternAddressForTile(self: Control, tile: u8, sprite_y_offset: u4) u16 { if (self.spriteHeight() == 8) { return self.sprite8x8PatternAddress() | (@as(u16, tile) << 4) | (sprite_y_offset & 7); } else { const bank: u16 = @as(u16, tile & 1) << 12; var tile_index: u16 = tile & 0xfe; if (sprite_y_offset >= 8) { tile_index += 1; } return bank | (tile_index << 4) | (sprite_y_offset & 7); } } pub fn backgroundPatternAddress(self: Control) u16 { return if ((self.raw & 0x10) != 0) 0x1000 else 0x0000; } pub fn spriteHeight(self: Control) u8 { return if ((self.raw & 0x20) != 0) 16 else 8; } pub fn masterSlave(self: Control) bool { return (self.raw & 0x40) != 0; } pub fn nmiEnabled(self: Control) bool { return (self.raw & 0x80) != 0; } const std = @import("std"); test "8x8 sprite pattern address" { const c0: Control = .{ .raw = 0x00 }; // 8x8, pattern table 0 try std.testing.expectEqual(@as(u16, 0x0000), c0.sprite8x8PatternAddress()); try std.testing.expectEqual(@as(u16, 0x0143), c0.spritePatternAddressForTile(0x14, 3)); const c1: Control = .{ .raw = 0x08 }; // 8x8, pattern table 1 ($1000) try std.testing.expectEqual(@as(u16, 0x1000), c1.sprite8x8PatternAddress()); try std.testing.expectEqual(@as(u16, 0x1143), c1.spritePatternAddressForTile(0x14, 3)); } test "8x16 sprite pattern address ignores bit 3 and uses tile bit 0" { const c: Control = .{ .raw = 0x28 }; // 8x16, bit 3 set (ignored!) try std.testing.expectEqual(@as(u8, 16), c.spriteHeight()); // Tile 0x20: bit 0 is 0 -> bank 0 ($0000). Top half (offset 0..7). try std.testing.expectEqual(@as(u16, 0x0203), c.spritePatternAddressForTile(0x20, 3)); // Tile 0x20: Bottom half (offset 8..15) -> tile index becomes 0x21! try std.testing.expectEqual(@as(u16, 0x0212), c.spritePatternAddressForTile(0x20, 10)); // Tile 0x25: bit 0 is 1 -> bank 1 ($1000). Tile index & 0xFE = 0x24. Top half. try std.testing.expectEqual(@as(u16, 0x1245), c.spritePatternAddressForTile(0x25, 5)); // Tile 0x25: Bottom half -> tile index & 0xFE + 1 = 0x25. try std.testing.expectEqual(@as(u16, 0x1254), c.spritePatternAddressForTile(0x25, 12)); }