A wall can appear in twenty places while its picture is stored once. Change that picture and every wall changes. Open one door instead, and you probably want to change just that cell’s number. These routines let you choose.

Include, initialise and call
Reserve 24 private zero-page bytes at G, clear decimal mode and include glyphs.inc. The complete reset caller sets up the video and interrupt policy before the component runs. It supplies its own firmware image; it does not use the manufacturer’s BASIC or operating system.
Include the maintained character component
kernel_start:
!source "glyphs.inc"glyph_init clears the owned pictures and names. That destroys their old contents, so use it during setup. Then install two original sketches and select the first twice:
Initialise, install and select shared pictures
jsr glyph_init
example_install:
lda #<original_patterns
sta G
lda #>original_patterns
sta G+1
lda #33
sta G+2
lda #2
sta G+3
jsr glyph_install
lda #33
sta G+4
lda #<76
sta G+5
lda #>76
sta G+6
jsr glyph_select
inc G+5
jsr glyph_selectYour game calls these entries directly. The later mailbox and debugger readbacks are optional verification transport.
Supply a source, slot and cell
All words are little-endian. glyph_install uploads a range; glyph_replace uploads one shape and ignores count; glyph_select writes one cell’s screen code. Init clears the owned allocations. Inputs 0–6 survive, and status/write count reset even when a request rejects.
| Offset | Supply or read |
|---|---|
| 0–1 | Readable CPU source address |
| 2, 3 | First stored glyph code and nonzero count |
| 4 | Screen code to select |
| 5–6 | Logical cell index |
| 7 | Output: 1 accepted, 0 rejected |
| 8–9 | Output: destination data bytes written |
| 10–23 | Private scratch |
The stored drawable shapes are ASCII 32–127, with counts 1–96 staying within that range. The selectable screen codes are 32–127 and 160–255: the latter are inverse forms of the same standard shapes. Codes 0–31 and 128–159 are attributes, so this picture API rejects them. Logical cells are 0–1035.
Zero counts, overflowing ranges and invalid slots/cells reject before a destination write. Keep the source long enough, readable, non-wrapping, stable and separate from the context/destination. These are trusted pointers, not an arbitrary-memory validator.
The context is non-re-entrant. A/X/Y and flags are clobbered; clear decimal mode and reserve six stack bytes including the direct call. Add space for your own calls and interrupts. The routines do not change interrupt state or banks.
Respect the machine’s characters
Oric TEXT has 40×28 physical cells, each 6×8 dots. This allocation reserves the first three cells of every row for the caller’s paper, ink and mode attributes: 16 selects black paper, 7 white ink, and 26 selects 50 Hz TEXT. Pictures use the remaining 37×28 rectangle. The routine skips the control columns rather than treating them as spare picture cells.
The standard character set starts at $B400. Drawable ASCII 32 begins at $B500; ASCII 127 ends at $B7FF. The low six bits of each row are dots, bit 5 on the left. Codes below 32 control display state instead of choosing a picture. The component preserves their unused font bytes, the alternate-font neighbour, reserved screen attributes and guards.
Bit 7 in a screen code selects inverse video. It does not independently select the alternate font: standard/alternate selection comes from the serial style attribute. This variant uses the standard set, normal height and no flash. HIRES and alternate-font versions need their own allocations and checks.
The complete caller owns the VIA/IRQ policy. The routines do not change video mode, colours, interrupt state or font style. Another renderer writing serial attributes can change the meaning of everything to their right, so coordinate ownership across the whole row.
Count a shape and its place
Logical cell (2,2) has index 2*37+2 = 76, but its physical screen address is $BB80+2*40+3+2 = $BBD5. The logical row width and physical stride differ. Using 37 for both puts later rows in the wrong place.
A six-dot row 100001 is 33, not 129. The two upper bits do not become extra visible dots. The example starts at physical column 5, so local dot (3,5) appears at pixel (5*6+3, 2*8+5) = (33,21). Row-transition checks exercise logical cells 36 and 37.
Move a glyph connects bit weights, stored shapes, codes and cell placement. Place a point explains local and screen coordinates. These routines place fixed-grid pictures; they do not implement proportional text, arbitrary placement or scaled letters.
Replace the shared picture, or one instance
After the example, point G+0 at a new eight-row sketch, set G+2 to 33 and call glyph_replace. Both cells still use that slot, so both change. To change only the first, put code 34 in G+4 and its logical cell index in G+5, then call glyph_select. The second keeps the old code.
Read the complete reusable 6502 component
Initialise, install, replace and select
; SPDX-License-Identifier: MIT
; oric: selected character-RAM allocation, G=24 private zero-page bytes.
; +0 CPU source word; +2 first slot byte; +3 count byte; +4 screen-code byte;
; +5 logical cell word; +7 output accepted; +8 output data-write word; +10..23 scratch.
; Inputs +0..6 survive. A/X/Y/flags clobbered, clear decimal; non-reentrant.
; Trusted readable source must not wrap/overlap; caller owns video/banks/IRQs.
; Allocations, bounds, character-code and colour rules are in the family README.
glyph_init:
jsr glyph_reset_result
lda #<46336
sta G+12
lda #>46336
sta G+13
lda #<768
sta G+16
lda #>768
sta G+17
jsr glyph_zero_range
lda #<$bb83
sta G+12
lda #>$bb83
sta G+13
lda #28
sta G+18
init_row:
lda #37
sta G+19
init_cell:
lda #32
jsr glyph_store
dec G+19
bne init_cell
clc
lda G+12
adc #3
sta G+12
bcc init_next
inc G+13
init_next:
dec G+18
bne init_row
jmp glyph_accept
glyph_zero_range:
jsr glyph_address
zero_next:
lda #0
jsr glyph_store
jsr glyph_decrement
bne zero_next
rts
glyph_replace:
lda #1
bne glyph_begin
glyph_install:
lda G+3
glyph_begin:
sta G+10
jsr glyph_reset_result
lda G+10
beq glyph_reject
lda G+2
cmp #32
bcc glyph_reject
cmp #128
bcs glyph_reject
lda G+10
clc
adc G+2
bcs glyph_reject
cmp #129
bcs glyph_reject
lda G+2
asl
sta G+12
lda #0
rol
sta G+13
asl G+12
rol G+13
asl G+12
rol G+13
lda G+13
clc
adc #>46080
sta G+13
jsr glyph_address
lda G
sta G+14
lda G+1
sta G+15
lda G+10
sta G+16
lda #0
sta G+17
asl G+16
rol G+17
asl G+16
rol G+17
asl G+16
rol G+17
ldy #0
copy_next:
lda (G+14),y
jsr glyph_store
inc G+14
bne source_ok
inc G+15
source_ok:
jsr glyph_decrement
bne copy_next
glyph_accept:
lda #1
sta G+7
glyph_reject:
rts
glyph_select:
jsr glyph_reset_result
lda G+4
and #127
cmp #32
bcc glyph_reject
lda G+6
cmp #>1036
bcc cell_ok
bne glyph_reject
lda G+5
cmp #<1036
bcs glyph_reject
cell_ok:
lda G+5
sta G+20
lda G+6
sta G+21
lda #<$bb83
sta G+12
lda #>$bb83
sta G+13
find_row:
lda G+21
bne subtract_row
lda G+20
cmp #37
bcc found_row
subtract_row:
sec
lda G+20
sbc #37
sta G+20
lda G+21
sbc #0
sta G+21
clc
lda G+12
adc #40
sta G+12
bcc find_row
inc G+13
jmp find_row
found_row:
clc
lda G+12
adc G+20
sta G+12
bcc name_address
inc G+13
name_address:
lda G+4
jsr glyph_store
jmp glyph_accept
glyph_reset_result:
lda #0
sta G+7
sta G+8
sta G+9
rts
glyph_decrement:
lda G+16
bne length_low
dec G+17
length_low:
dec G+16
lda G+16
ora G+17
rts
glyph_address:
rts
glyph_store:
ldy #0
sta (G+12),y
inc G+12
bne destination_ok
inc G+13
destination_ok:
inc G+8
bne count_ok
inc G+9
count_ok:
rtsPay for what changes
One cell selection writes one name byte. Replacing one picture writes eight bytes, regardless of how many cells use it. Full init writes 1,804 bytes. The assembled component uses 331 code bytes, 24 context bytes and eight source bytes per picture, excluding reset/video/diagnostic code.
Choose the operation by what should change. Eight writes can update many shared walls, but one write can open one door. These are storage and destination-write counts, not elapsed time. The Oric row lookup uses repeated subtraction; a row-address table can save arithmetic by spending table memory. Live upload scheduling and physical contention need their own measurements before claiming a frame budget.
What was verified
28 calls completed per build, using Asm198x 0.0.71. Qualification uses the released Oric Atmos Emu198x 0.41.0 binary. It checks the complete standard font, screen, reserved attributes and neighbours, including rejected control codes and the logical row transition. It does not turn the existing HIRES routines into evidence for this TEXT mode; this caller is separately executed.
Three builds on each of the three targets produce 210 executed calls in total. Tests include shared replacement, one-cell selection, inverse forms, full legal uploads, final slots/cells, zero/overflow requests and reset. Completed wrong-slot and wrong-name controls fail both memory and visible-output checks. Selected whole native frames match an independent compositor; retained checks reject missing calls and rehashed wrong observations.
These original reset callers do not establish stock-OS loading, interrupt coexistence, physical timing or tear-free updates. Code and original pictures are MIT licensed.