Put a score beside a moving object, label a map or draw a message inside a panel. This routine draws an 8×8 character or a counted string at a pixel position you choose. It uses the existing span backend, so your text and shapes agree about where pixels live.

Include it, then draw a string
Reserve 96 bytes at T plus 48 at BP, separate from code, stack, font, string and bitmap. This complete caller uses 320×256 pixels, T at $0400 and BP at $40 in zero page. Include the 512-byte bitmap_lo/hi scanline tables. The caller owns the 6845, video ULA and $5800–$7FFF bitmap; horizontal bytes are eight addresses apart.
Define TEXT_WIDTH, TEXT_HEIGHT, TEXT_PEN_MAX and TEXT_PACKED as shown in the caller. Include the text CPU component and its existing span backend once. Mono targets use pen maximum 1 and packed 0; CPC uses 3 and 1. BP must be zero page on 6502; T need not be. Scratch needs no initialisation; public inputs do.
After the display setup, this is an ordinary game call. The mailbox which follows is just how the verifier submits more requests.
Draw PIXEL TEXT at (21,37)
; A normal game call: eight-pixel advance, off-grid origin, opaque label.
lda #<(21)
sta T+0
lda #>(21)
sta T+1
lda #<(37)
sta T+2
lda #>(37)
sta T+3
lda #<(font_data)
sta T+4
lda #>(font_data)
sta T+5
lda #<(label_data)
sta T+6
lda #>(label_data)
sta T+7
lda #<(64)
sta T+11
lda #>(64)
sta T+12
lda #<(0)
sta T+18
lda #>(0)
sta T+19
lda #<(0)
sta T+20
lda #>(0)
sta T+21
lda #<(320)
sta T+22
lda #>(320)
sta T+23
lda #<(256)
sta T+24
lda #>(256)
sta T+25
lda #10
sta T+8
lda #65
sta T+9
lda #32
sta T+10
lda #63
sta T+13
lda #1
sta T+14
lda #1
sta T+15
lda #0
sta T+16
jsr text_string
lda #0
sta COMMAND
lda #239
sta COMMAND+1Call text_string, then read T+17. Change X/Y and the string address/length to draw another label. For one character, set T+9 and call text_char; string address and length are then ignored. There is no retained scene, cursor or game loop inside the routine.
The calling contract
Words and the 32-bit count use little-endian order. The origin is the cell’s top-left corner, with Y downwards. Pens are 0–1, selecting stored bitmap bits; the display palette/attributes determine their colours. Both pen inputs must be valid, even when transparent mode does not draw the background.
| T offset | Supply or read |
|---|---|
| 0,2 | Signed 16-bit X,Y origin, each −512..511 |
| 4 | Font address word |
| 6,8 | String address word and byte length 0..255 |
| 9 | Single code byte for text_char |
| 10,11 | First code byte and glyph-count word, 1..256 |
| 13 | Fallback code, which must exist in the font |
| 14 | 0 transparent, 1 opaque |
| 15,16 | Foreground and background pen bytes |
| 17 | Output: 1 accepted, 0 rejected |
| 18,20,22,24 | Scissor left,top,right,bottom words; right/bottom exclusive |
| 26 | Output signed word: next X |
| 28,29 | Output bytes: codes consumed, substitutions |
| 30 | Output word: backend span calls |
| 32 | Output 32-bit destination-byte writes |
| 36–95 | Private scratch |
A string is counted, not zero-terminated. A zero byte is a code; there is no newline, wrapping or implicit case conversion. An empty string is accepted without source reads or drawing, after declaration checks. A glyph always advances by eight: nextX = inputX + 8*consumed, including blank, substituted and fully clipped glyphs. That output is signed 16-bit, not a byte that wraps at 255.
The scissor must be nonempty and inside this physical bitmap. The routine intersects glyph runs with it before calling the backend. A completely clipped valid string still succeeds. Bad coordinates, mode, pens, font range, fallback, scissor or a source/font end beyond $10000 reject before destination writes, with the original X returned and zero counters. An allocation ending exactly at $10000 is allowed by the arithmetic check.
Inputs 0–16 and 18–25 survive. Source ranges must actually be readable in your machine’s current mapping: arithmetic checks cannot establish memory ownership or negotiate a bank switch. Keep the font/string stable and disjoint from code, contexts, stack and destination for the whole call.
Supply a font you own
Each glyph is eight bytes, top row first, bit 7 leftmost. The address of a row is font + (code-first)*8 + row. The optional original Code198x font supplies 64 glyphs for ASCII 32..95, occupying 512 bytes under the sample repository’s MIT licence. It was drawn for these examples, not copied from a machine ROM. Its visible design is five columns by seven rows inside each 8×8 cell.
You can supply a ten-digit font in just 80 bytes, or a contiguous 256-code font in 2,048. First code plus glyph count must not exceed 256; fallback must belong to that range. Unsupported codes use the fallback and increase the substitution count, even when clipped. Lowercase needs its own glyphs or explicit conversion in your caller.
Open the sample font data
Original 64-glyph 8×8 font
; SPDX-License-Identifier: MIT
; Original Code198x diagnostic glyphs, ASCII 32..95. Eight MSB-left rows each.
!byte $00,$00,$00,$00,$00,$00,$00,$00 ; code 32
!byte $10,$10,$10,$10,$10,$00,$10,$00 ; code 33
!byte $28,$28,$28,$00,$00,$00,$00,$00 ; code 34
!byte $28,$7c,$28,$28,$7c,$28,$00,$00 ; code 35
!byte $10,$3c,$50,$38,$14,$78,$10,$00 ; code 36
!byte $64,$68,$08,$10,$20,$2c,$4c,$00 ; code 37
!byte $30,$48,$50,$20,$54,$48,$34,$00 ; code 38
!byte $10,$10,$20,$00,$00,$00,$00,$00 ; code 39
!byte $08,$10,$20,$20,$20,$10,$08,$00 ; code 40
!byte $20,$10,$08,$08,$08,$10,$20,$00 ; code 41
!byte $00,$54,$38,$7c,$38,$54,$00,$00 ; code 42
!byte $00,$10,$10,$7c,$10,$10,$00,$00 ; code 43
!byte $00,$00,$00,$00,$00,$10,$20,$00 ; code 44
!byte $00,$00,$00,$7c,$00,$00,$00,$00 ; code 45
!byte $00,$00,$00,$00,$00,$00,$10,$00 ; code 46
!byte $04,$08,$08,$10,$20,$20,$40,$00 ; code 47
!byte $38,$44,$4c,$54,$64,$44,$38,$00 ; code 48
!byte $10,$30,$10,$10,$10,$10,$38,$00 ; code 49
!byte $38,$44,$04,$08,$10,$20,$7c,$00 ; code 50
!byte $78,$04,$04,$38,$04,$04,$78,$00 ; code 51
!byte $08,$18,$28,$48,$7c,$08,$08,$00 ; code 52
!byte $7c,$40,$40,$78,$04,$04,$78,$00 ; code 53
!byte $38,$40,$40,$78,$44,$44,$38,$00 ; code 54
!byte $7c,$04,$08,$10,$20,$20,$20,$00 ; code 55
!byte $38,$44,$44,$38,$44,$44,$38,$00 ; code 56
!byte $38,$44,$44,$3c,$04,$04,$38,$00 ; code 57
!byte $00,$10,$00,$00,$10,$00,$00,$00 ; code 58
!byte $00,$10,$00,$00,$10,$10,$20,$00 ; code 59
!byte $04,$08,$10,$20,$10,$08,$04,$00 ; code 60
!byte $00,$00,$7c,$00,$7c,$00,$00,$00 ; code 61
!byte $40,$20,$10,$08,$10,$20,$40,$00 ; code 62
!byte $38,$44,$04,$08,$10,$00,$10,$00 ; code 63
!byte $38,$44,$5c,$54,$5c,$40,$38,$00 ; code 64
!byte $38,$44,$44,$7c,$44,$44,$44,$00 ; code 65
!byte $78,$44,$44,$78,$44,$44,$78,$00 ; code 66
!byte $38,$44,$40,$40,$40,$44,$38,$00 ; code 67
!byte $78,$44,$44,$44,$44,$44,$78,$00 ; code 68
!byte $7c,$40,$40,$78,$40,$40,$7c,$00 ; code 69
!byte $7c,$40,$40,$78,$40,$40,$40,$00 ; code 70
!byte $38,$44,$40,$5c,$44,$44,$3c,$00 ; code 71
!byte $44,$44,$44,$7c,$44,$44,$44,$00 ; code 72
!byte $38,$10,$10,$10,$10,$10,$38,$00 ; code 73
!byte $1c,$08,$08,$08,$48,$48,$30,$00 ; code 74
!byte $44,$48,$50,$60,$50,$48,$44,$00 ; code 75
!byte $40,$40,$40,$40,$40,$40,$7c,$00 ; code 76
!byte $44,$6c,$54,$54,$44,$44,$44,$00 ; code 77
!byte $44,$64,$64,$54,$4c,$4c,$44,$00 ; code 78
!byte $38,$44,$44,$44,$44,$44,$38,$00 ; code 79
!byte $78,$44,$44,$78,$40,$40,$40,$00 ; code 80
!byte $38,$44,$44,$44,$54,$48,$34,$00 ; code 81
!byte $78,$44,$44,$78,$50,$48,$44,$00 ; code 82
!byte $3c,$40,$40,$38,$04,$04,$78,$00 ; code 83
!byte $7c,$10,$10,$10,$10,$10,$10,$00 ; code 84
!byte $44,$44,$44,$44,$44,$44,$38,$00 ; code 85
!byte $44,$44,$44,$44,$44,$28,$10,$00 ; code 86
!byte $44,$44,$44,$54,$54,$6c,$44,$00 ; code 87
!byte $44,$44,$28,$10,$28,$44,$44,$00 ; code 88
!byte $44,$44,$28,$10,$10,$10,$10,$00 ; code 89
!byte $7c,$04,$08,$10,$20,$40,$7c,$00 ; code 90
!byte $38,$20,$20,$20,$20,$20,$38,$00 ; code 91
!byte $40,$20,$20,$10,$08,$08,$04,$00 ; code 92
!byte $38,$08,$08,$08,$08,$08,$38,$00 ; code 93
!byte $10,$28,$44,$00,$00,$00,$00,$00 ; code 94
!byte $00,$00,$00,$00,$00,$00,$7c,$00 ; code 95Translation and byte boundaries
Glyph-local (3,5) at origin (21,37) lands at (24,42). The next cell starts at X=29. A blank space moves the next cell too: ink, cell bounds and advance are different things. Move a glyph, keep its shape works through that translation, clipping and division/remainder without requiring an algebra course.
Eight pixels at an arbitrary X can touch two mono bytes, or three four-pixel CPC bytes. The component finds foreground runs in each glyph row and sends them to bp_hspan. Transparent mode leaves zero font bits alone. Opaque mode paints the cell’s background row first, then its foreground runs; it replaces the visible clipped cell, including the blank rows/columns.
For the ten-code PIXEL TEXT, transparent drawing at X=24 and X=31 makes 77 / 77 destination-byte writes; opaque makes 157 / 237. Foreground uses 77 spans; opaque adds 80 background spans. Gaps and edge masks affect those counts, so an unaligned cell is not automatically more expensive in every workload.
This baseline favours understandable composition. Opaque drawing touches foreground bytes again after clearing their background. A masked KEEP/image blitter could combine those operations; pre-shifted glyph phases trade more font storage for less shifting/address work. Compare masked sprites and pre-shifted sprites. Those are design choices to measure, not invisible upgrades to this routine.
The text component assembles to 2,456 bytes, plus 526 for the backend, the chosen font/string and the contexts. BBC adds 512 bytes of scanline tables. Reset setup, display and stack are additional. These are storage and byte-write measurements, not cycle counts or a frame-budget promise.
Own the display and scratch
Clobbers: A/X/Y/P; decimal mode is cleared. Reserve a conservative 64-byte stack budget beyond your other calls/interrupts. T and BP are non-re-entrant. No call changes interrupt state, waits for a frame or synchronises video. On 6502, text temporarily uses BP+46/47 for indirect source reads, with no live source pointer carried over a span call. The caller owns banks, display access and interruption/coexistence policy.
Open the reusable 6502 text component
Include the text component once alongside its backend
; SPDX-License-Identifier: MIT
; Original 8x8 fixed-width text using bp_hspan; no display/font/OS setup.
; T: 96 private bytes; BP: separate 48 bytes (zero page on 6502).
; All registers/flags used by the backend may change. Non-re-entrant.
; See README.md for trusted source allocations, pens, bounds and outputs.
t_x = T+0
t_y = T+2
t_font = T+4
t_source = T+6
t_length = T+8
t_code = T+9
t_first = T+10
t_count = T+11
t_fallback = T+13
t_mode = T+14
t_fg = T+15
t_bg = T+16
t_status = T+17
t_clip_l = T+18
t_clip_t = T+20
t_clip_r = T+22
t_clip_b = T+24
t_next_x = T+26
t_consumed = T+28
t_missing = T+29
t_calls = T+30
t_writes = T+32
t_entry = T+36
t_limit = T+37
t_i = T+38
t_index = T+40
t_end = T+42
t_pointer = T+44
t_row = T+46
t_column = T+47
t_bits = T+48
t_run = T+49
t_start = T+50
t_left = T+52
t_right = T+54
t_py = T+56
t_pen = T+58
t_temp = T+60
t_work = T+64
t_cmp = T+95
bp_x0 = BP+0
bp_y0 = BP+2
bp_x1 = BP+4
bp_op = BP+8
bp_pen = BP+11
bp_writes = BP+40
g_cmp = T+95
text_char:
lda #$00
sta t_entry
jmp text_enter
text_string:
lda #$01
sta t_entry
jmp text_enter
text_enter:
lda #$00
sta t_status
lda #$00
sta t_writes
lda #$00
sta t_writes+1
lda #$00
sta t_writes+2
lda #$00
sta t_writes+3
lda #$00
sta t_calls
lda #$00
sta t_calls+1
lda #$00
sta t_consumed
lda #$00
sta t_missing
lda t_x
sta t_next_x
lda t_x+1
sta t_next_x+1
; Validate the complete declaration before drawing; no source read yet.
lda #$fe
eor #$80
sta g_cmp
lda t_x+1
eor #$80
cmp g_cmp
bcs text_branch_2
jmp text_done
text_branch_2:
beq text_branch_3
jmp text_branch_1
text_branch_3:
lda #$00
sta g_cmp
lda t_x
cmp g_cmp
bcs text_branch_4
jmp text_done
text_branch_4:
text_branch_1:
lda #$01
eor #$80
sta g_cmp
lda t_x+1
eor #$80
cmp g_cmp
bcs text_branch_6
jmp text_branch_5
text_branch_6:
beq text_branch_7
jmp text_done
text_branch_7:
lda #$ff
sta g_cmp
lda t_x
cmp g_cmp
bcs text_branch_8
jmp text_branch_5
text_branch_8:
beq text_branch_9
jmp text_done
text_branch_9:
text_branch_5:
lda #$fe
eor #$80
sta g_cmp
lda t_y+1
eor #$80
cmp g_cmp
bcs text_branch_11
jmp text_done
text_branch_11:
beq text_branch_12
jmp text_branch_10
text_branch_12:
lda #$00
sta g_cmp
lda t_y
cmp g_cmp
bcs text_branch_13
jmp text_done
text_branch_13:
text_branch_10:
lda #$01
eor #$80
sta g_cmp
lda t_y+1
eor #$80
cmp g_cmp
bcs text_branch_15
jmp text_branch_14
text_branch_15:
beq text_branch_16
jmp text_done
text_branch_16:
lda #$ff
sta g_cmp
lda t_y
cmp g_cmp
bcs text_branch_17
jmp text_branch_14
text_branch_17:
beq text_branch_18
jmp text_done
text_branch_18:
text_branch_14:
lda #$01
sta g_cmp
lda t_mode
cmp g_cmp
bcs text_branch_20
jmp text_branch_19
text_branch_20:
beq text_branch_21
jmp text_done
text_branch_21:
text_branch_19:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_fg
sta t_temp
lda #<TEXT_PEN_MAX
sta t_end
lda #>TEXT_PEN_MAX
sta t_end+1
lda t_end+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_23
jmp text_branch_22
text_branch_23:
beq text_branch_24
jmp text_done
text_branch_24:
lda t_end
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_25
jmp text_branch_22
text_branch_25:
beq text_branch_26
jmp text_done
text_branch_26:
text_branch_22:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_bg
sta t_temp
lda #<TEXT_PEN_MAX
sta t_end
lda #>TEXT_PEN_MAX
sta t_end+1
lda t_end+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_28
jmp text_branch_27
text_branch_28:
beq text_branch_29
jmp text_done
text_branch_29:
lda t_end
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_30
jmp text_branch_27
text_branch_30:
beq text_branch_31
jmp text_done
text_branch_31:
text_branch_27:
lda #$00
eor #$80
sta g_cmp
lda t_clip_l+1
eor #$80
cmp g_cmp
bcs text_branch_33
jmp text_done
text_branch_33:
beq text_branch_34
jmp text_branch_32
text_branch_34:
lda #$00
sta g_cmp
lda t_clip_l
cmp g_cmp
bcs text_branch_35
jmp text_done
text_branch_35:
text_branch_32:
lda #$00
eor #$80
sta g_cmp
lda t_clip_t+1
eor #$80
cmp g_cmp
bcs text_branch_37
jmp text_done
text_branch_37:
beq text_branch_38
jmp text_branch_36
text_branch_38:
lda #$00
sta g_cmp
lda t_clip_t
cmp g_cmp
bcs text_branch_39
jmp text_done
text_branch_39:
text_branch_36:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_clip_l+1
eor #$80
cmp g_cmp
bcs text_branch_41
jmp text_branch_40
text_branch_41:
beq text_branch_42
jmp text_done
text_branch_42:
lda t_clip_r
sta g_cmp
lda t_clip_l
cmp g_cmp
bcs text_branch_43
jmp text_branch_40
text_branch_43:
jmp text_done
text_branch_40:
lda t_clip_b+1
eor #$80
sta g_cmp
lda t_clip_t+1
eor #$80
cmp g_cmp
bcs text_branch_45
jmp text_branch_44
text_branch_45:
beq text_branch_46
jmp text_done
text_branch_46:
lda t_clip_b
sta g_cmp
lda t_clip_t
cmp g_cmp
bcs text_branch_47
jmp text_branch_44
text_branch_47:
jmp text_done
text_branch_44:
lda #<TEXT_WIDTH
sta t_end
lda #>TEXT_WIDTH
sta t_end+1
lda t_end+1
sta g_cmp
lda t_clip_r+1
cmp g_cmp
bcs text_branch_49
jmp text_branch_48
text_branch_49:
beq text_branch_50
jmp text_done
text_branch_50:
lda t_end
sta g_cmp
lda t_clip_r
cmp g_cmp
bcs text_branch_51
jmp text_branch_48
text_branch_51:
beq text_branch_52
jmp text_done
text_branch_52:
text_branch_48:
lda #<TEXT_HEIGHT
sta t_end
lda #>TEXT_HEIGHT
sta t_end+1
lda t_end+1
sta g_cmp
lda t_clip_b+1
cmp g_cmp
bcs text_branch_54
jmp text_branch_53
text_branch_54:
beq text_branch_55
jmp text_done
text_branch_55:
lda t_end
sta g_cmp
lda t_clip_b
cmp g_cmp
bcs text_branch_56
jmp text_branch_53
text_branch_56:
beq text_branch_57
jmp text_done
text_branch_57:
text_branch_53:
lda #$00
sta g_cmp
lda t_count+1
cmp g_cmp
beq text_branch_59
jmp text_branch_58
text_branch_59:
lda #$00
sta g_cmp
lda t_count
cmp g_cmp
beq text_branch_60
jmp text_branch_58
text_branch_60:
jmp text_done
text_branch_58:
lda #$01
sta g_cmp
lda t_count+1
cmp g_cmp
bcs text_branch_62
jmp text_branch_61
text_branch_62:
beq text_branch_63
jmp text_done
text_branch_63:
lda #$00
sta g_cmp
lda t_count
cmp g_cmp
bcs text_branch_64
jmp text_branch_61
text_branch_64:
beq text_branch_65
jmp text_done
text_branch_65:
text_branch_61:
lda #$00
sta t_end
lda #$00
sta t_end+1
lda t_first
sta t_end
clc
lda t_end
adc t_count
sta t_end
lda t_end+1
adc t_count+1
sta t_end+1
lda #$01
sta g_cmp
lda t_end+1
cmp g_cmp
bcs text_branch_67
jmp text_branch_66
text_branch_67:
beq text_branch_68
jmp text_done
text_branch_68:
lda #$00
sta g_cmp
lda t_end
cmp g_cmp
bcs text_branch_69
jmp text_branch_66
text_branch_69:
beq text_branch_70
jmp text_done
text_branch_70:
text_branch_66:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_fallback
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
lda t_temp+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_72
jmp text_done
text_branch_72:
beq text_branch_73
jmp text_branch_71
text_branch_73:
lda t_temp
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_74
jmp text_done
text_branch_74:
text_branch_71:
lda t_end+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_76
jmp text_branch_75
text_branch_76:
beq text_branch_77
jmp text_done
text_branch_77:
lda t_end
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_78
jmp text_branch_75
text_branch_78:
jmp text_done
text_branch_75:
lda t_count
sta t_temp
lda t_count+1
sta t_temp+1
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
clc
lda t_temp
adc t_font
sta t_temp
lda t_temp+1
adc t_font+1
sta t_temp+1
lda #$00
sta g_cmp
lda t_temp+1
cmp g_cmp
beq text_branch_81
jmp text_branch_80
text_branch_81:
lda #$00
sta g_cmp
lda t_temp
cmp g_cmp
beq text_branch_82
jmp text_branch_80
text_branch_82:
jmp text_branch_79
text_branch_80:
lda t_font+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_84
jmp text_done
text_branch_84:
beq text_branch_85
jmp text_branch_83
text_branch_85:
lda t_font
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_86
jmp text_done
text_branch_86:
text_branch_83:
text_branch_79:
lda #$01
sta t_limit
lda #$00
sta g_cmp
lda t_entry
cmp g_cmp
beq text_branch_89
jmp text_branch_88
text_branch_89:
jmp text_branch_87
text_branch_88:
lda t_length
sta t_limit
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_length
sta t_temp
clc
lda t_temp
adc t_source
sta t_temp
lda t_temp+1
adc t_source+1
sta t_temp+1
lda #$00
sta g_cmp
lda t_temp+1
cmp g_cmp
beq text_branch_92
jmp text_branch_91
text_branch_92:
lda #$00
sta g_cmp
lda t_temp
cmp g_cmp
beq text_branch_93
jmp text_branch_91
text_branch_93:
jmp text_branch_90
text_branch_91:
lda t_source+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_95
jmp text_done
text_branch_95:
beq text_branch_96
jmp text_branch_94
text_branch_96:
lda t_source
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_97
jmp text_done
text_branch_97:
text_branch_94:
text_branch_90:
text_branch_87:
lda #$01
sta t_status
lda #$00
sta t_i
text_glyph:
lda t_limit
sta g_cmp
lda t_i
cmp g_cmp
bcs text_branch_99
jmp text_branch_98
text_branch_99:
jmp text_done
text_branch_98:
lda #$00
sta g_cmp
lda t_entry
cmp g_cmp
beq text_branch_103
jmp text_branch_102
text_branch_103:
jmp text_branch_100
text_branch_102:
lda #$00
sta t_pointer
lda #$00
sta t_pointer+1
lda t_i
sta t_pointer
clc
lda t_pointer
adc t_source
sta t_pointer
lda t_pointer+1
adc t_source+1
sta t_pointer+1
lda t_pointer
sta BP+46
lda t_pointer+1
sta BP+47
ldy #0
lda (BP+46),y
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda #$00
sta t_index+1
jmp text_branch_101
text_branch_100:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_code
sta t_index
text_branch_101:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
lda t_temp+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_107
jmp text_branch_104
text_branch_107:
beq text_branch_108
jmp text_branch_106
text_branch_108:
lda t_temp
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_109
jmp text_branch_104
text_branch_109:
text_branch_106:
sec
lda t_index
sbc t_temp
sta t_index
lda t_index+1
sbc t_temp+1
sta t_index+1
lda t_count+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_111
jmp text_branch_105
text_branch_111:
beq text_branch_112
jmp text_branch_110
text_branch_112:
lda t_count
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_113
jmp text_branch_105
text_branch_113:
text_branch_110:
text_branch_104:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_fallback
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
sec
lda t_index
sbc t_temp
sta t_index
lda t_index+1
sbc t_temp+1
sta t_index+1
clc
lda t_missing
adc #$01
sta t_missing
text_branch_105:
asl t_index
rol t_index+1
asl t_index
rol t_index+1
asl t_index
rol t_index+1
lda t_font
sta t_pointer
lda t_font+1
sta t_pointer+1
clc
lda t_pointer
adc t_index
sta t_pointer
lda t_pointer+1
adc t_index+1
sta t_pointer+1
lda #$00
sta t_row
text_row:
lda #$08
sta g_cmp
lda t_row
cmp g_cmp
bcs text_branch_115
jmp text_branch_114
text_branch_115:
jmp text_next_glyph
text_branch_114:
lda t_y
sta t_py
lda t_y+1
sta t_py+1
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_row
sta t_temp
clc
lda t_py
adc t_temp
sta t_py
lda t_py+1
adc t_temp+1
sta t_py+1
lda #$00
sta g_cmp
lda t_mode
cmp g_cmp
beq text_branch_118
jmp text_branch_117
text_branch_118:
jmp text_branch_116
text_branch_117:
lda t_next_x
sta t_left
lda t_next_x+1
sta t_left+1
lda t_next_x
sta t_right
lda t_next_x+1
sta t_right+1
clc
lda t_right
adc #$08
sta t_right
lda t_right+1
adc #$00
sta t_right+1
lda t_bg
sta t_pen
jsr text_span
text_branch_116:
lda t_pointer
sta BP+46
lda t_pointer+1
sta BP+47
ldy #0
lda (BP+46),y
sta t_bits
clc
lda t_pointer
adc #$01
sta t_pointer
lda t_pointer+1
adc #$00
sta t_pointer+1
lda #$00
sta t_column
lda #$00
sta t_run
text_bit:
lda #$08
sta g_cmp
lda t_column
cmp g_cmp
bcs text_branch_120
jmp text_branch_119
text_branch_120:
jmp text_row_end
text_branch_119:
lda t_bits
and #$80
bne text_branch_123
jmp text_branch_121
text_branch_123:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_125
jmp text_branch_122
text_branch_125:
text_branch_124:
lda t_column
sta t_start
lda #$01
sta t_run
jmp text_branch_122
text_branch_121:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_127
jmp text_branch_126
text_branch_127:
jmp text_branch_122
text_branch_126:
jsr text_run
lda #$00
sta t_run
text_branch_122:
asl t_bits
clc
lda t_column
adc #$01
sta t_column
jmp text_bit
text_row_end:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_130
jmp text_branch_129
text_branch_130:
jmp text_branch_128
text_branch_129:
jsr text_run
text_branch_128:
clc
lda t_row
adc #$01
sta t_row
jmp text_row
text_next_glyph:
clc
lda t_next_x
adc #$08
sta t_next_x
lda t_next_x+1
adc #$00
sta t_next_x+1
clc
lda t_i
adc #$01
sta t_i
lda t_i
sta t_consumed
jmp text_glyph
text_done:
rts
text_run:
lda #$00
sta t_left
lda #$00
sta t_left+1
lda t_start
sta t_left
clc
lda t_left
adc t_next_x
sta t_left
lda t_left+1
adc t_next_x+1
sta t_left+1
lda #$00
sta t_right
lda #$00
sta t_right+1
lda t_column
sta t_right
clc
lda t_right
adc t_next_x
sta t_right
lda t_right+1
adc t_next_x+1
sta t_right+1
lda t_fg
sta t_pen
jmp text_span
text_span:
; Intersect a half-open run with the validated physical-screen scissor.
lda t_clip_t+1
eor #$80
sta g_cmp
lda t_py+1
eor #$80
cmp g_cmp
bcs text_branch_132
jmp text_span_done
text_branch_132:
beq text_branch_133
jmp text_branch_131
text_branch_133:
lda t_clip_t
sta g_cmp
lda t_py
cmp g_cmp
bcs text_branch_134
jmp text_span_done
text_branch_134:
text_branch_131:
lda t_clip_b+1
eor #$80
sta g_cmp
lda t_py+1
eor #$80
cmp g_cmp
bcs text_branch_136
jmp text_branch_135
text_branch_136:
beq text_branch_137
jmp text_span_done
text_branch_137:
lda t_clip_b
sta g_cmp
lda t_py
cmp g_cmp
bcs text_branch_138
jmp text_branch_135
text_branch_138:
jmp text_span_done
text_branch_135:
lda t_clip_l+1
eor #$80
sta g_cmp
lda t_right+1
eor #$80
cmp g_cmp
bcs text_branch_140
jmp text_span_done
text_branch_140:
beq text_branch_141
jmp text_branch_139
text_branch_141:
lda t_clip_l
sta g_cmp
lda t_right
cmp g_cmp
bcs text_branch_142
jmp text_span_done
text_branch_142:
bne text_branch_143
jmp text_span_done
text_branch_143:
text_branch_139:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_left+1
eor #$80
cmp g_cmp
bcs text_branch_145
jmp text_branch_144
text_branch_145:
beq text_branch_146
jmp text_span_done
text_branch_146:
lda t_clip_r
sta g_cmp
lda t_left
cmp g_cmp
bcs text_branch_147
jmp text_branch_144
text_branch_147:
jmp text_span_done
text_branch_144:
lda t_clip_l+1
eor #$80
sta g_cmp
lda t_left+1
eor #$80
cmp g_cmp
bcs text_branch_150
jmp text_branch_149
text_branch_150:
beq text_branch_151
jmp text_branch_148
text_branch_151:
lda t_clip_l
sta g_cmp
lda t_left
cmp g_cmp
bcs text_branch_152
jmp text_branch_149
text_branch_152:
jmp text_branch_148
text_branch_149:
lda t_clip_l
sta t_left
lda t_clip_l+1
sta t_left+1
text_branch_148:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_right+1
eor #$80
cmp g_cmp
bcs text_branch_155
jmp text_branch_153
text_branch_155:
beq text_branch_156
jmp text_branch_154
text_branch_156:
lda t_clip_r
sta g_cmp
lda t_right
cmp g_cmp
bcs text_branch_157
jmp text_branch_153
text_branch_157:
bne text_branch_158
jmp text_branch_153
text_branch_158:
text_branch_154:
lda t_clip_r
sta t_right
lda t_clip_r+1
sta t_right+1
text_branch_153:
lda t_left
sta bp_x0
lda t_left+1
sta bp_x0+1
lda t_right
sta bp_x1
lda t_right+1
sta bp_x1+1
lda t_py
sta bp_y0
lda t_py+1
sta bp_y0+1
!if TEXT_PACKED {
lda #$00
sta bp_op
lda t_pen
sta bp_pen
} else {
lda #$01
sta bp_op
lda #$00
sta g_cmp
lda t_pen
cmp g_cmp
beq text_branch_161
jmp text_branch_160
text_branch_161:
jmp text_branch_159
text_branch_160:
lda #$00
sta bp_op
text_branch_159:
}
jsr bp_hspan
lda #$00
sta t_work
lda #$00
sta t_work+1
lda #$00
sta t_work+2
lda #$00
sta t_work+3
lda bp_writes
sta t_work
lda bp_writes+1
sta t_work+1
clc
lda t_writes
adc t_work
sta t_writes
lda t_writes+1
adc t_work+1
sta t_writes+1
lda t_writes+2
adc t_work+2
sta t_writes+2
lda t_writes+3
adc t_work+3
sta t_writes+3
clc
lda t_calls
adc #$01
sta t_calls
lda t_calls+1
adc #$00
sta t_calls+1
text_span_done:
rts
text_end:What was verified
76 calls completed using Asm198x 0.0.71 and native Emu198x 0.39.0 in this original reset configuration. Every call compares the full bitmap, guards, font/string preservation, inputs, acceptance, advance, consumption/substitutions and work counters with an independent pixel model. Cases include phases, negative origins, scissor edges, all pen pairs, empty/long strings, fallback, 256-code fonts and rejected declarations. Selected whole native frames also match independently composed pixels.
Wrong glyph-bit and string-advance builds each completed accepted calls and failed both memory and picture checks. Retained records are bound to source and exact tools, with failure tests for missing/corrupted evidence. This establishes the stated reset fixture; stock-OS loading, real-hardware timing and tear-free live drawing need their own checks.
- Complete caller and execution evidence
- Full contract, costs and MIT attribution
- Compare text paths
- Underlying pixels and spans
Make the letters bigger
A title need not use the same tiny cells as a score. Repeat each source pixel twice in both directions and an 8×8 cell becomes 16×16. Use different factors for deliberately wide or tall lettering: 3×2 gives a 24×16 cell. The font still stores just eight bytes per glyph.

