Draw a door once and put it in two places. Open that shared picture, and both doors open. Change just one screen cell’s number, and only that door changes. These routines let you choose which job you mean.
Include, initialise and call
Reserve 24 private RAM bytes at G. Include the maintained MSX Z80 component: this SVI-328 port reuses its code unchanged and supplies its own wired ports and video setup. That reuse does not require an MSX BIOS.
Include the reusable glyph component
kernel_start:
include "../../../../../msx/patterns/assembly/rendering/custom-glyphs/glyphs.inc"Set G+10=$80 for data writes and G+11=$81 for control. Establish Graphics I with patterns at $0000, names at $1800 and grouped colours at $2000. The complete caller shows the register writes, sprite terminator and memory reservations. Call glyph_init once: it erases the owned font and name table, leaving colours, sprites and VDP registers alone.
Then point at eight-byte sketches, install them and select the first twice:
Install pictures and use code one in two cells
example_install:
ld hl,original_glyphs
ld (G),hl
ld a,1
ld (G+2),a
ld a,2
ld (G+3),a
call glyph_install
ld a,1
ld (G+4),a
ld hl,66
ld (G+5),hl
call glyph_select
ld hl,67
ld (G+5),hl
call glyph_selectThe source sketches are original MIT-licensed Code198x data. Your game calls these entries directly; the caller’s later mailbox and snapshot buffer are verification tools.
Supply a source, slot and cell
Words are little-endian. glyph_install copies a contiguous range; glyph_replace copies one slot and ignores count. glyph_select writes one cell’s code; glyph_init blanks all 2,048 font bytes and 768 name bytes.
| Offset | Supply or read |
|---|---|
| 0 | Word: readable CPU source pointer |
| 2, 3 | First slot and nonzero glyph count |
| 4 | Code to select |
| 5 | Word: linear cell index |
| 7 | Output: 1 accepted, 0 rejected |
| 8 | Output word: destination data bytes written |
| 10, 11 | Write data and control ports |
Inputs 0–6 and the two port bytes survive. Slots/codes are 0–255, cells 0–767 and install counts 1–255. A range ending exactly at 256 is valid; a zero count, overflowing range or invalid cell rejects before writing. Status and cost reset on rejection. Two calls can install all 256 pictures.
Keep the complete source readable and stable in CPU ROM or RAM, separate from G, with no address wrap or bank change. It is a trusted pointer; the component does not validate arbitrary mappings. Init/select ignore the source. The context is non-re-entrant. Calls clobber AF/BC/DE/HL/flags and need ten stack bytes including nested calls, plus your own callers and interrupts.
Count rows, slots and places
An 8×8 sketch stores eight row bytes, bit 7 on the left. Row 10000001 is 129. Slot 255 starts at 255*8 = 2040, or $07F8; its final row is $07FF. The Crates opening shows the same dots-to-byte arithmetic with another machine’s upload rules.
At column 31, row 23, the cell index is 23*32+31 = 767, at $1AFF. Cell 768 would run into other video data. For local dot (3,5) in cell (2,2), the screen pixel is (2*8+3, 2*8+5) = (19,21). Place a point explores those coordinate choices; move a glyph compares shared character cells with drawing a label at any pixel.
Own the video interface
Patterns occupy VRAM $0000–$07FF and names $1800–$1AFF. They are reached through ports, not through an invented CPU bitmap address. Two control writes choose an address before the data transfer. Coordinate every other VDP user for the whole call; an interrupt changing the latch can send bytes to the wrong place. These routines do not mask interrupts or change banks. DI does not mask a console NMI.
Graphics I shares a foreground/background colour byte among eight slots. Slot 7 uses $2000; slot 8 uses $2001. Replacing bits does not choose colours, and replacing one glyph does not select a new screen code. The complete caller sets white-on-black colours separately and owns its VDP configuration.
This caller reserves G at $8200, starts its stack at $FFF0 and uses an original 32,768-byte rom image. Its diagnostic buffer is only 256 bytes, so the test fits the selected 1 KiB CPU RAM on the two small-RAM consoles. That buffer is not required by a game calling the routine. The read port is $84; the write port is $80. The capture diagnostic uses the read port; the reusable upload uses only the write/control pair.
The component retains conservative fixed port spacing. The native check qualifies this emulator’s selected default clock/video configuration. Physical access timing, accelerated CPUs, tear-free live animation and VBlank transfer budgets need their own measurements. A running-game upload queue is a useful next component.
Replace a shared picture, or change one instance
Point G+0 at a new sketch, put 1 in G+2 and call glyph_replace: both cells containing code 1 show it. Put 2 in G+4, the first cell’s index in G+5, and call glyph_select: the other cell keeps code 1.

