# 02: Sprite blitter into a PMODE 4 framebuffer. # # Exercises: record types, nested loops, index-carrying iteration # (`enumerate`), the bounds-elision design principle, bitwise ops, # explicit integer widening. # PMODE 4: 256x192, 1bpp, 32 bytes per row, 6144 bytes at $0E00. var framebuffer: [Uint8; 6144] at 0x0E00 const ROW_STRIDE: Uint16 = 32 # VDG/SAM setup for PMODE 4 with the display at $0E00. Each SAM bit is # a pair of addresses: writing anything to the odd one sets the bit, # the even one clears it. var vdg_mode: Uint8 at 0xFF22 var sam_v0_clear: Uint8 at 0xFFC0 var sam_v1_set: Uint8 at 0xFFC3 var sam_v2_set: Uint8 at 0xFFC5 var sam_f0_set: Uint8 at 0xFFC7 var sam_f1_set: Uint8 at 0xFFC9 var sam_f2_set: Uint8 at 0xFFCB var sam_f3_clear: Uint8 at 0xFFCC var sam_f4_clear: Uint8 at 0xFFCE var sam_f5_clear: Uint8 at 0xFFD0 var sam_f6_clear: Uint8 at 0xFFD2 fn set_pmode4() { # VDG: graphics mode, GM = 111 (256x192 1bpp), green/black. vdg_mode = 0xF0 # SAM video mode V = 110. sam_v0_clear = 0 sam_v1_set = 0 sam_v2_set = 0 # SAM display offset = $0E00 / $200 = 7: F0..F2 set, F3..F6 clear. sam_f0_set = 0 sam_f1_set = 0 sam_f2_set = 0 sam_f3_clear = 0 sam_f4_clear = 0 sam_f5_clear = 0 sam_f6_clear = 0 } # Records use `type` with a struct body. All fields have fixed # footprints, so Sprite's size is known at compile time. type Sprite = { x_byte: Uint8, # horizontal position in bytes (0..31) y: Uint8, # vertical position in scanlines rows: [Uint8; 16], # 8 pixels wide, 16 tall, one byte per row } fn blit(sprite: Sprite) { # `enumerate` yields (index, value) pairs. The index is proven # in-bounds for `rows` by construction, and the compiler can see # `ix < 16`, which feeds the framebuffer bounds proof below. for (ix, row_bits) in sprite.rows.enumerate() { # No implicit widening: Uint8 -> Uint16 must be spelled out. # On a 6809 this is a real codegen decision, so it's explicit. let y = (sprite.y as Uint16) + ix let offset = y * ROW_STRIDE + (sprite.x_byte as Uint16) # `offset` can still exceed 6143 if y >= 192, so this check # survives -- but it's hoisted: one check per loop iteration, # not one per byte touched. framebuffer[offset] = framebuffer[offset] | row_bits } } fn clear() { # Mutable iteration over an array: `var` binding in the loop # head gives a by-reference element you can assign through. for var byte in framebuffer { byte = 0x00 } } fn main() { clear() set_pmode4() let player = Sprite { x_byte: 12, y: 90, rows: [ 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, ], } blit(player) loop {} }