A life bar, a room outline or a tiny drawing tool needs a few reliable drawing calls. These routines let you change a bit, read it back, or paint a box without borrowing a game’s renderer.

Include it and make a first call
Define BP at 48 private RAM bytes and include 6809.inc. The PMODE 4 bitmap is fixed at $0600 in this variant; establish the matching SAM/VDG configuration first. The caller shows the PIA direction/data setup as well as the SAM address bits. Supply a valid S stack.
Initialise and clear the display once. This example then sets pixel (0,0); the context’s unused end fields need no initialisation for a point call. The complete reset caller also contains a mailbox used by the verifier. Your game calls the routine directly.
Set one pixel using the reusable component
; Example: pixel (0,0), native big-endian coordinates.
clra
clrb
std BP
std BP+2
sta BP+8
jsr bp_pixelThe screen origin is the top left. X increases rightwards and Y downwards. This mode is 256×192; the last pixel is (255,191).
The calling contract
Reserve 48 context bytes at BP. The word fields are big-endian on this CPU.
| Offset | What to supply or read |
|---|---|
| 0, 2 | x0, y0: unsigned 16-bit coordinates |
| 4, 6 | x1, y1: unsigned 16-bit exclusive ends |
| 8 | Operation: 0 set, 1 clear, 2 XOR |
| 9 | Output: 1 accepted, 0 rejected |
| 10 | Output: the bit read, as 0 or 1 |
| 12–47 | Private scratch; word at 40 counts destination-byte writes |
bp_fill paints [x0,x1)×[y0,y1); bp_box outlines it. bp_hspan uses x0/x1/y0, bp_vspan uses x0/y0/y1, and bp_pixel/bp_read use x0/y0. Set operation to zero for reads. Public inputs survive. Unused end fields are ignored.
A box from (5,20) to (13,24) is eight pixels wide and four high. A 1×1 outline touches its pixel once, including with XOR. Empty, reversed, invalid-operation or out-of-bounds requests reject before any destination write. The routine does not silently clip.
From a coordinate to a byte
PMODE 4 has 32 bytes per scanline. At (13,20), $0600 + 20*32 + 1 = $0881; mask $04 selects the pixel. Five shifts build y×32 without a row table. Unlike the Spectrum and BBC, the next row really is another 32 bytes. The caller owns $0600–$1DFF and the SAM/VDG/PIA configuration. Set/clear are bit values; the selected colour set determines their visible colours.
Integer division and remainder tell us two different things: 13 div 8 = 1 chooses a byte column, while 13 mod 8 = 5 chooses a bit within it. The leftmost pixel uses bit 7, so 128 >> 5 = 4. Set ORs that mask into the old byte; clear ANDs its inverse; XOR toggles it. Writing $04 straight over the byte would erase seven neighbours.
Place a point explores position and origin. Its world Y points upwards; these screen coordinates point downwards. For integer points in a bottom-left-origin world of this height, convert with screenY = 191 - worldY. The width calculation needs no sign reversal: 13-5=8, height 24-20=4, area 8*4=32 pixels.
The maintained implementation
Open the complete reusable 6809 include
Include this backend once in your program
; SPDX-License-Identifier: MIT
; Dragon PMODE 4 at $0600. BP reserves 48 bytes RAM, native big-endian words:
; +0 x0,+2 y0,+4 x1,+6 y1 (exclusive ends); +8 op 0=set/1=clear/2=XOR.
; +9 accepted,+10 read,+40 word write count. +12..47 scratch, inputs survive.
; D/X/Y/CC destroyed; stack <=6 bytes incl caller. Not re-entrant; no IRQ mask changes.
bp_fill
clra
bra bp_enter
bp_box
lda #1
bra bp_enter
bp_pixel
lda #2
bra bp_enter
bp_read
lda #3
bra bp_enter
bp_hspan
lda #4
bra bp_enter
bp_vspan
lda #5
bp_enter
sta BP+12
clr BP+9
clr BP+10
clr BP+40
clr BP+41
ldx #BP
ldy #BP+16
ldb #8
bp_copy
lda ,x+
sta ,y+
decb
bne bp_copy
lda BP+12
cmpa #2
blo bp_validate
cmpa #5
beq bp_unit_x
ldd BP+18
addd #1
std BP+22
lda BP+12
cmpa #4
beq bp_validate
bp_unit_x
ldd BP+16
addd #1
std BP+20
bp_validate
lda BP+8
cmpa #3
bhs bp_invalid
ldd BP+20
cmpd #256
bhi bp_invalid
ldd BP+16
cmpd BP+20
bhs bp_invalid
ldd BP+22
cmpd #192
bhi bp_invalid
ldd BP+18
cmpd BP+22
bhs bp_invalid
bra bp_valid
bp_invalid
rts
bp_valid
lda #1
sta BP+9
ldb BP+17
andb #7
ldx #bp_right_masks
lda b,x
sta BP+24
ldx #bp_bit_masks
lda b,x
sta BP+26
ldb BP+21
andb #7
ldx #bp_left_masks
lda b,x
sta BP+25
decb
andb #7
ldx #bp_bit_masks
lda b,x
sta BP+27
ldb BP+17
lsrb
lsrb
lsrb
stb BP+28
ldd BP+20
subd #1
lsrb
lsrb
lsrb
stb BP+29
ldb BP+19
stb BP+32
ldb BP+23
decb
stb BP+42
bp_row
clr BP+39
; A 32-byte scanline: y*32 + x/8 + $0600. No row table.
clra
ldb BP+32
lslb
rola
lslb
rola
lslb
rola
lslb
rola
lslb
rola
addd #$0600
addb BP+28
adca #0
std BP+34
lda BP+28
sta BP+30
lda BP+24
sta BP+36
lda BP+25
sta BP+37
lda BP+12
cmpa #1
bne bp_bytes
lda BP+32
cmpa BP+19
beq bp_bytes
cmpa BP+42
beq bp_bytes
lda #1
sta BP+39
lda BP+26
sta BP+36
lda BP+27
sta BP+37
bp_bytes
ldb #255
lda BP+30
cmpa BP+28
bne bp_not_first
ldb BP+36
bp_not_first
lda BP+30
cmpa BP+29
bne bp_not_last
andb BP+37
tst BP+39
beq bp_not_last
cmpa BP+28
bne bp_not_last
ldb BP+36
orb BP+37
bp_not_last
tst BP+39
beq bp_apply
lda BP+30
cmpa BP+28
beq bp_apply
cmpa BP+29
bne bp_advance
bp_apply
ldx BP+34
lda BP+12
cmpa #3
bne bp_write
andb ,x
beq bp_return
lda #1
sta BP+10
bp_return
rts
bp_write
lda BP+8
beq bp_set
cmpa #1
beq bp_clear
eorb ,x
bra bp_store
bp_clear
comb
andb ,x
bra bp_store
bp_set
orb ,x
bp_store
stb ,x
ldd BP+40
addd #1
std BP+40
bp_advance
lda BP+30
cmpa BP+29
beq bp_next_row
inc BP+30
ldx BP+34
leax 1,x
stx BP+34
jmp bp_bytes
bp_next_row
inc BP+32
lda BP+32
cmpa BP+23
beq bp_return
jmp bp_row
bp_right_masks
fcb 255,127,63,31,15,7,3,1
bp_left_masks
fcb 255,128,192,224,240,248,252,254
bp_bit_masks
fcb 128,64,32,16,8,4,2,1Choose a path for the work
The 32×8 aligned fill in the native check makes 32 byte writes; shifting it one pixel makes 40. The eight-pixel-wide example above makes eight writes for its 32 pixels. Calling a plot routine for each pixel would repeat addressing and edge work. This backend works a byte at a time, preserving the first and last byte’s unrelated bits.
A full-display clear makes 6,144 writes. The kernel, including its masks, takes 470 assembled bytes; BP needs 48 RAM bytes. Assets/display memory and the reset caller are additional. These are measured storage and write counts, not a frame-budget or cross-CPU speed ranking. The write counter itself also costs work.
An unchecked inner loop, cached row address or specialised full-byte store can be smaller/faster for a particular caller. This version still reads a byte before a full-byte set/clear; an outline skips stores to middle columns but still visits them. Clipped lines, circles and ellipses add a reusable geometry layer over this unchanged backend.
Own the resources
D/X/Y and CC are clobbered; U and DP survive. The context is not re-entrant. Reserve at least eight stack bytes for the routines, plus the rest of your program and interrupts. Keep context, code and any address tables out of the bitmap; do not let another renderer or interrupt handler change them during a call. The routines do not change interrupt state. Colour, palette and video setup remain caller-owned.
What was verified
All 94 calls completed independently on this Dragon 32 configuration using Asm198x 0.0.71 and Emu198x 0.39.0. Every call checks the complete bitmap, guards or attribute neighbours, status, read value, unchanged public inputs and byte-write count. Cases cover all bit phases, set/clear/XOR/read, spans, one-pixel and full-size boxes, empty/reversed extents and invalid bounds. Selected whole native frames match a separate composition. A completed wrong-mask version fails both memory and picture comparisons.
This is an original reset fixture. It establishes these calls in this mode; stock-OS loading, physical-machine timing, interrupt coexistence and tear-free animation need their own checks.
- Complete caller and execution evidence
- Reusable include
- Full contracts, measured space/work costs and primary references
- Compare the three bitmap layouts
- Draw a tile-map region
Draw bitmap text reuses these spans for glyphs at arbitrary pixel positions, with an explicit font, advance and scissor.