Choose scaled-text/6502.inc instead of the fixed bitmap-text CPU include. It exports the same text_char and text_string labels, so include only one. Keep this machine’s existing backend and required helper/tables. The allocations remain 96 bytes at T plus 48 at BP. Add two explicit inputs: T+68 is scale X and T+69 is scale Y, each an integer from 1 to 4. Set both, even for 1×1; an old fixed-width caller leaves those bytes uninitialised.
Initialise font, pens, scissor and both scales before text_string
; SPDX-License-Identifier: MIT
; A normal game call: enlarged glyph cells, off-grid origin, opaque label.
lda #<(21)
sta T+0
lda #>(21)
sta T+1
lda #<(37)
sta T+2
lda #>(37)
sta T+3
lda #<(font_data)
sta T+4
lda #>(font_data)
sta T+5
lda #<(label_data)
sta T+6
lda #>(label_data)
sta T+7
lda #<(64)
sta T+11
lda #>(64)
sta T+12
lda #<(0)
sta T+18
lda #>(0)
sta T+19
lda #<(0)
sta T+20
lda #>(0)
sta T+21
lda #<(320)
sta T+22
lda #>(320)
sta T+23
lda #<(256)
sta T+24
lda #>(256)
sta T+25
lda #MESSAGE_LENGTH
sta T+8
lda #65
sta T+9
lda #32
sta T+10
lda #63
sta T+13
lda #1
sta T+14
lda #1
sta T+15
lda #0
sta T+16
lda #SCALE_X
sta T+68
lda #SCALE_Y
sta T+69
jsr text_stringThe existing font/range, fallback, source/count, mode, pens and scissor contract still applies. Inputs T+0..16, +18..25 and +68..69 survive. T+17 reports acceptance; T+26 returns X + length×8×scaleX as a word. Clipped, blank and fallback codes still advance. At scale X=4, 255 codes advance 8,160 pixels; a byte would lose most of that result. A rejected declaration preserves the original X and returns zero consumed, fallback, span and write counts before any destination writes.
Each font pixel becomes a rectangle, rather than a smoothed edge. For a destination-local pixel, the source position is (x div scaleX, y div scaleY). Equivalently, source column 3 at scale X=2 occupies local columns 6 and 7. Add the cell origin, then clip. A cut-off letter keeps that sampling phase: it does not start again at font column zero at the clipping edge. Integer division and remainders and glyph-local coordinates explain those two steps.
The native code scales the endpoints of each foreground run and repeats source rows. The independent checker instead samples destination pixels using integer division. Transparent zero bits preserve the picture underneath; opaque mode paints the enlarged background cell first, then its ink. Opaque spaces therefore clear their whole enlarged cells. Neither mode clears unused panel space.
Make it your own: wide lettering
The maintained custom caller changes 2×2 to 3×2 and replaces the ten-byte PIXEL TEXT message with four-byte BIG!. Its cells become 24×16, and the four-code label advances 96 pixels. The font and workspace sizes stay the same. Change the counted length with the message.
Choose independent X/Y enlargement and a counted label
; SPDX-License-Identifier: MIT
CUSTOM_TEXT = 1
SCALE_X = 3
SCALE_Y = 2
MESSAGE_LENGTH = 4
!source "caller.inc"
Use 1×4 or 4×1 for other shapes; all sixteen pairs are executed. These factors describe logical pixels. Equal X/Y factors preserve the native raster’s pixel proportions, which are not necessarily square on a physical display. The captures do not calibrate a television’s aspect ratio.
Bigger glyphs, but an old drawing step
The following fragment is deliberately wrong. The glyph is now 16 pixels wide, but the next origin still moves eight. WAVE overlaps its neighbours. In opaque mode, a later background cell can erase part of the preceding letter.
Deliberately wrong: keep an eight-pixel advance
; BEGIN WRONG
clc
lda t_next_x
adc #8
sta t_next_x
lda t_next_x+1
adc #0
sta t_next_x+1
Restore the cached 8×scaleX advance, then check both the final X and the full picture:
Correct: move by the enlarged cell width
; BEGIN FIX
clc
lda t_next_x
adc t_advance
sta t_next_x
lda t_next_x+1
adc t_advance+1
sta t_next_x+1
Size has a drawing cost
The scaled component uses 3,128 code bytes with the qualified assembler; the backend remains 526. The original sample font is still 512 bytes, and T/BP remain 96+48. BBC scanline tables, display memory, caller, string and stack remain separate. Keep a conservative 64-byte stack budget plus your caller/interrupt depth, and the renderer’s CPU/register obligations above.
For the same original W! label at (21,37), opaque mode and the same pens, native counters report. Cell dimensions are logical pixels:
| Scale | Cell (px) | Spans | Byte writes |
|---|---|---|---|
| 1×1 | 8×8 | 38 | 54 |
| 2×2 | 16×16 | 76 | 168 |
| 3×3 | 24×24 | 114 | 282 |
| 4×4 | 32×32 | 152 | 416 |
Doubling both factors quadruples a cell’s background area. Horizontal enlargement widens runs and changes byte crossings; vertical enlargement repeats rows and spans. CPC packs four pen indices per byte while the mono backends pack eight pixels, so their write counts differ. Counters include repeated writes to a byte. They measure drawing work, not cycles or a frame budget.
A separate pre-rendered size costs more assets but can avoid runtime expansion. Pre-shifted/masked blitters or precomposed hardware characters offer other trade-offs. Fully clipped cells skip raster work after resolving fallback, retaining logical advance and consumed/missing counts. This avoids expanding invisible rows in long offscreen strings.
The component owns T/BP, is non-re-entrant and supplies no OS negotiation, banking, interrupt protection or safe display interval. Keep source, font, code, contexts, stack and bitmap distinct and stable. This is a direct character/string renderer: the current fixed/proportional text-layout wrappers assume eight-pixel cells and row heights and cannot simply be placed over this include. A scaled layout must agree on both dimensions. Scaled wrapping, baselines and kerning remain separate work. The bounded shrinking routine below resamples ink inside an eight-byte cell. The glyph transform below supplies flips and quarter-turns before drawing.
The complete source and retained records contain 155 positive native calls, the customisation and completed wrong/fixed observations. Full bitmap allocations, neighbours, public outputs, counted source/font immutability and selected entire native frames match independent expectations. Coverage includes all scale pairs, every horizontal bit phase, pens, partial/full clipping, empty/255-code input, a 256-glyph declaration and invalid declarations. Qualified tools are Asm198x 0.0.71 and Emu198x 0.41.0 for this named reset/display path; original hardware, stock-OS coexistence, live tearing and other modes need separate checks.
Turn or reflect a glyph
An arrow can point in four directions without four hand-drawn assets. This separate 6502 glyph_transform routine takes eight row bytes and writes eight transformed bytes. It can turn by 0°, 90°, 180° or 270° clockwise, then optionally reflect horizontally. It does not draw or rotate the arrangement of a string.

