Skip to content

Change Shared Characters on ColecoVision

Install a picture, use it in many cells, then replace the picture or change one instance. Reusable Z80 routines for ColecoVision Graphics I.

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.

Two screen cells containing code one share one eight-row picture
The glyph holds the picture; each screen cell holds its number. Replacing one and selecting the other do different jobs.

Include, initialise and call

Reserve 24 private RAM bytes at G. Include the maintained MSX Z80 component: this ColecoVision 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

demo.asm, excerpt
kernel_start:
    include "../../../../../msx/patterns/assembly/rendering/custom-glyphs/glyphs.inc"

Set G+10=$BE for data writes and G+11=$BF 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

demo.asm, excerpt
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_select

The 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 $6200, starts its stack at $63F0 and uses an original 8,192-byte bios 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 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.

ColecoVision native Graphics I frame showing the same replacement picture in two adjacent cells
One replacement changes both uses of code 1. The native frame and complete video-memory snapshot are checked independently.
Open the complete reusable Z80 component

Initialise, install, replace and select

glyphs.inc
; 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
    ret

Pay 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 ColecoVision 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.

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.

Two original F characters enlarged to sixteen dots across and down on the native character grid
Each F uses four shared patterns and four screen names. The two copies share their picture bytes, just as ordinary characters do.

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

assets.inc
; 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,0

You 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

caller.inc, excerpt
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

demo.asm
; 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

prepare.inc
; 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_select

Call 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

customise.asm
; SPDX-License-Identifier: MIT
ASSET_OFFSET equ 32
FIRST_SLOT equ 16
FIRST_CELL equ 734
SECOND_CELL equ 330
    include "caller.inc"
An original arrow appears near the centre and exactly at the lower-right corner of the native character screen
The customised program ran independently. Four cells fit at the bottom-right; starting one column or row later would reject the whole placement.

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

stride.inc, excerpt
; BEGIN WRONG
    ld de,1
    add hl,de
The completed faulty program breaks both F characters by placing their bottom tiles in the upper row
Wrong: the names are in valid VRAM, but the four tiles no longer form a 2×2 picture. A successful return did not prove the geometry.

Fixed: move one full 32-cell row

stride.inc, excerpt
; BEGIN FIX
    ld de,32
    add hl,de
The independently executed repaired program restores both complete F characters
Fixed: the names and full native picture match the independent model. The repaired executable is byte-identical to the normal caller.

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

z80.inc
; 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
    ret

This colecovision 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.