Four pixels share each MODE 1 byte, but their colour bits are not adjacent. This backend handles that packing so your game can set, clear, XOR or read a pixel, then reuse the same work for spans and boxes.

Include it and make a first call
Copy cpc.inc and define BP at 48 private RAM bytes. The complete geometry caller places it at $4300. Initialise a 320×200 MODE 1 screen at $C000 using the caller’s Gate Array/CRTC setup, clear it, and choose your palette. The include does not perform video setup or change the palette.
To set pixel (1,20) to pen 3, store little-endian words 1 at BP+0 and 20 at BP+2, set BP+8 to 0 and BP+11 to 3, then call bp_pixel. BP+9 returns 1 when accepted. Set BP+8 to 0 and call bp_read at the same coordinates; BP+10 returns pen index 3. The native checks exercise those entry points through the complete caller. Its mailbox is verification transport; a game calls them directly.
The actual checked pixel entry, not a browser substitute
bp_pixel:
ld a,2
jr bp_enterThe calling contract
Words are unsigned 16-bit, little-endian. Origin is top left; Y increases downwards.
| BP offset | Supply or read |
|---|---|
| 0,2 | x0,y0 |
| 4,6 | x1,y1, exclusive ends for spans/rectangles |
| 8 | 0 replace with pen, 1 clear to pen 0, 2 XOR pen bits |
| 9 | Output: accepted 1, rejected 0 |
| 10 | Output: read pen 0–3, zero for other calls/rejection |
| 11 | Input pen 0–3 |
| 12–47 | Private scratch; word at 40 counts destination-byte writes |
bp_pixel/bp_read use x0,y0; set operation 0 for a read. bp_hspan uses x0,x1,y0 and bp_vspan uses x0,y0,y1. bp_fill and bp_box use all four coordinates. Unused ends are ignored. Inputs 0–8 and 11 survive. Invalid pen, operation or used coordinates reject before writes. Empty/reversed spans and boxes reject; nothing is silently clipped. A 1×1 outlined box writes once under XOR. Geometry supplies clipping above this backend.
Replace really means replace: setting pen 2 over pen 1 must clear one pen bit and set the other. OR alone would produce pen 3. Clear ignores the selected pen and writes pen 0; XOR with pen 0 changes no bits. Read returns the stored pen index, not an RGB colour.
Find the byte, then the two bits
address = $C000 + (y & 7)*2048 + (y >> 3)*80 + (x >> 2)
At (13,20), scanline phase is 4, character row is 2 and byte column is 3: the byte is $E0A3. 13 mod 4 = 1 selects pixel phase 1. Its low pen bit uses bit 6 ($40), its high pen bit bit 2 ($04). Pen 2 inserts $04 into mask $44, keeping the byte’s other six bits.
| Pixel phase | Pen-bit mask | Pen 1 bits | Pen 2 bits | Pen 3 bits |
|---|---|---|---|---|
| 0 | $88 | $80 | $08 | $88 |
| 1 | $44 | $40 | $04 | $44 |
| 2 | $22 | $20 | $02 | $22 |
| 3 | $11 | $10 | $01 | $11 |
A span [1,5) covers four pixels but touches two bytes. The first selected mask is $77; the second is $88. Each edge must preserve its neighbours. Interior bytes can be handled together instead of recalculating each pixel address. The backend still uses checked read/modify/write work; a specialised full-byte store path can avoid reads for set/clear at the cost of another path.
Each 2 KiB scanline bank contains 2,000 visible bytes and 48 unused bytes. The backend leaves those 384 unused bytes unchanged. It supports this standard CRTC layout only; another start address, stride or MODE needs its own configuration and checks. Native map regions demonstrates another use of this physical display layout.
Costs and ownership
The backend is 578 assembled bytes, plus BP’s 48 RAM bytes. That excludes setup, stack, display and assets. A full clear writes 16,000 visible bytes. A four-pixel span can cost two byte writes when unaligned and one when aligned. These are destination-write counts, not cycles or a frame-budget guarantee.
AF/BC/DE/HL and flags are clobbered. Reserve at least ten stack bytes including the direct caller, plus other calls and interrupts. BP is non-re-entrant. The routines neither change interrupt state nor coordinate another video user. Own the CRTC, Gate Array, palette and bitmap; a firmware-managed display needs a deliberate coexistence/loading path. The original reset fixture takes over the machine.
Open the complete reusable MODE 1 backend
Include once; all six drawing/read entries share BP
; SPDX-License-Identifier: MIT
; CPC 464 MODE 1, 320x200, display base $C000. Caller owns Gate Array,
; CRTC, palette, ROM/RAM mapping and interrupt policy. BP reserves 48 RAM bytes.
; +0/+2 x0/y0, +4/+6 exclusive x1/y1 (little-endian); +8 op, +11 pen 0..3.
; Op 0 replaces selected pixels with pen; 1 clears to pen 0; 2 XORs pen bits.
; +9 accepted, +10 read pen, +40 word write count; inputs +0..8/+11 survive.
; Same bp_pixel/read/hspan/vspan/box/fill entries as the monochrome backends.
; Whole invalid requests reject before writes. AF/BC/DE/HL/flags clobbered.
; Non-re-entrant; stack <=10 bytes including direct caller, plus interrupts.
bp_fill:
xor a
jr bp_enter
bp_box:
ld a,1
jr bp_enter
bp_pixel:
ld a,2
jr bp_enter
bp_read:
ld a,3
jr bp_enter
bp_hspan:
ld a,4
jr bp_enter
bp_vspan:
ld a,5
bp_enter:
ld (BP+12),a
xor a
ld (BP+9),a
ld (BP+10),a
ld (BP+40),a
ld (BP+41),a
ld hl,BP
ld de,BP+16
ld bc,8
ldir
ld a,(BP+12)
cp 2
jr c,bp_validate
cp 5
jr z,bp_unit_x
ld hl,(BP+18)
inc hl
ld (BP+22),hl
cp 4
jr z,bp_validate
bp_unit_x:
ld hl,(BP+16)
inc hl
ld (BP+20),hl
bp_validate:
ld a,(BP+11)
cp 4
ret nc
ld a,(BP+8)
cp 3
ret nc
ld hl,(BP+20)
ld de,321
or a
sbc hl,de
ret nc
ld hl,(BP+16)
ld de,(BP+20)
or a
sbc hl,de
ret nc
ld hl,(BP+22)
ld de,201
or a
sbc hl,de
ret nc
ld hl,(BP+18)
ld de,(BP+22)
or a
sbc hl,de
ret nc
ld a,(BP+11)
ld e,a
ld d,0
ld hl,bp_pens
add hl,de
ld a,(hl)
ld (BP+43),a
ld a,1
ld (BP+9),a
ld a,(BP+16)
and 3
ld e,a
ld d,0
ld hl,bp_right_masks
add hl,de
ld a,(hl)
ld (BP+24),a
ld hl,bp_bit_masks
add hl,de
ld a,(hl)
ld (BP+26),a
ld a,(BP+20)
and 3
ld e,a
ld hl,bp_left_masks
add hl,de
ld a,(hl)
ld (BP+25),a
ld hl,(BP+20)
dec hl
ld a,l
and 3
ld e,a
ld hl,bp_bit_masks
add hl,de
ld a,(hl)
ld (BP+27),a
ld hl,(BP+16)
srl h
rr l
srl h
rr l
ld a,l
ld (BP+28),a
ld hl,(BP+20)
dec hl
srl h
rr l
srl h
rr l
ld a,l
ld (BP+29),a
ld a,(BP+18)
ld (BP+32),a
ld a,(BP+22)
dec a
ld (BP+42),a
bp_row:
xor a
ld (BP+39),a
call bp_cpc_address
ld (BP+34),hl
ld a,(BP+28)
ld (BP+30),a
ld a,(BP+24)
ld (BP+36),a
ld a,(BP+25)
ld (BP+37),a
ld a,(BP+12)
cp 1
jr nz,bp_bytes
ld a,(BP+18)
ld b,a
ld a,(BP+32)
cp b
jr z,bp_bytes
ld a,(BP+42)
ld b,a
ld a,(BP+32)
cp b
jr z,bp_bytes
ld a,1
ld (BP+39),a
ld a,(BP+26)
ld (BP+36),a
ld a,(BP+27)
ld (BP+37),a
bp_bytes:
ld c,255
ld a,(BP+28)
ld b,a
ld a,(BP+30)
cp b
jr nz,bp_not_first
ld a,(BP+36)
ld c,a
bp_not_first:
ld a,(BP+29)
ld b,a
ld a,(BP+30)
cp b
jr nz,bp_not_last
ld a,(BP+37)
and c
ld c,a
ld a,(BP+39)
or a
jr z,bp_not_last
ld a,(BP+28)
cp b
jr nz,bp_not_last
ld a,(BP+36)
ld b,a
ld a,(BP+37)
or b
ld c,a
bp_not_last:
ld a,(BP+39)
or a
jr z,bp_apply
ld a,(BP+30)
ld b,a
ld a,(BP+28)
cp b
jr z,bp_apply
ld a,(BP+29)
cp b
jr nz,bp_advance
bp_apply:
ld hl,(BP+34)
ld a,(BP+12)
cp 3
jr nz,bp_write
; Align the selected two-bit pixel with bits 7 and 3.
ld a,(BP+16)
and 3
ld b,a
inc b
ld a,(hl)
jr bp_read_test
bp_read_shift:
add a,a
bp_read_test:
djnz bp_read_shift
ld c,a
and $80
ld a,0
jr z,bp_read_low
inc a
bp_read_low:
ld b,a
ld a,c
and 8
ld a,b
jr z,bp_read_done
or 2
bp_read_done:
ld (BP+10),a
ret
bp_write:
ld a,(BP+8)
or a
jr z,bp_set
cp 1
jr z,bp_clear
ld a,(BP+43)
and c
xor (hl)
jr bp_store
bp_clear:
ld a,c
cpl
and (hl)
jr bp_store
bp_set:
ld a,c
cpl
and (hl)
ld b,a
ld a,(BP+43)
and c
or b
bp_store:
ld (hl),a
ld hl,(BP+40)
inc hl
ld (BP+40),hl
bp_advance:
ld a,(BP+29)
ld b,a
ld a,(BP+30)
cp b
jr z,bp_next_row
inc a
ld (BP+30),a
ld hl,(BP+34)
inc hl
ld (BP+34),hl
jp bp_bytes
bp_next_row:
ld a,(BP+22)
ld b,a
ld a,(BP+32)
inc a
cp b
ret z
ld (BP+32),a
jp bp_row
bp_cpc_address:
; $C000 + (y AND 7)*2048 + (y DIV 8)*80 + byte column.
ld a,(BP+32)
and 7
add a,a
add a,a
add a,a
or $c0
ld h,a
ld l,0
push hl
ld a,(BP+32)
srl a
srl a
srl a
ld l,a
ld h,0
add hl,hl
add hl,hl
add hl,hl
add hl,hl
ld d,h
ld e,l
add hl,hl
add hl,hl
add hl,de
pop de
add hl,de
ld a,(BP+28)
ld e,a
ld d,0
add hl,de
ret
bp_right_masks:
db $ff,$77,$33,$11
bp_left_masks:
db $ff,$88,$cc,$ee
bp_bit_masks:
db $88,$44,$22,$11
bp_pens:
db $00,$f0,$0f,$ffWhat was verified
166 native backend calls cover all pen/phase combinations, replacement/clear/XOR/readback, partial-byte spans, boxes, full-screen bounds and rejected requests. Each checks the entire 16 KiB allocation with patterned neighbours and unused bytes, public inputs, status and write count. Another 216 calls include geometry and the direct startup ellipse. Selected whole native frames match independent composition. A completed wrong partial-byte mask and a wrong ellipse recurrence fail both memory and pixel comparisons.
The tools are released Asm198x 0.0.71 and Emu198x 0.39.0, CPC 464. This verifies the original reset configuration, not stock-firmware loading, physical-machine timing or tear-free animation.
- Complete caller and execution evidence
- Reusable CPC backend
- Full contracts and primary references
- Compare pixels, spans and boxes
- Draw with integer error · Place a point
Draw bitmap text reuses these spans for glyphs at arbitrary pixel positions, with an explicit font, advance and scissor.