Define F as 32 writable bytes, include cross-platform/patterns/rendering/glyph-transform/6502.inc once, and set the source address, destination address and flags before calling glyph_transform. Define GP as two zero-page bytes too; F need not be zero page. The complete caller owns the same display setup and text backend as the enlarged example above.
Transform eight bytes, then draw the result
; SPDX-License-Identifier: MIT
lda #<(font_data+304)
sta F
lda #>(font_data+304)
sta F+1
lda #<8320
sta F+2
lda #>8320
sta F+3
lda #GLYPH_FLAGS
sta F+4
jsr glyph_transform
; SPDX-License-Identifier: MIT
; A normal game call: enlarged glyph cells, off-grid origin, opaque label.
lda #<(21)
sta T+0
lda #>(21)
sta T+1
lda #<(37)
sta T+2
lda #>(37)
sta T+3
lda #<(8320)
sta T+4
lda #>(8320)
sta T+5
lda #<(label_data)
sta T+6
lda #>(label_data)
sta T+7
lda #<(1)
sta T+11
lda #>(1)
sta T+12
lda #<(0)
sta T+18
lda #>(0)
sta T+19
lda #<(0)
sta T+20
lda #>(0)
sta T+21
lda #<(320)
sta T+22
lda #>(320)
sta T+23
lda #<(256)
sta T+24
lda #>(256)
sta T+25
lda #MESSAGE_LENGTH
sta T+8
lda #65
sta T+9
lda #65
sta T+10
lda #65
sta T+13
lda #1
sta T+14
lda #1
sta T+15
lda #0
sta T+16
lda #SCALE_X
sta T+68
lda #SCALE_Y
sta T+69
jsr text_stringThe first call produces a one-glyph font in RAM. The rest declares that font to the existing renderer and draws it. The source is our original F glyph; the one-glyph output is deliberately assigned code 65. Change the source address to reuse the component for your own icon. No other part of the font is required by the transform.
| F offset | Supply or read |
|---|---|
| 0 | Source address word, eight readable bytes |
| 2 | Destination address word, eight writable bytes |
| 4 | Flags 0..7: quarter-turns in bits 0..1, horizontal reflection in bit 2 |
| 5 | Output: 1 completed, 0 rejected |
| 6..31 | Private scratch, including an eight-byte snapshot |
Use little-endian words. Inputs 0..4 survive. Flags above 7, or either address above $FFF8, reject before source/destination access. An allocation ending exactly at $10000 passes the arithmetic check; you still own its mapping and access permissions. Keep F, GP, code and stack disjoint from both glyph buffers. Source and destination may overlap, including exact in-place operation: the routine snapshots all eight rows before clearing output.
Clobbers: A/X/Y and flags; keep decimal mode clear. GP is two additional zero-page bytes ($80 in this caller), separate from BP. Scratch is non-re-entrant. Reserve a conservative 16 stack bytes beyond the caller and interrupt requirements. The routine changes no interrupt policy or display state. An interrupt handler must preserve live registers and leave its buffers and scratch alone. The later drawing call retains the renderer’s separate stack and hardware obligations.
The top-left dot must move to the top-right
Coordinates inside the glyph run from 0 to 7, with Y down. A clockwise quarter-turn maps (x,y) to (7−y,x): local (0,0) becomes (7,0). Twice gives (7−x,7−y); three turns gives (y,7−x); four returns the original. Reflection then maps final X to 7−X. Doing the reflection first generally gives a different result.
The transformed glyph still occupies an 8×8 cell. Scale 4 makes it a 32×32 drawn cell; translation places it at the chosen screen origin. Layout advance and the order of characters do not rotate. Proportional widths and bearings need their own matching metrics; they are not updated by turning the ink. The Maths for Games overview places this alongside planned transform coverage without requiring that future lesson to use the routine.
Make it your own: a reflected icon
Change GLYPH_FLAGS from 1 to 4: no turn, then horizontal reflection. The stem of F moves to the right and its top bar stays horizontal. The input/output sizes and reusable component stay the same.
Reflect the same source glyph
; SPDX-License-Identifier: MIT
CUSTOM_TEXT = 1
SCALE_X = 4
SCALE_Y = 4
MESSAGE_LENGTH = 1
GLYPH_FLAGS = 4
!source "caller.inc"
To retain a whole transformed font, call once per glyph and move both pointers by eight. Check the whole bank in your caller. Per-glyph overlap safety does not make an overlapping bank traversal safe: one result could overwrite the next source glyph. Use separate banks or arrange traversal/snapshots deliberately.
Common mistake: a diagonal reflection instead of a turn
Swapping X and Y alone produces (y,x). That is a transpose: on this F, the long top bar points down from the left instead of becoming the right-hand vertical bar. The wrong routine returns success and stays inside its output buffer. Neither property proves that its picture is right.
Deliberately wrong mapping:
Swap coordinates without reversing an axis
; BEGIN WRONG
lda f_dy
sta f_dx
Restore the missing 7−y:
Turn the glyph clockwise
; BEGIN FIX
lda #$07
sta f_dx
sec
lda f_dx
sbc f_dy
sta f_dx
Pay once, cache, or store the variants
The transform component occupies 548 bytes, including its eight masks; it needs 32 bytes at F, two at GP and eight bytes for each retained result. Font, renderer, backend, bitmap, caller and stack are additional.
A valid call reads eight source bytes into its snapshot, clears eight output bytes and scans all 64 source pixels. Each set pixel then reads and writes its destination byte. These are glyph-buffer operations, not all CPU accesses or cycle timings. More quarter-turns add coordinate arithmetic per set pixel. The original F contains 14 set pixels, so it makes that many destination read/modify/write operations after the eight clears.
Cache the result while it is useful, or prepare the variants at build time. Eight orientations cost 64 bytes per glyph rather than eight; eight whole 64-glyph banks cost 4,096 rather than 512. Stored variants avoid this transform call but still need drawing or transfer work. A hardware flip can be a better choice where the native display supports one; these bitmap backends do not gain that facility from this routine. No cycle or frame-budget claim follows from a correct still image.
All eight transforms preserve every source bit exactly. Arbitrary-angle rotation, larger pictures and pixel-aspect correction remain separate extensions. The bounded shrinking routine below supplies one explicit sampling policy.
What the transform checks establish
596 native observations completed on this machine with Asm198x 0.0.71 and Emu198x 0.41.0: all eight orientations, every single source pixel, asymmetric/full/empty glyphs, partial/exact overlap, invalid flags and wrapping addresses, plus normal, customised, faulty and repaired paths. Complete glyph/source/guard memory and selected full bitmap/native frames match an independent matrix-based oracle. The wrong rotation completed and failed the correct-image comparison; the repaired build matches the positive binary.
This is the same original reset configuration as the enlargement example, with exclusive ownership of the display and buffers. Stock OS integration, original hardware, interrupt coexistence and cycle timing need separate qualification. Retained replay binds source and tools and rejects incomplete, missing, corrupted and rehashed-but-wrong evidence.
Open the glyph transform routine
Transform a glyph independently of the display
; SPDX-License-Identifier: MIT
; Transform one MSB-left 8x8 glyph. F = 32 caller-owned RAM bytes.
; 6502 additionally needs GP = two zero-page bytes, disjoint from F and data.
; Flags 0..3 clockwise quarter-turns, bit 2 horizontal reflection AFTER rotation.
; Snapshot input before any output; overlapping source/destination is supported.
f_source = F+0
f_dest = F+2
f_flags = F+4
f_status = F+5
f_ptr = F+6
f_x = F+8
f_y = F+9
f_dx = F+10
f_dy = F+11
f_bits = F+12
f_mask = F+13
f_turns = F+14
f_i = F+15
f_copy = F+16
f_tmp = F+24
f_cmp = F+31
glyph_cmp = F+31
glyph_transform:
lda #$00
sta f_status
lda #$07
sta glyph_cmp
lda f_flags
cmp glyph_cmp
bcs glyph_branch_2
jmp glyph_branch_1
glyph_branch_2:
beq glyph_branch_3
jmp glyph_done
glyph_branch_3:
glyph_branch_1:
lda #$ff
sta glyph_cmp
lda f_source+1
cmp glyph_cmp
bcs glyph_branch_5
jmp glyph_branch_4
glyph_branch_5:
beq glyph_branch_6
jmp glyph_done
glyph_branch_6:
lda #$f8
sta glyph_cmp
lda f_source
cmp glyph_cmp
bcs glyph_branch_7
jmp glyph_branch_4
glyph_branch_7:
beq glyph_branch_8
jmp glyph_done
glyph_branch_8:
glyph_branch_4:
lda #$ff
sta glyph_cmp
lda f_dest+1
cmp glyph_cmp
bcs glyph_branch_10
jmp glyph_branch_9
glyph_branch_10:
beq glyph_branch_11
jmp glyph_done
glyph_branch_11:
lda #$f8
sta glyph_cmp
lda f_dest
cmp glyph_cmp
bcs glyph_branch_12
jmp glyph_branch_9
glyph_branch_12:
beq glyph_branch_13
jmp glyph_done
glyph_branch_13:
glyph_branch_9:
; Copy all eight source rows before clearing the destination.
lda #$00
sta f_i
lda f_source
sta f_ptr
lda f_source+1
sta f_ptr+1
glyph_snapshot:
lda f_ptr
sta GP
lda f_ptr+1
sta GP+1
ldy #0
lda (GP),y
ldx f_i
sta f_copy,x
clc
lda f_ptr
adc #$01
sta f_ptr
lda f_ptr+1
adc #$00
sta f_ptr+1
clc
lda f_i
adc #$01
sta f_i
lda #$08
sta glyph_cmp
lda f_i
cmp glyph_cmp
bcs glyph_branch_15
jmp glyph_snapshot
glyph_branch_15:
glyph_branch_14:
lda #$00
sta f_i
lda f_dest
sta f_ptr
lda f_dest+1
sta f_ptr+1
glyph_clear:
lda #$00
pha
lda f_ptr
sta GP
lda f_ptr+1
sta GP+1
ldy #0
pla
sta (GP),y
clc
lda f_ptr
adc #$01
sta f_ptr
lda f_ptr+1
adc #$00
sta f_ptr+1
clc
lda f_i
adc #$01
sta f_i
lda #$08
sta glyph_cmp
lda f_i
cmp glyph_cmp
bcs glyph_branch_17
jmp glyph_clear
glyph_branch_17:
glyph_branch_16:
lda #$00
sta f_y
glyph_row:
ldx f_y
lda f_copy,x
sta f_bits
lda #$00
sta f_x
glyph_pixel:
lda f_bits
and #$80
bne glyph_branch_18
jmp glyph_next
glyph_branch_18:
lda f_x
sta f_dx
lda f_y
sta f_dy
lda f_flags
and #3
sta f_turns
glyph_turn:
lda #$00
sta glyph_cmp
lda f_turns
cmp glyph_cmp
beq glyph_branch_20
jmp glyph_branch_19
glyph_branch_20:
jmp glyph_reflect
glyph_branch_19:
lda f_dx
sta f_tmp
; BEGIN ROTATION FIX: (x,y) becomes (7-y,x), not (y,x).
lda #$07
sta f_dx
sec
lda f_dx
sbc f_dy
sta f_dx
; END ROTATION FIX
lda f_tmp
sta f_dy
clc
lda f_turns
adc #$ff
sta f_turns
jmp glyph_turn
glyph_reflect:
lda f_flags
and #4
bne glyph_branch_21
jmp glyph_store
glyph_branch_21:
lda #$07
sta f_tmp
sec
lda f_tmp
sbc f_dx
sta f_tmp
lda f_tmp
sta f_dx
glyph_store:
ldx f_dx
lda glyph_masks,x
sta f_mask
lda #$00
sta f_ptr
lda #$00
sta f_ptr+1
lda f_dy
sta f_ptr
clc
lda f_ptr
adc f_dest
sta f_ptr
lda f_ptr+1
adc f_dest+1
sta f_ptr+1
lda f_ptr
sta GP
lda f_ptr+1
sta GP+1
ldy #0
lda (GP),y
ora f_mask
pha
lda f_ptr
sta GP
lda f_ptr+1
sta GP+1
ldy #0
pla
sta (GP),y
glyph_next:
asl f_bits
clc
lda f_x
adc #$01
sta f_x
lda #$08
sta glyph_cmp
lda f_x
cmp glyph_cmp
bcs glyph_branch_23
jmp glyph_pixel
glyph_branch_23:
glyph_branch_22:
clc
lda f_y
adc #$01
sta f_y
lda #$08
sta glyph_cmp
lda f_y
cmp glyph_cmp
bcs glyph_branch_25
jmp glyph_row
glyph_branch_25:
glyph_branch_24:
lda #$01
sta f_status
glyph_done:
rts
glyph_masks:
!byte 128,64,32,16,8,4,2,1
glyph_transform_end:Shrink the ink, keep the cell
Need a smaller icon? glyph_resize resamples an 8×8 source into a width and height you choose, each 1..8. The result still occupies eight bytes: useful pixels sit at the top-left, with zero padding to the right and below. Your existing renderer can draw that cell without learning a new asset format. The whole source cell is sampled, including blank margins: the requested rectangle is not a measured tight ink box.