Open the complete reusable Z80 component
Initialise, install, replace and select
; SPDX-License-Identifier: MIT
; MSX1 Graphics I: patterns $0000..07ff, name table $1800..1aff.
; G reserves 24 RAM bytes: +0 source LE word,+2 first,+3 count (1..255),
; +4 code,+5 cell LE word(0..767),+7 status,+8 LE word bytes written.
; G+10 data write port and +11 control port must be set from BIOS before init.
; Inputs +0..6,+10..11 survive; AF/BC/DE/HL/flags destroyed; non-reentrant.
; Caller owns VDP latch exclusively for entire call, IRQs disabled or coordinated.
; Fixed conservative port delay, works at standard MSX1 clock. No DI/EI here.
; Init blanks patterns and names, not colours/sprites/registers; see caller.
glyph_init:
xor a
ld (G+7),a
ld hl,0
call glyph_address
ld de,2048
call glyph_zero
ld hl,$1800
call glyph_address
ld de,768
call glyph_zero
ld hl,2816
ld (G+8),hl
ld a,1
ld (G+7),a
ret
glyph_zero:
xor a
call glyph_out
dec de
ld a,d
or e
jr nz,glyph_zero
ret
glyph_replace:
ld a,1
jr glyph_begin
glyph_install:
ld a,(G+3)
glyph_begin:
ld b,a
xor a
ld (G+7),a
ld (G+8),a
ld (G+9),a
ld a,b
or a
ret z
ld e,a
ld d,0
ld a,(G+2)
ld l,a
ld h,0
add hl,de
ld de,257
or a
sbc hl,de
ret nc
ld a,(G+2)
ld l,a
ld h,0
add hl,hl
add hl,hl
add hl,hl
push bc
call glyph_address
pop bc
ld l,b
ld h,0
add hl,hl
add hl,hl
add hl,hl
ld (G+8),hl
ld d,h
ld e,l
ld hl,(G)
glyph_copy:
ld a,(hl)
call glyph_out
inc hl
dec de
ld a,d
or e
jr nz,glyph_copy
ld a,1
ld (G+7),a
ret
glyph_select:
xor a
ld (G+7),a
ld (G+8),a
ld (G+9),a
ld hl,(G+5)
ld de,768
or a
sbc hl,de
ret nc
ld hl,(G+5)
ld de,$1800
add hl,de
call glyph_address
ld a,(G+4)
call glyph_out
ld a,1
ld (G+7),a
ld (G+8),a
ret
glyph_address:
ld a,(G+11)
ld c,a
ld a,l
call glyph_port
ld a,h
or $40
call glyph_port
ret
glyph_out:
push af
ld a,(G+10)
ld c,a
pop af
glyph_port:
out (c),a
nop
nop
nop
nop
retPay for the change you need
Changing one cell writes 1 name byte plus 2 control bytes. Replacing a glyph writes 8 pattern bytes plus 2 control bytes. A 255-glyph upload writes 2,040 data bytes; init writes 2,816. G+8 counts data writes only. The control-byte counts follow the listing’s address setup. These are amounts of work, not measured frame times.
The unchanged routine takes 205 code bytes, plus the 24-byte context. Reserve the 2 KiB font, 768-byte name table and eight source bytes per sketch separately. Reset setup, diagnostic machinery and ROM padding are excluded. Replacing one shared wall can save editing many cells; opening one door often calls for selecting a different code in one cell.
For larger screen changes, use native map regions. Bitmap text solves a different job: drawing labels away from character-grid positions. Graphics II, pattern caches and four-plane Sega Mode 4 need their own contracts.
What was verified
24 calls completed on this SVI-328 configuration with Asm198x 0.0.71 and Emu198x 0.39.0. After each, an independent model checks all 16 KiB VRAM, public inputs/ports, status, data-write count, CPU guards and unchanged RAM/ROM sources. Cases cover shared replacement, single-cell selection, first/last slots, largest transfers, rejected ranges/cells, reset and reuse after reset. Six complete native frames per build match a separate glyph compositor, including borders.
Two completed accepted faulty builds, wrong-slot and wrong-name, fail both memory and visible-output checks on this machine. Retained checks also reject altered observations with recomputed hashes. This original reset fixture proves the named routine/configuration, not stock-OS loading, physical hardware timing or another display mode.
- Complete caller and execution evidence
- Unchanged reusable include
- Full contract, costs and primary references
- Compare writable character systems
Make a larger character from four tiles
A score digit or a title can grow beyond one cell without a bitmap renderer. Prepare four ordinary 8×8 tiles, put them in four consecutive slots, and select them as a 2×2 character. The VDP still draws its usual tiles; your picture now occupies 16×16 stored dots.

Prepare the picture once
The maintained host-side converter doubles every source dot into a 2×2 block. Run python3 cross-platform/patterns/rendering/character-quads/prepare.py source.bin large.bin with exactly eight source bytes. The result is 32 bytes: top-left, top-right, bottom-left, bottom-right, eight rows each. The supplied original F and arrow are also checked in as assembler data:
Four row-major tiles per large character
; SPDX-License-Identifier: MIT
; Original source rows; prepared TL, TR, BL, BR tiles, eight rows each.
large_f:
db 63,63,48,48,48,48,63,63
db 252,252,0,0,0,0,240,240
db 48,48,48,48,48,48,0,0
db 0,0,0,0,0,0,0,0
large_arrow:
db 3,3,3,3,255,255,255,255
db 0,0,192,192,240,240,252,252
db 255,255,3,3,3,3,0,0
db 240,240,192,192,0,0,0,0You can design a full 16×16 picture instead of enlarging an 8×8 source. Keep that tile order and length. Passing eight bytes to an upload declaring four patterns would read 24 bytes beyond your asset: the upload API trusts its source pointer. Keep all 32 bytes readable, non-wrapping and stable in the selected CPU bank.
Include, install and place
Keep the existing 24-byte G context and allocate eight more private bytes at Q, separate from G, assets, code and stack. Set the same wired G+10/G+11 ports and Graphics I mode described above; call glyph_init once after video setup. Then include the placement routine alongside the unchanged upload component:
Include upload and four-cell placement
kernel_start:
include "../../../../../msx/patterns/assembly/rendering/custom-glyphs/glyphs.inc"
kernel_end:
quad_start:
include "../../../../../cross-platform/patterns/rendering/character-quads/z80.inc"The normal caller chooses the F at asset offset 0, slot 8 and origins 330 and 334. Those origins are column 10 and 14 of row 10, because cell = row*32+column:
Choose the asset, four slots and two places
; SPDX-License-Identifier: MIT
ASSET_OFFSET equ 0
FIRST_SLOT equ 8
FIRST_CELL equ 330
SECOND_CELL equ 334
include "caller.inc"Install once, then place two instances
; SPDX-License-Identifier: MIT
; Install four prepared tiles once, then select them at two independent origins.
ld hl,large_f+ASSET_OFFSET
ld (G),hl
ld a,FIRST_SLOT
ld (G+2),a
ld a,4
ld (G+3),a
call glyph_install
ld a,FIRST_SLOT
ld (Q),a
ld hl,FIRST_CELL
ld (Q+1),hl
call quad_select
ld hl,SECOND_CELL
ld (Q+1),hl
call quad_selectCall quad_select directly in your own project. Set Q+0 to the first slot and the little-endian word Q+1 to the top-left cell. These three input bytes survive. Q+3 returns 1 for an accepted call or 0 for rejection; Q+4 returns four name data writes or zero. Q+5–7 are private. All G bytes survive placement, including the upload results and ports.
AF/BC/DE/HL/flags are clobbered. Reserve twelve stack bytes including the call, plus your caller/interrupt needs. The routine is non-re-entrant, changes no interrupt-enable or bank state, and needs the same exclusive VDP latch ownership as the upload component. A console NMI is not masked by DI. The reset fixture’s mailbox and 256-byte readback buffer are verification tools, not requirements for a game.
Accepted origins have column 0–30 and row 0–22. The last is 22*32+30 = 734, whose lower-right cell is 767. Column 31, row 23 and word indices outside the screen reject before any VDP access. First slot 252 is valid; 253–255 would run past slot 255 and reject. Both outputs reset even after a previous successful call. There is no clipping or automatic allocation. Install the four slots first; erase an old instance separately when moving one.
Make it your own
The customised caller changes the asset offset to 32 for the arrow, chooses slot 16 and places its first copy at cell 734, the last legal origin. Its second copy remains near the centre. Scratch and code size stay the same:
Change the shape, allocation and position
; SPDX-License-Identifier: MIT
ASSET_OFFSET equ 32
FIRST_SLOT equ 16
FIRST_CELL equ 734
SECOND_CELL equ 330
include "caller.inc"
Graphics I colours still belong to groups of eight slots. Slots 8–11 share one colour byte; slots 5–8 cross two groups. A four-slot-aligned start keeps the four tiles in one group, but that group can also contain other pictures. Set its colours separately. This white-on-black fixture does not verify colour animation. Replacing a shared tile changes every instance using that slot, including unrelated pictures if their allocations overlap.
Common mistake: one cell is not one row
Deliberately wrong: this changes the bottom-row step from 32 to 1. The call completes and reports four data writes, but its lower tiles land beside the upper ones. One overwrites the top-right name.
Wrong: move one cell to find the lower row
; BEGIN WRONG
ld de,1
add hl,de
Fixed: move one full 32-cell row
; BEGIN FIX
ld de,32
add hl,de
Count tiles, names and work
Tile (tx,ty) uses code first + ty*2+tx; its screen cell is origin + ty*32+tx. Inside the 16×16 result, output dot (x,y) comes from source (x div 2,y div 2). Translation and character grids connects those local and screen coordinates; integer division explains choosing the containing source cell. These are stored dots, not a promise of physically square pixels on every TV.
A prepared character needs 32 asset bytes and four pattern slots, four times its eight-byte source. Upload once for 32 pattern data bytes plus two control bytes. Each placement writes four name bytes plus four control bytes. Q+4 counts only name data; none of these counts is elapsed cycles or a frame budget. The placement adds 92 code bytes and eight RAM bytes to the existing 205-byte upload component and its 24-byte context. Video setup, stack and diagnostics are additional. There is no runtime pixel resampling.
At most 64 disjoint four-tile characters fit before you reserve blank, UI and game tiles. A string API, non-consecutive tile sharing, banks, a VBlank queue and larger grid shapes need separately verified contracts. Bitmap text offers arbitrary placement and other sizing choices at a different draw/storage cost. Keep ordinary one-cell text when it does the job cheaply.
Reusable bounded four-tile placement
; SPDX-License-Identifier: MIT
; Graphics I 32x24 names at $1800, four row-major codes already installed.
; Q owns 8 writable bytes, disjoint from 24-byte G, code, stack and assets.
; Q+0 first code (0..252), Q+1 cell LE word; these inputs survive.
; Q+3 status 1 accepted/0 rejected, Q+4 data writes 4/0, Q+5..7 private.
; G+10/+11 are the data/control ports; all G bytes survive.
; AF/BC/DE/HL/flags clobbered. No DI/EI. Reserve 12 stack bytes including call.
; Own the VDP latch/interrupt policy throughout; see unchanged glyphs.inc.
quad_select:
xor a
ld (Q+3),a
ld (Q+4),a
ld a,(Q)
cp 253
ret nc
ld (Q+5),a
ld hl,(Q+1)
ld de,736
or a
sbc hl,de
ret nc
ld hl,(Q+1)
ld a,l
and 31
cp 31
ret z
ld (Q+6),hl
ld de,$1800
add hl,de
call quad_row
ld a,(Q+5)
add a,2
ld (Q+5),a
ld hl,(Q+6)
ld de,32
add hl,de
ld de,$1800
add hl,de
call quad_row
ld a,4
ld (Q+4),a
ld a,1
ld (Q+3),a
ret
quad_row:
call glyph_address
ld a,(Q+5)
call glyph_out
inc a
call glyph_out
retThis spectravideo-svi-328 reset configuration completed 893 native observations with Asm198x 0.0.71 and Emu198x 0.39.0: every screen origin, slot boundaries, rejected edges, the customised caller and completed wrong/fixed builds. Every call checks all 768 name bytes, inputs, status/count, unchanged G and CPU guards; selected whole 16 KiB VRAM snapshots and native frames check other resources and visible output. Original replacement firmware only: no stock OS, physical-device timing, interrupt coexistence or tear-free live-upload claim. Shared code does not qualify another machine automatically.