Skip to content

Change Shared Character Pictures on Master System

Reusable install, replace and select routines for Master System, with real code, native output and explicit video ownership.

A single door picture can appear in many places. Replacing it changes every cell using its number. Changing one cell’s number affects only that instance. Here is reusable code for both jobs.

Native Master System output with two cells showing the same replacement picture
One replacement changes both cells. Later checks select a different picture in only one cell. Original Code198x patterns, captured from this machine's separately executed caller.

Include, initialise and call

Reserve 32 private RAM bytes at P and set P+14=$BE, P+15=$BF. The machine include uses the shared four-plane Mode 4 implementation. Establish the caller-owned mode, palette and sprite-table allocation before calling pattern_init.

Include the maintained component

demo.asm, excerpt
kernel_start:
    include "glyphs.inc"

Init clears the owned patterns and names. It destroys their old contents; call it during your setup, not before every tile change. The complete caller establishes the video state. Then install two original sketches and put the first in two cells:

Initialise, install pictures, then select the first twice

demo.asm, excerpt
    call pattern_init
example_install:
    ld hl,original_patterns
    ld (P),hl
    ld hl,1
    ld (P+2),hl
    ld hl,2
    ld (P+4),hl
    call pattern_install
    ld hl,1
    ld (P+6),hl
    xor a
    ld (P+10),a
    ld hl,168
    ld (P+8),hl
    call pattern_select
    ld hl,169
    ld (P+8),hl
    call pattern_select

To replace both instances, point the source at a new sketch, set first=1 and call pattern_replace. To change only one cell, select code 2 there. Games call these entries directly; the later mailbox and readback buffer are optional verification transport.

The calling contract

Words are little-endian. pattern_install uploads a range, pattern_replace uploads one picture and ignores count, pattern_select changes one name. Status and data-write count reset on every call, including rejection.

Offset Supply or read
0 CPU source address, word
2 First pattern, word
4 Count, word
6 Selected pattern, word
8 Linear cell, word
10 Attribute byte
11 Output: 1 accepted, 0 rejected
12 Output: destination data bytes written, word
14, 15 Data/control ports, preserved
16–31 Private scratch

Valid patterns are 0–447, cells 0–767. Counts are words: 1–448 with first+count <= 448. Index 256 is real, not index zero after truncation. Zero/overflowing ranges, invalid indices/cells and forbidden attributes reject without destination writes.

Keep the source readable, stable, long enough, non-wrapping and separate from the context and destination. These are trusted pointers, not a memory sandbox. The context is non-re-entrant. Inputs 0–10 and ports survive; AF/BC/DE/HL and flags are clobbered. Reserve eight stack bytes including the direct call. Add space for your own calls and interrupts.

Respect the display

Mode 4 stores four bitplanes, not the SG-1000’s one-bit Graphics I pictures. This allocation owns patterns $0000–$37FF and two-byte names $3800–$3DFF. The hardware has 512 slots; the component owns 448 because the upper area holds this caller’s name/sprite allocation. It preserves the guarded neighbouring region and sprite table.

Supply only attribute bits $02 H flip, $04 V flip, $08 palette 1, $10 priority. Index bit 8 comes from the selected pattern word. Bit 0 in the attribute input and bits 5–7 reject. Changing palette and flipping a cell reuse the same pattern bytes. Priority is checked as stored name data; a no-sprite fixture cannot establish sprite-priority interaction.

Own the VDP latch, source bank, interrupt/NMI policy and VRAM allocation throughout each call. No DI/EI is hidden inside the routine. Two control writes select an address before data transfer. The fixed port spacing is conservative; it does not establish a physical-machine frame budget.

Master System CRAM uses one byte per entry. Its 256×192 background is checked inside the native release’s 280×240 framebuffer, including the border. Game Gear shares the pattern format but has different palette storage and a smaller viewport; it has its own verified entry.

Turn a sketch into stored pixels

Each 8×8 pattern has 32 bytes: four plane bytes for row 0, then four for row 1, and so on. Bit 7 is the leftmost dot. Add the selected bits with weights 1, 2, 4 and 8 to choose one of 16 palette entries.

Pattern 256 starts at 256*32 = 8192, or $2000. Its name entry needs low byte $00 and index bit 8 in the high byte. The last owned pattern, 447, starts at $37E0. A cell at (31,23) has index 23*32+31 = 767; its two-byte name begins at $3800+767*2 = $3DFE. These boundaries are exercised by the native checks.

Move a glyph connects these bit weights and cell indices with text drawing. Place a point covers local-to-screen coordinates. These routines place fixed-grid pictures; they do not provide proportional text or arbitrary-size letters.

Read the complete reusable Z80 component

Initialise, install, replace and select

mode4.inc
; SPDX-License-Identifier: MIT
; Sega Mode 4, 448 owned four-plane patterns ($0000..37ff), names $3800..3dff.
; Hardware has 512 slots; the top 64 overlap this allocation's name/sprite data.
; P reserves 32 private RAM bytes: LE source +0, first word +2, count word +4,
; selected pattern word +6, cell word +8, attributes byte +10; outputs status +11,
; data-write count word +12; write-data/control ports +14/+15; scratch +16..31.
; Inputs +0..10 and ports survive. AF/BC/DE/HL/flags clobbered, non-reentrant.
; Own VDP latch for whole call; no DI/EI or banking here. Source must be readable,
; stable, non-wrapping and disjoint from P. Attributes allow bits 1..4 only:
; H flip, V flip, palette 1, priority. Index bit 8 comes from the pattern word.
pattern_init:
    call pattern_reset_result
    ld hl,0
    call pattern_address
    ld de,$3800
    call pattern_zero
    ld hl,$3800
    call pattern_address
    ld de,1536
    call pattern_zero
    jp pattern_accept
pattern_zero:
    xor a
    call pattern_out
    dec de
    ld a,d
    or e
    jr nz,pattern_zero
    ret
pattern_replace:
    ld bc,1
    jr pattern_begin
pattern_install:
    ld bc,(P+4)
pattern_begin:
    call pattern_reset_result
    ld a,b
    or c
    ret z
    ld (P+16),bc
    ld hl,(P+2)
    ld d,b
    ld e,c
    add hl,de
    ret c
    ld de,449
    or a
    sbc hl,de
    ret nc
    ld hl,(P+2)
    add hl,hl
    add hl,hl
    add hl,hl
    add hl,hl
    add hl,hl
    call pattern_address
    ld hl,(P+16)
    add hl,hl
    add hl,hl
    add hl,hl
    add hl,hl
    add hl,hl
    ld d,h
    ld e,l
    ld hl,(P)
pattern_copy:
    ld a,(hl)
    call pattern_out
    inc hl
    dec de
    ld a,d
    or e
    jr nz,pattern_copy
pattern_accept:
    ld a,1
    ld (P+11),a
    ret
pattern_select:
    call pattern_reset_result
    ld a,(P+10)
    and $e1
    ret nz
    ld hl,(P+6)
    ld de,448
    or a
    sbc hl,de
    ret nc
    ld hl,(P+8)
    ld de,768
    or a
    sbc hl,de
    ret nc
    ld hl,(P+8)
    add hl,hl
    ld de,$3800
    add hl,de
    call pattern_address
    ld a,(P+6)
    call pattern_out
    ld a,(P+7)
    ld b,a
    ld a,(P+10)
    or b
    call pattern_out
    jp pattern_accept
pattern_reset_result:
    xor a
    ld (P+11),a
    ld (P+12),a
    ld (P+13),a
    ret
pattern_address:
    ld a,l
    call pattern_control
    ld a,h
    or $40
    call pattern_control
    ret
pattern_control:
    push af
    ld a,(P+15)
    ld c,a
    pop af
    out (c),a
    nop
    nop
    nop
    nop
    ret
pattern_out:
    push af
    ld a,(P+14)
    ld c,a
    pop af
    out (c),a
    nop
    nop
    nop
    nop
    push af
    ld a,(P+12)
    inc a
    ld (P+12),a
    jr nz,pattern_count_ok
    ld a,(P+13)
    inc a
    ld (P+13),a
pattern_count_ok:
    pop af
    ret

Pay for what changes

A single name costs 2 destination bytes; replacing a picture costs 32. Full init writes 15,872 bytes. The assembled component takes 239 code bytes and 32 context bytes; each source picture takes 32 bytes. These exclude the reset caller, video setup and assets.

If many cells use one picture and all should change, a replacement can save writes. If one door should open, change its name. These are storage and data-write counts, not elapsed time. Control writes and fixed port spacing also cost work. Measure inside your actual game before promising a frame rate. Upload queues, caches and alternate allocations are useful further components.

What was verified

40 calls completed per build with Asm198x 0.0.71 and Emu198x 0.39.0. Checks compare complete 16 KiB VRAM, guards, inputs, status and actual destination-write counts. They cover shared replacement, single-cell selection, last slots/cells, maximum uploads, zero/overflow requests and reset. Word counts and indices cross 255/256, and flips/palettes are checked in native frames.

Selected whole native frames match a separate compositor. Completed wrong-slot and wrong-name builds fail both memory and visible-output comparisons. Retained checks independently rederive the expectations; corruption tests reject rehashed wrong memory and pictures.

This is an original reset fixture, not proof of stock-OS loading, interrupt coexistence, hardware timing or tear-free live updates. Palette RGB values describe the pinned emulator release. Code and source pictures are MIT licensed.