Define RZ as 32 writable bytes and include cross-platform/patterns/rendering/glyph-resize/6502.inc once. Define RP as two separate zero-page bytes too; RZ need not be zero page. Set both pointers and both dimensions before calling glyph_resize. Scratch needs no initialisation.
These are the complete caller’s scratch addresses. Choose free RAM in your own game; the address declaration does not allocate or protect it. The include is separate from the renderer and backend already shown above. Adjust repository-relative paths to match your project’s files.
Name the caller-owned scratch storage
RZ = 1152
RP = $80Include the reusable resize component once
!source "../../../../../cross-platform/patterns/rendering/glyph-resize/6502.inc"Resample one glyph, then draw its padded cell
; SPDX-License-Identifier: MIT
lda #<(font_data+304)
sta RZ
lda #>(font_data+304)
sta RZ+1
lda #<8320
sta RZ+2
lda #>8320
sta RZ+3
lda #GLYPH_WIDTH
sta RZ+4
lda #GLYPH_HEIGHT
sta RZ+5
jsr glyph_resize
; SPDX-License-Identifier: MIT
; A normal game call: enlarged glyph cells, off-grid origin, opaque label.
lda #<(21)
sta T+0
lda #>(21)
sta T+1
lda #<(37)
sta T+2
lda #>(37)
sta T+3
lda #<(8320)
sta T+4
lda #>(8320)
sta T+5
lda #<(label_data)
sta T+6
lda #>(label_data)
sta T+7
lda #<(1)
sta T+11
lda #>(1)
sta T+12
lda #<(0)
sta T+18
lda #>(0)
sta T+19
lda #<(0)
sta T+20
lda #>(0)
sta T+21
lda #<(320)
sta T+22
lda #>(320)
sta T+23
lda #<(256)
sta T+24
lda #>(256)
sta T+25
lda #MESSAGE_LENGTH
sta T+8
lda #65
sta T+9
lda #65
sta T+10
lda #65
sta T+13
lda #1
sta T+14
lda #1
sta T+15
lda #0
sta T+16
lda #SCALE_X
sta T+68
lda #SCALE_Y
sta T+69
jsr text_stringThe source is our original F; the caller assigns its resized output code 65 in a one-glyph font. The second part declares that font and calls the existing enlarged renderer. The resize routine itself draws nothing and changes no text metrics. Transparent drawing leaves zero padding alone. Opaque drawing still paints the background across the whole padded 8×8 cell (32×32 at the demo’s scale four), not just the requested sampling rectangle.
| RZ offset | Supply or read |
|---|---|
| 0 | Source address word, eight readable bytes |
| 2 | Destination address word, eight writable bytes |
| 4,5 | Width and height bytes, each 1..8 |
| 6 | Output: 1 completed, 0 rejected |
| 7..31 | Private scratch, including eight source snapshot bytes |
Words are little-endian; inputs 0..5 survive. Dimensions outside 1..8 or either pointer above $FFF8 reject before source/destination access. An eight-byte range ending at $10000 passes the arithmetic check; you must still own its mapped memory. Source and destination may overlap, even exactly, because all source rows are copied before output is cleared. Keep RZ, RP, code and stack disjoint from both.
Clobbers: A/X/Y and flags; decimal mode must be clear. Reserve RP as two zero-page bytes, separately from BP. The routine is non-re-entrant and changes no interrupt/display policy. Reserve a conservative 16-byte stack allowance beyond caller/interrupt requirements. An interrupt handler must preserve live registers and avoid its scratch and buffers. The later renderer retains its own stack, bank, display-access and interrupt obligations. This reset caller owns the same machine-specific display setup as the examples above; it is not a stock-OS launcher.
Sampling is a choice
Destination (x,y) selects source (x*8 div width, y*8 div height). This is an explicit top-left nearest-neighbour convention: select one source pixel, with no interpolation. Width five samples columns 0,1,3,4,6; width four samples 0,2,4,6. Height follows the same rule independently. An 8×8 request is the identity; 1×1 retains only the top-left source pixel.
The 6502 implementation uses bounded error accumulators. Add eight after each output pixel, subtract width and advance the source column while the error is at least width. Reset that state at the next row. The independent oracle uses direct division, so it checks the recurrence against a different calculation. Division and remainders explain the quotient and leftover; local-to-screen coordinates place the resulting cell.
A one-pixel vertical stroke in source column one disappears at width four: that column is never sampled. A specially drawn small icon can be clearer. Averaging, coverage filters and pixel-centre sampling would give different results; they are not hidden options in this routine. Logical bitmap pixels can also have non-square physical proportions, which these numeric dimensions do not correct.
Make it your own: a short, wide icon
Change GLYPH_WIDTH/GLYPH_HEIGHT from 5/7 to 8/4. Horizontal detail returns while alternate source rows are omitted. The result still occupies eight bytes, and the renderer’s cell and advance stay unchanged.
Keep all columns and halve the useful height
; SPDX-License-Identifier: MIT
CUSTOM_TEXT = 1
SCALE_X = 4
SCALE_Y = 4
MESSAGE_LENGTH = 1
GLYPH_WIDTH = 8
GLYPH_HEIGHT = 4
!source "caller.inc"
Replace the eight source bytes to resize another icon. A larger input, target above eight, tightly packed variable-size output or proportional metrics needs a different contract. For a bank, validate the complete allocation before calling once per glyph. Per-call overlap safety does not make an overlapping bank traversal safe: an early result may overwrite a later source. Prefer separate banks or plan snapshots/traversal explicitly.
Common mistake: crop instead of resample
The following fragment is deliberately wrong. It advances the source column by one per output pixel. At width five it takes columns 0..4 and drops the right side, rather than selecting across the whole source. Y still resamples correctly in this example. The F’s bars acquire the wrong proportions despite a successful, in-bounds call.
Wrong: take only the left source columns
; BEGIN WRONG
clc
lda r_sx
adc #$01
sta r_sx
Restore the accumulator which distributes eight source steps over the requested width:
Resample across the whole source width
; BEGIN FIX
clc
lda r_xe
adc #$08
sta r_xe
resize_x_sample:
lda r_width
sta resize_cmp
lda r_xe
cmp resize_cmp
bcs resize_branch_27
jmp resize_x_ready
resize_branch_27:
resize_branch_26:
sec
lda r_xe
sbc r_width
sta r_xe
clc
lda r_sx
adc #$01
sta r_sx
jmp resize_x_sample
resize_x_ready:
Work, storage and quality
The component occupies 611 bytes, including its eight masks. It needs 32 bytes at RZ, two at RP, and eight bytes per retained result. Font, renderer, backend, caller, display memory and stack remain additional.
Each valid call snapshots eight source bytes, clears eight result bytes and samples width×height pixels, from 1 to 64. Each sampled set bit reads and writes its destination byte. These are glyph-buffer operations, not all CPU accesses or measured cycles. Smaller requests also perform repeated accumulator subtraction; cost does not fall simply in proportion to output area.
The output always costs eight bytes, even for 1×1. This implementation preserves a convenient asset format rather than packing smaller glyphs. Cache a useful size or prepare it at build time. All 64 dimension pairs, including the 8×8 identity, cost 512 bytes per source glyph if stored together; one source costs eight. Store only the sizes your game needs. A hand-designed small font may preserve strokes better, and enlargement after shrinking cannot recover discarded details.
This does not change text advance, bearings or wrapping. A proportional font must keep its cropped pixels and metric table in agreement. Arbitrary angles, targets above eight and scale-aware layout remain separate extensions.
What the resize checks establish
543 native observations completed for this machine with Asm198x 0.0.71 and Emu198x 0.41.0: all 64 size pairs on four source shapes, every source pixel at four representative dimensions, partial/exact overlap, rejected dimensions/addresses, normal/customised callers, and completed wrong/repaired sampling. Complete glyph/source/guard data and selected full bitmap/native frames match an independent division-based oracle. Retained controls reject missing, incomplete, corrupted and rehashed-but-wrong evidence.
This qualifies the named original reset fixture, not stock-OS loading, original hardware, interrupt coexistence or cycle timing. Width/height are useful ink bounds inside the padded cell, not a promise of variable-size layout or hardware upload.
Open the glyph shrinking routine
Resample independently of the display
; SPDX-License-Identifier: MIT
; Shrink an MSB-left 8x8 glyph into 1..8 by 1..8 ink bounds, zero-padded to 8x8.
; RZ = 32 RAM bytes; 6502 RP = separate two zero-page bytes.
; Source sampling: floor(destination*8/size), top-left convention. Snapshot permits overlap.
r_source = RZ+0
r_dest = RZ+2
r_width = RZ+4
r_height = RZ+5
r_status = RZ+6
r_ptr = RZ+8
r_x = RZ+10
r_y = RZ+11
r_sx = RZ+12
r_sy = RZ+13
r_xe = RZ+14
r_ye = RZ+15
r_bits = RZ+16
r_mask = RZ+17
r_cmp = RZ+23
r_copy = RZ+24
resize_cmp = RZ+23
glyph_resize:
lda #$00
sta r_status
lda #$01
sta resize_cmp
lda r_width
cmp resize_cmp
bcs resize_branch_2
jmp resize_done
resize_branch_2:
resize_branch_1:
lda #$08
sta resize_cmp
lda r_width
cmp resize_cmp
bcs resize_branch_4
jmp resize_branch_3
resize_branch_4:
beq resize_branch_5
jmp resize_done
resize_branch_5:
resize_branch_3:
lda #$01
sta resize_cmp
lda r_height
cmp resize_cmp
bcs resize_branch_7
jmp resize_done
resize_branch_7:
resize_branch_6:
lda #$08
sta resize_cmp
lda r_height
cmp resize_cmp
bcs resize_branch_9
jmp resize_branch_8
resize_branch_9:
beq resize_branch_10
jmp resize_done
resize_branch_10:
resize_branch_8:
lda #$ff
sta resize_cmp
lda r_source+1
cmp resize_cmp
bcs resize_branch_12
jmp resize_branch_11
resize_branch_12:
beq resize_branch_13
jmp resize_done
resize_branch_13:
lda #$f8
sta resize_cmp
lda r_source
cmp resize_cmp
bcs resize_branch_14
jmp resize_branch_11
resize_branch_14:
beq resize_branch_15
jmp resize_done
resize_branch_15:
resize_branch_11:
lda #$ff
sta resize_cmp
lda r_dest+1
cmp resize_cmp
bcs resize_branch_17
jmp resize_branch_16
resize_branch_17:
beq resize_branch_18
jmp resize_done
resize_branch_18:
lda #$f8
sta resize_cmp
lda r_dest
cmp resize_cmp
bcs resize_branch_19
jmp resize_branch_16
resize_branch_19:
beq resize_branch_20
jmp resize_done
resize_branch_20:
resize_branch_16:
lda #$00
sta r_x
lda r_source
sta r_ptr
lda r_source+1
sta r_ptr+1
resize_snapshot:
lda r_ptr
sta RP
lda r_ptr+1
sta RP+1
ldy #0
lda (RP),y
ldx r_x
sta r_copy,x
clc
lda r_ptr
adc #$01
sta r_ptr
lda r_ptr+1
adc #$00
sta r_ptr+1
clc
lda r_x
adc #$01
sta r_x
lda #$08
sta resize_cmp
lda r_x
cmp resize_cmp
bcs resize_branch_22
jmp resize_snapshot
resize_branch_22:
resize_branch_21:
lda #$00
sta r_x
lda r_dest
sta r_ptr
lda r_dest+1
sta r_ptr+1
resize_clear:
lda #$00
pha
lda r_ptr
sta RP
lda r_ptr+1
sta RP+1
ldy #0
pla
sta (RP),y
clc
lda r_ptr
adc #$01
sta r_ptr
lda r_ptr+1
adc #$00
sta r_ptr+1
clc
lda r_x
adc #$01
sta r_x
lda #$08
sta resize_cmp
lda r_x
cmp resize_cmp
bcs resize_branch_24
jmp resize_clear
resize_branch_24:
resize_branch_23:
lda #$00
sta r_y
lda #$00
sta r_sy
lda #$00
sta r_ye
resize_row:
ldx r_sy
lda r_copy,x
sta r_bits
lda #$00
sta r_x
lda #$00
sta r_sx
lda #$00
sta r_xe
resize_pixel:
ldx r_sx
lda resize_masks,x
and r_bits
bne resize_branch_25
jmp resize_next
resize_branch_25:
ldx r_x
lda resize_masks,x
sta r_mask
lda #$00
sta r_ptr
lda #$00
sta r_ptr+1
lda r_y
sta r_ptr
clc
lda r_ptr
adc r_dest
sta r_ptr
lda r_ptr+1
adc r_dest+1
sta r_ptr+1
lda r_ptr
sta RP
lda r_ptr+1
sta RP+1
ldy #0
lda (RP),y
ora r_mask
pha
lda r_ptr
sta RP
lda r_ptr+1
sta RP+1
ldy #0
pla
sta (RP),y
resize_next:
; BEGIN SAMPLING FIX: accumulate eight source steps per destination width.
clc
lda r_xe
adc #$08
sta r_xe
resize_x_sample:
lda r_width
sta resize_cmp
lda r_xe
cmp resize_cmp
bcs resize_branch_27
jmp resize_x_ready
resize_branch_27:
resize_branch_26:
sec
lda r_xe
sbc r_width
sta r_xe
clc
lda r_sx
adc #$01
sta r_sx
jmp resize_x_sample
resize_x_ready:
; END SAMPLING FIX
clc
lda r_x
adc #$01
sta r_x
lda r_width
sta resize_cmp
lda r_x
cmp resize_cmp
bcs resize_branch_29
jmp resize_pixel
resize_branch_29:
resize_branch_28:
clc
lda r_ye
adc #$08
sta r_ye
resize_y_sample:
lda r_height
sta resize_cmp
lda r_ye
cmp resize_cmp
bcs resize_branch_31
jmp resize_y_ready
resize_branch_31:
resize_branch_30:
sec
lda r_ye
sbc r_height
sta r_ye
clc
lda r_sy
adc #$01
sta r_sy
jmp resize_y_sample
resize_y_ready:
clc
lda r_y
adc #$01
sta r_y
lda r_height
sta resize_cmp
lda r_y
cmp resize_cmp
bcs resize_branch_33
jmp resize_row
resize_branch_33:
resize_branch_32:
lda #$01
sta r_status
resize_done:
rts
resize_masks:
!byte 128,64,32,16,8,4,2,1
glyph_resize_end:Read the reusable integer-enlarged renderer
Scaled foreground runs over the existing native span backend
; SPDX-License-Identifier: MIT
; Integer-enlarged 8x8 glyphs using the existing bp_hspan backend.
; T: 96 bytes, with scale inputs T+68/69 (1..4). BP: separate 48 bytes.
; All registers/flags used by the backend may change. Non-re-entrant.
; See README.md for trusted source allocations, pens, bounds and outputs.
t_x = T+0
t_y = T+2
t_font = T+4
t_source = T+6
t_length = T+8
t_code = T+9
t_first = T+10
t_count = T+11
t_fallback = T+13
t_mode = T+14
t_fg = T+15
t_bg = T+16
t_status = T+17
t_clip_l = T+18
t_clip_t = T+20
t_clip_r = T+22
t_clip_b = T+24
t_next_x = T+26
t_consumed = T+28
t_missing = T+29
t_calls = T+30
t_writes = T+32
t_entry = T+36
t_limit = T+37
t_i = T+38
t_index = T+40
t_end = T+42
t_pointer = T+44
t_row = T+46
t_column = T+47
t_bits = T+48
t_run = T+49
t_start = T+50
t_left = T+52
t_right = T+54
t_py = T+56
t_pen = T+58
t_temp = T+60
t_work = T+64
t_sx = T+68
t_sy = T+69
t_advance = T+70
t_repeat = T+74
t_product = T+76
t_factor = T+78
t_raw = T+80
t_cmp = T+95
bp_x0 = BP+0
bp_y0 = BP+2
bp_x1 = BP+4
bp_op = BP+8
bp_pen = BP+11
bp_writes = BP+40
g_cmp = T+95
text_char:
lda #$00
sta t_entry
jmp text_enter
text_string:
lda #$01
sta t_entry
jmp text_enter
text_enter:
lda #$00
sta t_status
lda #$00
sta t_writes
lda #$00
sta t_writes+1
lda #$00
sta t_writes+2
lda #$00
sta t_writes+3
lda #$00
sta t_calls
lda #$00
sta t_calls+1
lda #$00
sta t_consumed
lda #$00
sta t_missing
lda t_x
sta t_next_x
lda t_x+1
sta t_next_x+1
; Validate the complete declaration before drawing; no source read yet.
lda #$fe
eor #$80
sta g_cmp
lda t_x+1
eor #$80
cmp g_cmp
bcs text_branch_2
jmp text_done
text_branch_2:
beq text_branch_3
jmp text_branch_1
text_branch_3:
lda #$00
sta g_cmp
lda t_x
cmp g_cmp
bcs text_branch_4
jmp text_done
text_branch_4:
text_branch_1:
lda #$01
eor #$80
sta g_cmp
lda t_x+1
eor #$80
cmp g_cmp
bcs text_branch_6
jmp text_branch_5
text_branch_6:
beq text_branch_7
jmp text_done
text_branch_7:
lda #$ff
sta g_cmp
lda t_x
cmp g_cmp
bcs text_branch_8
jmp text_branch_5
text_branch_8:
beq text_branch_9
jmp text_done
text_branch_9:
text_branch_5:
lda #$fe
eor #$80
sta g_cmp
lda t_y+1
eor #$80
cmp g_cmp
bcs text_branch_11
jmp text_done
text_branch_11:
beq text_branch_12
jmp text_branch_10
text_branch_12:
lda #$00
sta g_cmp
lda t_y
cmp g_cmp
bcs text_branch_13
jmp text_done
text_branch_13:
text_branch_10:
lda #$01
eor #$80
sta g_cmp
lda t_y+1
eor #$80
cmp g_cmp
bcs text_branch_15
jmp text_branch_14
text_branch_15:
beq text_branch_16
jmp text_done
text_branch_16:
lda #$ff
sta g_cmp
lda t_y
cmp g_cmp
bcs text_branch_17
jmp text_branch_14
text_branch_17:
beq text_branch_18
jmp text_done
text_branch_18:
text_branch_14:
lda #$01
sta g_cmp
lda t_sx
cmp g_cmp
bcs text_branch_20
jmp text_done
text_branch_20:
text_branch_19:
lda #$04
sta g_cmp
lda t_sx
cmp g_cmp
bcs text_branch_22
jmp text_branch_21
text_branch_22:
beq text_branch_23
jmp text_done
text_branch_23:
text_branch_21:
lda #$01
sta g_cmp
lda t_sy
cmp g_cmp
bcs text_branch_25
jmp text_done
text_branch_25:
text_branch_24:
lda #$04
sta g_cmp
lda t_sy
cmp g_cmp
bcs text_branch_27
jmp text_branch_26
text_branch_27:
beq text_branch_28
jmp text_done
text_branch_28:
text_branch_26:
lda #$01
sta g_cmp
lda t_mode
cmp g_cmp
bcs text_branch_30
jmp text_branch_29
text_branch_30:
beq text_branch_31
jmp text_done
text_branch_31:
text_branch_29:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_fg
sta t_temp
lda #<TEXT_PEN_MAX
sta t_end
lda #>TEXT_PEN_MAX
sta t_end+1
lda t_end+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_33
jmp text_branch_32
text_branch_33:
beq text_branch_34
jmp text_done
text_branch_34:
lda t_end
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_35
jmp text_branch_32
text_branch_35:
beq text_branch_36
jmp text_done
text_branch_36:
text_branch_32:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_bg
sta t_temp
lda #<TEXT_PEN_MAX
sta t_end
lda #>TEXT_PEN_MAX
sta t_end+1
lda t_end+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_38
jmp text_branch_37
text_branch_38:
beq text_branch_39
jmp text_done
text_branch_39:
lda t_end
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_40
jmp text_branch_37
text_branch_40:
beq text_branch_41
jmp text_done
text_branch_41:
text_branch_37:
lda #$00
eor #$80
sta g_cmp
lda t_clip_l+1
eor #$80
cmp g_cmp
bcs text_branch_43
jmp text_done
text_branch_43:
beq text_branch_44
jmp text_branch_42
text_branch_44:
lda #$00
sta g_cmp
lda t_clip_l
cmp g_cmp
bcs text_branch_45
jmp text_done
text_branch_45:
text_branch_42:
lda #$00
eor #$80
sta g_cmp
lda t_clip_t+1
eor #$80
cmp g_cmp
bcs text_branch_47
jmp text_done
text_branch_47:
beq text_branch_48
jmp text_branch_46
text_branch_48:
lda #$00
sta g_cmp
lda t_clip_t
cmp g_cmp
bcs text_branch_49
jmp text_done
text_branch_49:
text_branch_46:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_clip_l+1
eor #$80
cmp g_cmp
bcs text_branch_51
jmp text_branch_50
text_branch_51:
beq text_branch_52
jmp text_done
text_branch_52:
lda t_clip_r
sta g_cmp
lda t_clip_l
cmp g_cmp
bcs text_branch_53
jmp text_branch_50
text_branch_53:
jmp text_done
text_branch_50:
lda t_clip_b+1
eor #$80
sta g_cmp
lda t_clip_t+1
eor #$80
cmp g_cmp
bcs text_branch_55
jmp text_branch_54
text_branch_55:
beq text_branch_56
jmp text_done
text_branch_56:
lda t_clip_b
sta g_cmp
lda t_clip_t
cmp g_cmp
bcs text_branch_57
jmp text_branch_54
text_branch_57:
jmp text_done
text_branch_54:
lda #<TEXT_WIDTH
sta t_end
lda #>TEXT_WIDTH
sta t_end+1
lda t_end+1
sta g_cmp
lda t_clip_r+1
cmp g_cmp
bcs text_branch_59
jmp text_branch_58
text_branch_59:
beq text_branch_60
jmp text_done
text_branch_60:
lda t_end
sta g_cmp
lda t_clip_r
cmp g_cmp
bcs text_branch_61
jmp text_branch_58
text_branch_61:
beq text_branch_62
jmp text_done
text_branch_62:
text_branch_58:
lda #<TEXT_HEIGHT
sta t_end
lda #>TEXT_HEIGHT
sta t_end+1
lda t_end+1
sta g_cmp
lda t_clip_b+1
cmp g_cmp
bcs text_branch_64
jmp text_branch_63
text_branch_64:
beq text_branch_65
jmp text_done
text_branch_65:
lda t_end
sta g_cmp
lda t_clip_b
cmp g_cmp
bcs text_branch_66
jmp text_branch_63
text_branch_66:
beq text_branch_67
jmp text_done
text_branch_67:
text_branch_63:
lda #$00
sta g_cmp
lda t_count+1
cmp g_cmp
beq text_branch_69
jmp text_branch_68
text_branch_69:
lda #$00
sta g_cmp
lda t_count
cmp g_cmp
beq text_branch_70
jmp text_branch_68
text_branch_70:
jmp text_done
text_branch_68:
lda #$01
sta g_cmp
lda t_count+1
cmp g_cmp
bcs text_branch_72
jmp text_branch_71
text_branch_72:
beq text_branch_73
jmp text_done
text_branch_73:
lda #$00
sta g_cmp
lda t_count
cmp g_cmp
bcs text_branch_74
jmp text_branch_71
text_branch_74:
beq text_branch_75
jmp text_done
text_branch_75:
text_branch_71:
lda #$00
sta t_end
lda #$00
sta t_end+1
lda t_first
sta t_end
clc
lda t_end
adc t_count
sta t_end
lda t_end+1
adc t_count+1
sta t_end+1
lda #$01
sta g_cmp
lda t_end+1
cmp g_cmp
bcs text_branch_77
jmp text_branch_76
text_branch_77:
beq text_branch_78
jmp text_done
text_branch_78:
lda #$00
sta g_cmp
lda t_end
cmp g_cmp
bcs text_branch_79
jmp text_branch_76
text_branch_79:
beq text_branch_80
jmp text_done
text_branch_80:
text_branch_76:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_fallback
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
lda t_temp+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_82
jmp text_done
text_branch_82:
beq text_branch_83
jmp text_branch_81
text_branch_83:
lda t_temp
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_84
jmp text_done
text_branch_84:
text_branch_81:
lda t_end+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_86
jmp text_branch_85
text_branch_86:
beq text_branch_87
jmp text_done
text_branch_87:
lda t_end
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_88
jmp text_branch_85
text_branch_88:
jmp text_done
text_branch_85:
lda t_count
sta t_temp
lda t_count+1
sta t_temp+1
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
clc
lda t_temp
adc t_font
sta t_temp
lda t_temp+1
adc t_font+1
sta t_temp+1
lda #$00
sta g_cmp
lda t_temp+1
cmp g_cmp
beq text_branch_91
jmp text_branch_90
text_branch_91:
lda #$00
sta g_cmp
lda t_temp
cmp g_cmp
beq text_branch_92
jmp text_branch_90
text_branch_92:
jmp text_branch_89
text_branch_90:
lda t_font+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_94
jmp text_done
text_branch_94:
beq text_branch_95
jmp text_branch_93
text_branch_95:
lda t_font
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_96
jmp text_done
text_branch_96:
text_branch_93:
text_branch_89:
lda #$01
sta t_limit
lda #$00
sta g_cmp
lda t_entry
cmp g_cmp
beq text_branch_99
jmp text_branch_98
text_branch_99:
jmp text_branch_97
text_branch_98:
lda t_length
sta t_limit
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_length
sta t_temp
clc
lda t_temp
adc t_source
sta t_temp
lda t_temp+1
adc t_source+1
sta t_temp+1
lda #$00
sta g_cmp
lda t_temp+1
cmp g_cmp
beq text_branch_102
jmp text_branch_101
text_branch_102:
lda #$00
sta g_cmp
lda t_temp
cmp g_cmp
beq text_branch_103
jmp text_branch_101
text_branch_103:
jmp text_branch_100
text_branch_101:
lda t_source+1
sta g_cmp
lda t_temp+1
cmp g_cmp
bcs text_branch_105
jmp text_done
text_branch_105:
beq text_branch_106
jmp text_branch_104
text_branch_106:
lda t_source
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_107
jmp text_done
text_branch_107:
text_branch_104:
text_branch_100:
text_branch_97:
lda #$00
sta t_advance
lda #$00
sta t_advance+1
lda t_sx
sta t_advance
asl t_advance
rol t_advance+1
asl t_advance
rol t_advance+1
asl t_advance
rol t_advance+1
lda #$01
sta t_status
lda #$00
sta t_i
text_glyph:
lda t_limit
sta g_cmp
lda t_i
cmp g_cmp
bcs text_branch_109
jmp text_branch_108
text_branch_109:
jmp text_done
text_branch_108:
lda #$00
sta g_cmp
lda t_entry
cmp g_cmp
beq text_branch_113
jmp text_branch_112
text_branch_113:
jmp text_branch_110
text_branch_112:
lda #$00
sta t_pointer
lda #$00
sta t_pointer+1
lda t_i
sta t_pointer
clc
lda t_pointer
adc t_source
sta t_pointer
lda t_pointer+1
adc t_source+1
sta t_pointer+1
lda t_pointer
sta BP+46
lda t_pointer+1
sta BP+47
ldy #0
lda (BP+46),y
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda #$00
sta t_index+1
jmp text_branch_111
text_branch_110:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_code
sta t_index
text_branch_111:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
lda t_temp+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_117
jmp text_branch_114
text_branch_117:
beq text_branch_118
jmp text_branch_116
text_branch_118:
lda t_temp
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_119
jmp text_branch_114
text_branch_119:
text_branch_116:
sec
lda t_index
sbc t_temp
sta t_index
lda t_index+1
sbc t_temp+1
sta t_index+1
lda t_count+1
sta g_cmp
lda t_index+1
cmp g_cmp
bcs text_branch_121
jmp text_branch_115
text_branch_121:
beq text_branch_122
jmp text_branch_120
text_branch_122:
lda t_count
sta g_cmp
lda t_index
cmp g_cmp
bcs text_branch_123
jmp text_branch_115
text_branch_123:
text_branch_120:
text_branch_114:
lda #$00
sta t_index
lda #$00
sta t_index+1
lda t_fallback
sta t_index
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_first
sta t_temp
sec
lda t_index
sbc t_temp
sta t_index
lda t_index+1
sbc t_temp+1
sta t_index+1
clc
lda t_missing
adc #$01
sta t_missing
text_branch_115:
; Reject fully clipped glyph cells after resolving fallback, before reading font rows.
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_next_x+1
eor #$80
cmp g_cmp
bcs text_branch_125
jmp text_branch_124
text_branch_125:
beq text_branch_126
jmp text_next_glyph
text_branch_126:
lda t_clip_r
sta g_cmp
lda t_next_x
cmp g_cmp
bcs text_branch_127
jmp text_branch_124
text_branch_127:
jmp text_next_glyph
text_branch_124:
lda t_next_x
sta t_temp
lda t_next_x+1
sta t_temp+1
clc
lda t_temp
adc t_advance
sta t_temp
lda t_temp+1
adc t_advance+1
sta t_temp+1
lda t_clip_l+1
eor #$80
sta g_cmp
lda t_temp+1
eor #$80
cmp g_cmp
bcs text_branch_129
jmp text_next_glyph
text_branch_129:
beq text_branch_130
jmp text_branch_128
text_branch_130:
lda t_clip_l
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_131
jmp text_next_glyph
text_branch_131:
bne text_branch_132
jmp text_next_glyph
text_branch_132:
text_branch_128:
lda t_clip_b+1
eor #$80
sta g_cmp
lda t_y+1
eor #$80
cmp g_cmp
bcs text_branch_134
jmp text_branch_133
text_branch_134:
beq text_branch_135
jmp text_next_glyph
text_branch_135:
lda t_clip_b
sta g_cmp
lda t_y
cmp g_cmp
bcs text_branch_136
jmp text_branch_133
text_branch_136:
jmp text_next_glyph
text_branch_133:
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_sy
sta t_temp
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
asl t_temp
rol t_temp+1
clc
lda t_temp
adc t_y
sta t_temp
lda t_temp+1
adc t_y+1
sta t_temp+1
lda t_clip_t+1
eor #$80
sta g_cmp
lda t_temp+1
eor #$80
cmp g_cmp
bcs text_branch_138
jmp text_next_glyph
text_branch_138:
beq text_branch_139
jmp text_branch_137
text_branch_139:
lda t_clip_t
sta g_cmp
lda t_temp
cmp g_cmp
bcs text_branch_140
jmp text_next_glyph
text_branch_140:
bne text_branch_141
jmp text_next_glyph
text_branch_141:
text_branch_137:
asl t_index
rol t_index+1
asl t_index
rol t_index+1
asl t_index
rol t_index+1
lda t_font
sta t_pointer
lda t_font+1
sta t_pointer+1
clc
lda t_pointer
adc t_index
sta t_pointer
lda t_pointer+1
adc t_index+1
sta t_pointer+1
lda #$00
sta t_row
text_row:
lda #$08
sta g_cmp
lda t_row
cmp g_cmp
bcs text_branch_143
jmp text_branch_142
text_branch_143:
jmp text_next_glyph
text_branch_142:
lda t_pointer
sta BP+46
lda t_pointer+1
sta BP+47
ldy #0
lda (BP+46),y
sta t_raw
lda #$00
sta t_repeat
text_scaled_row:
lda t_y
sta t_py
lda t_y+1
sta t_py+1
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_row
sta t_temp
lda t_temp
sta t_product
lda t_temp+1
sta t_product+1
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_sy
sta t_factor
text_branch_144:
clc
lda t_temp
adc t_product
sta t_temp
lda t_temp+1
adc t_product+1
sta t_temp+1
clc
lda t_factor
adc #$ff
sta t_factor
lda #$00
sta g_cmp
lda t_factor
cmp g_cmp
beq text_branch_146
jmp text_branch_144
text_branch_146:
text_branch_145:
clc
lda t_py
adc t_temp
sta t_py
lda t_py+1
adc t_temp+1
sta t_py+1
lda #$00
sta t_temp
lda #$00
sta t_temp+1
lda t_repeat
sta t_temp
clc
lda t_py
adc t_temp
sta t_py
lda t_py+1
adc t_temp+1
sta t_py+1
lda #$00
sta g_cmp
lda t_mode
cmp g_cmp
beq text_branch_149
jmp text_branch_148
text_branch_149:
jmp text_branch_147
text_branch_148:
lda t_next_x
sta t_left
lda t_next_x+1
sta t_left+1
lda t_next_x
sta t_right
lda t_next_x+1
sta t_right+1
clc
lda t_right
adc t_advance
sta t_right
lda t_right+1
adc t_advance+1
sta t_right+1
lda t_bg
sta t_pen
jsr text_span
text_branch_147:
lda t_raw
sta t_bits
lda #$00
sta t_column
lda #$00
sta t_run
text_bit:
lda #$08
sta g_cmp
lda t_column
cmp g_cmp
bcs text_branch_151
jmp text_branch_150
text_branch_151:
jmp text_row_end
text_branch_150:
lda t_bits
and #$80
bne text_branch_154
jmp text_branch_152
text_branch_154:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_156
jmp text_branch_153
text_branch_156:
text_branch_155:
lda t_column
sta t_start
lda #$01
sta t_run
jmp text_branch_153
text_branch_152:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_158
jmp text_branch_157
text_branch_158:
jmp text_branch_153
text_branch_157:
jsr text_run
lda #$00
sta t_run
text_branch_153:
asl t_bits
clc
lda t_column
adc #$01
sta t_column
jmp text_bit
text_row_end:
lda #$00
sta g_cmp
lda t_run
cmp g_cmp
beq text_branch_161
jmp text_branch_160
text_branch_161:
jmp text_branch_159
text_branch_160:
jsr text_run
text_branch_159:
clc
lda t_repeat
adc #$01
sta t_repeat
lda t_sy
sta g_cmp
lda t_repeat
cmp g_cmp
bcs text_branch_163
jmp text_scaled_row
text_branch_163:
text_branch_162:
clc
lda t_pointer
adc #$01
sta t_pointer
lda t_pointer+1
adc #$00
sta t_pointer+1
clc
lda t_row
adc #$01
sta t_row
jmp text_row
text_next_glyph:
; BEGIN ADVANCE FIX: move by the enlarged glyph cell width.
clc
lda t_next_x
adc t_advance
sta t_next_x
lda t_next_x+1
adc t_advance+1
sta t_next_x+1
; END ADVANCE FIX
clc
lda t_i
adc #$01
sta t_i
lda t_i
sta t_consumed
jmp text_glyph
text_done:
rts
text_run:
lda #$00
sta t_left
lda #$00
sta t_left+1
lda t_start
sta t_left
lda t_left
sta t_product
lda t_left+1
sta t_product+1
lda #$00
sta t_left
lda #$00
sta t_left+1
lda t_sx
sta t_factor
text_branch_164:
clc
lda t_left
adc t_product
sta t_left
lda t_left+1
adc t_product+1
sta t_left+1
clc
lda t_factor
adc #$ff
sta t_factor
lda #$00
sta g_cmp
lda t_factor
cmp g_cmp
beq text_branch_166
jmp text_branch_164
text_branch_166:
text_branch_165:
clc
lda t_left
adc t_next_x
sta t_left
lda t_left+1
adc t_next_x+1
sta t_left+1
lda #$00
sta t_right
lda #$00
sta t_right+1
lda t_column
sta t_right
lda t_right
sta t_product
lda t_right+1
sta t_product+1
lda #$00
sta t_right
lda #$00
sta t_right+1
lda t_sx
sta t_factor
text_branch_167:
clc
lda t_right
adc t_product
sta t_right
lda t_right+1
adc t_product+1
sta t_right+1
clc
lda t_factor
adc #$ff
sta t_factor
lda #$00
sta g_cmp
lda t_factor
cmp g_cmp
beq text_branch_169
jmp text_branch_167
text_branch_169:
text_branch_168:
clc
lda t_right
adc t_next_x
sta t_right
lda t_right+1
adc t_next_x+1
sta t_right+1
lda t_fg
sta t_pen
jmp text_span
text_span:
; Intersect a half-open run with the validated physical-screen scissor.
lda t_clip_t+1
eor #$80
sta g_cmp
lda t_py+1
eor #$80
cmp g_cmp
bcs text_branch_171
jmp text_span_done
text_branch_171:
beq text_branch_172
jmp text_branch_170
text_branch_172:
lda t_clip_t
sta g_cmp
lda t_py
cmp g_cmp
bcs text_branch_173
jmp text_span_done
text_branch_173:
text_branch_170:
lda t_clip_b+1
eor #$80
sta g_cmp
lda t_py+1
eor #$80
cmp g_cmp
bcs text_branch_175
jmp text_branch_174
text_branch_175:
beq text_branch_176
jmp text_span_done
text_branch_176:
lda t_clip_b
sta g_cmp
lda t_py
cmp g_cmp
bcs text_branch_177
jmp text_branch_174
text_branch_177:
jmp text_span_done
text_branch_174:
lda t_clip_l+1
eor #$80
sta g_cmp
lda t_right+1
eor #$80
cmp g_cmp
bcs text_branch_179
jmp text_span_done
text_branch_179:
beq text_branch_180
jmp text_branch_178
text_branch_180:
lda t_clip_l
sta g_cmp
lda t_right
cmp g_cmp
bcs text_branch_181
jmp text_span_done
text_branch_181:
bne text_branch_182
jmp text_span_done
text_branch_182:
text_branch_178:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_left+1
eor #$80
cmp g_cmp
bcs text_branch_184
jmp text_branch_183
text_branch_184:
beq text_branch_185
jmp text_span_done
text_branch_185:
lda t_clip_r
sta g_cmp
lda t_left
cmp g_cmp
bcs text_branch_186
jmp text_branch_183
text_branch_186:
jmp text_span_done
text_branch_183:
lda t_clip_l+1
eor #$80
sta g_cmp
lda t_left+1
eor #$80
cmp g_cmp
bcs text_branch_189
jmp text_branch_188
text_branch_189:
beq text_branch_190
jmp text_branch_187
text_branch_190:
lda t_clip_l
sta g_cmp
lda t_left
cmp g_cmp
bcs text_branch_191
jmp text_branch_188
text_branch_191:
jmp text_branch_187
text_branch_188:
lda t_clip_l
sta t_left
lda t_clip_l+1
sta t_left+1
text_branch_187:
lda t_clip_r+1
eor #$80
sta g_cmp
lda t_right+1
eor #$80
cmp g_cmp
bcs text_branch_194
jmp text_branch_192
text_branch_194:
beq text_branch_195
jmp text_branch_193
text_branch_195:
lda t_clip_r
sta g_cmp
lda t_right
cmp g_cmp
bcs text_branch_196
jmp text_branch_192
text_branch_196:
bne text_branch_197
jmp text_branch_192
text_branch_197:
text_branch_193:
lda t_clip_r
sta t_right
lda t_clip_r+1
sta t_right+1
text_branch_192:
lda t_left
sta bp_x0
lda t_left+1
sta bp_x0+1
lda t_right
sta bp_x1
lda t_right+1
sta bp_x1+1
lda t_py
sta bp_y0
lda t_py+1
sta bp_y0+1
!if TEXT_PACKED {
lda #$00
sta bp_op
lda t_pen
sta bp_pen
} else {
lda #$01
sta bp_op
lda #$00
sta g_cmp
lda t_pen
cmp g_cmp
beq text_branch_200
jmp text_branch_199
text_branch_200:
jmp text_branch_198
text_branch_199:
lda #$00
sta bp_op
text_branch_198:
}
jsr bp_hspan
lda #$00
sta t_work
lda #$00
sta t_work+1
lda #$00
sta t_work+2
lda #$00
sta t_work+3
lda bp_writes
sta t_work
lda bp_writes+1
sta t_work+1
clc
lda t_writes
adc t_work
sta t_writes
lda t_writes+1
adc t_work+1
sta t_writes+1
lda t_writes+2
adc t_work+2
sta t_writes+2
lda t_writes+3
adc t_work+3
sta t_writes+3
clc
lda t_calls
adc #$01
sta t_calls
lda t_calls+1
adc #$00
sta t_calls+1
text_span_done:
rts
text_end: