Put a score above a moving enemy, slide a room name into view or label a point on a map. This component draws an 8×8 character or a counted string at any pixel position in an owned bitmap plane. It uses the CPU span backend, so shapes and labels share a clear definition of a pixel.

Include it and draw a label
Reserve 96 bytes at T plus 48 at BP, both even-aligned and disjoint from code, stack, source data and the plane. The complete caller uses T=$2200, BP=$2000, the 320×256 plane at $10000 and its Copper at $4000. Define TEXT_WIDTH/HEIGHT and PIXEL_WIDTH/HEIGHT as 320/256, set BITMAP_BASE, and include the text and CPU span components once. Scratch needs no initialisation; public fields do.
After the named display setup, this is a normal game call. It stops before the diagnostic marker/mailbox, which your project does not need.
Draw PIXEL TEXT at (21,37), using real font and string pointers
; A normal game call: a counted label at an arbitrary pixel position.
move.w #21,T
move.w #37,T+2
lea font_data(pc),a1
move.l a1,T+4
lea label_data(pc),a1
move.l a1,T+8
move.b #10,T+12
move.b #65,T+13
move.b #32,T+14
move.b #63,T+15
move.w #64,T+16
move.b #1,T+18
move.b #1,T+19
clr.b T+20
clr.w T+22
clr.w T+24
move.w #320,T+26
move.w #256,T+28
bsr text_stringCall text_string, then read T+21: one means accepted. Change the position and source pointer/length to draw another label. For a single glyph, set T+13 and call text_char; string pointer and length are then ignored. The routine owns no cursor, drawing list or game loop.
A pointer is wider; the contract is explicit
This Amiga context differs from the other text ports. It uses 32-bit source pointers; copying their 16-bit-address offsets would not describe the same fields. Words and longwords are big-endian. Contexts must be even-aligned; byte font/string sources may be odd.
| T offset | Supply or read |
|---|---|
| 0,2 | Signed origin X,Y, each −512..511 |
| 4 | Font pointer longword |
| 8 | String pointer longword |
| 12,13 | String length byte 0..255; single character code byte |
| 14,15 | First code and fallback code bytes |
| 16 | Glyph-count word, 1..256; first+count ≤256 |
| 18 | Mode byte: 0 transparent, 1 opaque |
| 19,20 | Foreground/background bit pens, each 0..1 |
| 21 | Accepted 1, rejected 0 |
| 22,24,26,28 | Scissor left,top,right,bottom words; right/bottom exclusive |
| 30 | Output signed word: next X |
| 32,33 | Output consumed/substitution bytes |
| 34 | Output word: backend span calls |
| 36 | Output longword: destination-byte stores |
| 40–95 | Private scratch |
The origin is the cell’s top-left corner, Y downwards. The scissor must be nonempty and inside the physical plane. Each glyph advances by eight, including blank, substituted and completely clipped glyphs: nextX = startX + 8*consumed. A counted string has no hidden terminator, newline handling, wrapping or case conversion. A zero byte is a code. Empty strings are accepted without source reads or drawing after declaration checks.
Pointers are stored as longwords, but this 68000 contract accepts addresses only in $000000–$FFFFFF, with allocation ends no greater than $1000000. A last byte at $FFFFFF is arithmetically allowed. The source must actually be readable in the selected RAM/ROM mapping: arithmetic validation does not acquire memory or establish ownership. The default font is in the original reset ROM; the tests also use odd-addressed Chip RAM font and string buffers.
Invalid coordinates, mode, either pen, font range/fallback, scissor or pointer arithmetic reject before destination writes, returning the original X and zero counters. Inputs 0–20 and 22–29 survive. Keep the whole font/string allocation stable and separate from code, T/BP, stack and destination throughout the call.
Supply the shape; keep the advance
The optional original Code198x font supplies 64 ASCII glyphs, codes 32..95, under the sample repository’s MIT licence. It is not extracted from ROM. Each glyph has eight high-bit-first row bytes, a five-column/seven-row visible design inside an 8×8 cell. A font row lives at font + (code-first)*8 + row. Unsupported codes use the declared fallback, which must exist in the font.
Open the original 64-glyph font
Optional 512-byte sample font
; SPDX-License-Identifier: MIT
; Original Code198x 8x8 diagnostic font, ASCII32..95.
dc.b 0,0,0,0,0,0,0,0,16,16,16,16,16,0,16,0
dc.b 40,40,40,0,0,0,0,0,40,124,40,40,124,40,0,0
dc.b 16,60,80,56,20,120,16,0,100,104,8,16,32,44,76,0
dc.b 48,72,80,32,84,72,52,0,16,16,32,0,0,0,0,0
dc.b 8,16,32,32,32,16,8,0,32,16,8,8,8,16,32,0
dc.b 0,84,56,124,56,84,0,0,0,16,16,124,16,16,0,0
dc.b 0,0,0,0,0,16,32,0,0,0,0,124,0,0,0,0
dc.b 0,0,0,0,0,0,16,0,4,8,8,16,32,32,64,0
dc.b 56,68,76,84,100,68,56,0,16,48,16,16,16,16,56,0
dc.b 56,68,4,8,16,32,124,0,120,4,4,56,4,4,120,0
dc.b 8,24,40,72,124,8,8,0,124,64,64,120,4,4,120,0
dc.b 56,64,64,120,68,68,56,0,124,4,8,16,32,32,32,0
dc.b 56,68,68,56,68,68,56,0,56,68,68,60,4,4,56,0
dc.b 0,16,0,0,16,0,0,0,0,16,0,0,16,16,32,0
dc.b 4,8,16,32,16,8,4,0,0,0,124,0,124,0,0,0
dc.b 64,32,16,8,16,32,64,0,56,68,4,8,16,0,16,0
dc.b 56,68,92,84,92,64,56,0,56,68,68,124,68,68,68,0
dc.b 120,68,68,120,68,68,120,0,56,68,64,64,64,68,56,0
dc.b 120,68,68,68,68,68,120,0,124,64,64,120,64,64,124,0
dc.b 124,64,64,120,64,64,64,0,56,68,64,92,68,68,60,0
dc.b 68,68,68,124,68,68,68,0,56,16,16,16,16,16,56,0
dc.b 28,8,8,8,72,72,48,0,68,72,80,96,80,72,68,0
dc.b 64,64,64,64,64,64,124,0,68,108,84,84,68,68,68,0
dc.b 68,100,100,84,76,76,68,0,56,68,68,68,68,68,56,0
dc.b 120,68,68,120,64,64,64,0,56,68,68,68,84,72,52,0
dc.b 120,68,68,120,80,72,68,0,60,64,64,56,4,4,120,0
dc.b 124,16,16,16,16,16,16,0,68,68,68,68,68,68,56,0
dc.b 68,68,68,68,68,40,16,0,68,68,68,84,84,108,68,0
dc.b 68,68,40,16,40,68,68,0,68,68,40,16,16,16,16,0
dc.b 124,4,8,16,32,64,124,0,56,32,32,32,32,32,56,0
dc.b 64,32,32,16,8,8,4,0,56,8,8,8,8,8,56,0
dc.b 16,40,68,0,0,0,0,0,0,0,0,0,0,0,124,0Glyph-local (3,5) at origin (21,37) becomes (24,42). The next cell begins at X29, even if the first is a blank space. Move a glyph, keep its shape explains that translation, clipping and the distinction between ink, cell and advance.
Transparent mode sends foreground runs through bp_hspan, leaving zero font bits alone. Opaque first paints each clipped eight-pixel background row, then its foreground runs. Pens are stored bit values in this one plane. The caller chooses the palette and owns any other planes; this is not a hidden multi-plane colour replacement routine.
Make the cost visible
For ten-code PIXEL TEXT, transparent drawing takes 77 / 77 byte stores at X24/X31; opaque takes 157 / 237. Foreground uses 77 spans; opaque adds 80 backgrounds. Gaps and partial edge bytes matter. Opaque drawing revisits foreground bytes after the background, buying a clear composable baseline at a cost.
The text component is 888 assembled bytes, plus 480 for the CPU backend and 96+48 RAM context bytes. The sample font takes 512 bytes; ten digits need only 80, and 256 contiguous glyphs need 2,048. Reset setup, bitmap, string, stack and the 72-byte Copper list are additional. These are storage and store counts, not cycles or a frame-rate promise.
A masked word/longword blit could combine ink and background in fewer passes. Pre-shifted Amiga sprites trade more asset storage for simpler positioning. The CPU/blitter region example provides an aligned two-plane comparison. General blitter text would also need masks, alignment, Chip RAM, DMA ownership and completion handling; it is not supplied by this CPU component.
Own the resources
T/BP are non-re-entrant. Clobbers D0–D7/A1/CCR; SR interrupt-mask bits survive. Reserve a conservative 64-byte stack budget beyond your other calls/IRQs. The routine changes no interrupt enables, waits for no frame, acquires no OS display and synchronises no renderer. Keep other CPU, blitter and IRQ writers from racing its read/modify/write operations. An AmigaOS project needs its own loading, allocation and display ownership arrangement.
Open the reusable 68000 text component
Include this text component once alongside its span backend
; SPDX-License-Identifier: MIT
; 8x8 fixed-width bitmap text over bp_hspan. Native big-endian, 32-bit pointers.
; T:96 even-aligned private bytes; BP:48 separate bytes. No hidden scene/OS/IRQ.
; +0/2 signed origin; +4 font long; +8 string long; +12 length; +13 char;
; +14 first; +15 fallback; +16 glyph count word; +18 mode; +19/20 fg/bg;
; +21 accepted; +22/24/26/28 scissor L/T/R/B words (exclusive ends);
; +30 nextX word; +32 consumed; +33 substitutions; +34 span word; +36 stores long.
; +40..95 scratch. Public 0..20 and 22..29 survive. D0-D7/A1/CCR clobbered.
; Caller supplies readable stable allocations and owns plane, banks and DMA/IRQ access.
text_char:
move.b #1,T+60
moveq #1,d0
bra text_begin
text_string:
clr.b T+60
moveq #0,d0
move.b T+12,d0
text_begin:
move.w d0,T+40
clr.b T+21
move.w T,T+30
clr.b T+32
clr.b T+33
clr.w T+34
clr.l T+36
cmp.w #-512,T
blt text_return
cmp.w #511,T
bgt text_return
cmp.w #-512,T+2
blt text_return
cmp.w #511,T+2
bgt text_return
cmp.b #1,T+18
bhi text_return
cmp.b #1,T+19
bhi text_return
cmp.b #1,T+20
bhi text_return
move.w T+22,d0
cmp.w T+26,d0
bhs text_return
cmp.w #TEXT_WIDTH,T+26
bhi text_return
move.w T+24,d0
cmp.w T+28,d0
bhs text_return
cmp.w #TEXT_HEIGHT,T+28
bhi text_return
moveq #0,d1
move.w T+16,d1
beq text_return
cmp.w #256,d1
bhi text_return
moveq #0,d0
move.b T+14,d0
add.w d0,d1
cmp.w #256,d1
bhi text_return
moveq #0,d2
move.b T+15,d2
cmp.w d0,d2
blo text_return
cmp.w d1,d2
bhs text_return
move.l T+4,d0
cmp.l #$ffffff,d0
bhi text_return
moveq #0,d1
move.w T+16,d1
lsl.l #3,d1
add.l d0,d1
cmp.l #$1000000,d1
bhi text_return
tst.b T+60
bne text_valid
move.l T+8,d0
cmp.l #$ffffff,d0
bhi text_return
moveq #0,d1
move.w T+40,d1
add.l d0,d1
cmp.l #$1000000,d1
bhi text_return
text_valid:
move.b #1,T+21
clr.w T+42
tst.w T+40
beq text_return
text_glyph:
moveq #0,d0
tst.b T+60
beq text_from_string
move.b T+13,d0
bra text_lookup
text_from_string:
move.l T+8,a1
moveq #0,d1
move.w T+42,d1
adda.l d1,a1
move.b (a1),d0
text_lookup:
moveq #0,d1
move.b T+14,d1
cmp.w d1,d0
blo text_fallback
add.w T+16,d1
cmp.w d1,d0
blo text_have_code
text_fallback:
move.b T+15,d0
addq.b #1,T+33
text_have_code:
moveq #0,d1
move.b T+14,d1
sub.w d1,d0
lsl.w #3,d0
move.w d0,T+44
clr.w T+46
text_row:
move.w T+2,d2
add.w T+46,d2
move.w d2,T+52
tst.b T+18
beq text_load_row
move.w T+30,d0
move.w d0,d1
addq.w #8,d1
moveq #0,d3
move.b T+20,d3
bsr text_emit
text_load_row:
move.l T+4,a1
moveq #0,d0
move.w T+44,d0
add.w T+46,d0
adda.l d0,a1
move.b (a1),T+48
clr.b T+49
move.w #-1,T+50
text_column:
moveq #0,d0
move.b T+48,d0
andi.b #$80,d0
beq text_zero
tst.w T+50
bpl text_shift
moveq #0,d0
move.b T+49,d0
move.w d0,T+50
bra text_shift
text_zero:
tst.w T+50
bmi text_shift
move.w T+30,d0
add.w T+50,d0
moveq #0,d1
move.b T+49,d1
add.w T+30,d1
move.w T+52,d2
moveq #0,d3
move.b T+19,d3
bsr text_emit
move.w #-1,T+50
text_shift:
move.b T+48,d0
add.b d0,d0
move.b d0,T+48
addq.b #1,T+49
cmp.b #8,T+49
blo text_column
tst.w T+50
bmi text_next_row
move.w T+30,d0
add.w T+50,d0
move.w T+30,d1
addq.w #8,d1
move.w T+52,d2
moveq #0,d3
move.b T+19,d3
bsr text_emit
text_next_row:
addq.w #1,T+46
cmp.w #8,T+46
blo text_row
addq.w #8,T+30
addq.b #1,T+32
addq.w #1,T+42
subq.w #1,T+40
bne text_glyph
text_return:
rts
text_emit:
cmp.w T+24,d2
blt text_return
cmp.w T+28,d2
bge text_return
cmp.w T+22,d0
bge text_left_inside
move.w T+22,d0
text_left_inside:
cmp.w T+26,d1
ble text_right_inside
move.w T+26,d1
text_right_inside:
cmp.w d1,d0
bge text_return
move.w d0,BP
move.w d1,BP+4
move.w d2,BP+2
clr.b BP+8
tst.b d3
bne text_pen_set
move.b #1,BP+8
text_pen_set:
bsr bp_hspan
addq.w #1,T+34
moveq #0,d0
move.w BP+40,d0
add.l d0,T+36
rtsWhat was verified
81 calls completed using Asm198x 0.0.71 and native Emu198x 0.39.0. Every call compares the full plane, guards, public inputs, source/font preservation, acceptance, advance, consumption/substitution and work counts against an independent logical pixel model. Cases include every phase, pens, clipping edges, negative origins, empty/255-code strings, fallback, 256-code fonts, odd buffers, wide pointers and the 24-bit arithmetic boundary. Selected whole native frames match independently composed images; the Copper list stays unchanged.
Wrong glyph-bit and advance executables each complete accepted calls and fail both memory and picture checks. Exact source/tool bindings and evidence-corruption tests are retained. This qualifies the original A500 PAL reset fixture, not stock Kickstart/AmigaOS loading, original-hardware timing or tear-free display coexistence.