Skip to content

Draw a Native Region on Mattel Aquarius

Reusable Mattel Aquarius code for a room, changed tile or strip of scenery, with its display layout and ownership explicit.

A door opens, and one map cell changes. This routine redraws its picture without rebuilding the whole screen. The same call can paint a room or the strip of scenery revealed by a camera: choose a source rectangle and a destination, then let the machine’s renderer do the copying.

Mattel Aquarius native diagnostic showing a small patterned region, a single-cell redraw and a separate corner tile
Original reset diagnostic in Emu198x 0.39.0: unexpanded character and colour screen. Complete display memory and every native pixel are checked separately.

The capture uses an original diagnostic font ROM, supplied by the test fixture for repeatable glyph checks. It is not the stock font and does not show software-redefinable characters on this machine. Set your game’s descriptors to codes in its installed ROM.

A picture has two screen bytes

Aquarius stores character codes at $3000–$33FF and matching colour bytes at $3400–$37FF. The active 40×25 display uses 1,000 entries from each; the remaining 24 bytes in each allocation stay unchanged.

glyph_codes maps each logical ID to a code in the fixed character ROM. The example uses the deliberately non-identity mapping 0,3,1,2. tile_colours supplies $F0,$F0,$10,$80: the high nibble selects foreground and the low nibble selects background.

The surround repeats cell zero and its colour attribute. Changing (0,0) can change the border as well. BASIC normally uses rows 1–24; direct hardware drawing can use all 25. A game sharing the editor needs an explicit rule for that first row, clearing and scrolling.

Reserve your workspace

Define NR, MAP, MAP_W, MAP_H, TILE_COUNT, SCREEN, COLOUR, SCREEN_COLS=40, SCREEN_ROWS=25, plus the glyph and colour descriptor tables. Include character-regions/z80.inc once. The context needs 32 writable bytes; no scratch initialisation call is needed.

The example source map is twelve bytes wide and eight high. Context, results, source map, guards and stack all fit in the unexpanded $3800–$3FFF RAM. Map dimensions and tile count are nonzero bytes; every allocation must fit mapped memory without wrapping or overlapping the overwritten screen.

AF/BC/DE/HL and flags are clobbered. Six stack bytes cover the direct CALL, paint helper and saved pointer, beyond your own caller/interrupt use. A 5×3 patch writes fifteen character bytes and fifteen colour bytes, plus preflight/address work.

Choose glyphs which exist in the installed ROM. The routine cannot redefine this fixed font in software. Combining available characters into larger scenery or objects gives you more options; a custom bitmap is not a hidden feature of the screen-code copy.

Draw the rectangle

The constants belong to the original caller’s working memory plan. Choose allocations which fit your own project, keeping setup and drawing separate.

Caller-owned memory and display constants

demo.asm, excerpt
NR EQU 15360
RESULT EQU 15424
MAP EQU 15600
MAP_W EQU 12
MAP_H EQU 8
TILE_COUNT EQU 4
SCREEN_COLS EQU 40
SCREEN_ROWS EQU 25
SCREEN EQU $3000
COLOUR EQU $3400

Translate logical IDs into native screen codes and colours

demo.asm, excerpt
glyph_codes:
DB 0,3,1,2
tile_colours:
DB $f0,$f0,$10,$80

Supply six unsigned bytes at NR+0..5: source column, source row, destination column, destination row, width and height. Call region_draw; NR+6 is one for completion or zero for rejection. The six request bytes survive.

Draw a 5×3 patch from map cell (2,1) at display cell (4,3)

demo.asm, excerpt
case_0:
    ld a,2
    ld (NR+0),a
    ld a,1
    ld (NR+1),a
    ld a,4
    ld (NR+2),a
    ld a,3
    ld (NR+3),a
    ld a,5
    ld (NR+4),a
    ld a,3
    ld (NR+5),a
    call region_draw
    ld a,(NR+6)
    or 128
    ld (RESULT+1),a

The result store after the call belongs to the diagnostic. In a game, read the completion flag and carry on with your own logic. The routine rejects zero sizes, invalid starts, source/destination overruns and IDs outside the configured table. Every selected ID is checked before painting begins, so a bad tile cannot leave half a patch behind. This variant rejects rather than clips.

Keep the routine

Checked character and colour region drawing

z80.inc
; Checked character/colour region. See README for symbols, state and ownership.
region_draw:
    xor a
    ld (NR+6),a
    ld a,(NR+4)
    or a
    ret z
    ld a,(NR+0)
    cp MAP_W
    ret nc
    ld b,a
    ld a,MAP_W
    sub b
    ld b,a
    ld a,(NR+4)
    cp b
    jr c,bounds_fit_0
    ret nz
bounds_fit_0:
    ld a,(NR+5)
    or a
    ret z
    ld a,(NR+1)
    cp MAP_H
    ret nc
    ld b,a
    ld a,MAP_H
    sub b
    ld b,a
    ld a,(NR+5)
    cp b
    jr c,bounds_fit_1
    ret nz
bounds_fit_1:
    ld a,(NR+4)
    or a
    ret z
    ld a,(NR+2)
    cp SCREEN_COLS
    ret nc
    ld b,a
    ld a,SCREEN_COLS
    sub b
    ld b,a
    ld a,(NR+4)
    cp b
    jr c,bounds_fit_2
    ret nz
bounds_fit_2:
    ld a,(NR+5)
    or a
    ret z
    ld a,(NR+3)
    cp SCREEN_ROWS
    ret nc
    ld b,a
    ld a,SCREEN_ROWS
    sub b
    ld b,a
    ld a,(NR+5)
    cp b
    jr c,bounds_fit_3
    ret nz
bounds_fit_3:
    xor a
    ld (NR+29),a
region_pass:
    xor a
    ld (NR+16),a
region_row:
    xor a
    ld (NR+17),a
region_cell:
    ld hl,MAP
    ld a,(NR+1)
    ld b,a
    ld a,(NR+16)
    add a,b
    or a
    jr z,source_column
    ld b,a
    ld de,MAP_W
source_row:
    add hl,de
    djnz source_row
source_column:
    ld a,(NR)
    ld b,a
    ld a,(NR+17)
    add a,b
    ld e,a
    ld d,0
    add hl,de
    ld a,(hl)
    ld (NR+19),a
    cp TILE_COUNT
    ret nc
    ld a,(NR+29)
    or a
    call nz,region_paint
    ld hl,NR+17
    inc (hl)
    ld a,(NR+4)
    cp (hl)
    jp nz,region_cell
    ld hl,NR+16
    inc (hl)
    ld a,(NR+5)
    cp (hl)
    jp nz,region_row
    ld a,(NR+29)
    or a
    jr nz,region_success
    inc a
    ld (NR+29),a
    jp region_pass
region_success:
    ld a,1
    ld (NR+6),a
    ret
region_paint:
    ld hl,SCREEN
    ld a,(NR+3)
    ld b,a
    ld a,(NR+16)
    add a,b
    or a
    jr z,char_column
    ld b,a
    ld de,SCREEN_COLS
char_row:
    add hl,de
    djnz char_row
char_column:
    ld a,(NR+2)
    ld b,a
    ld a,(NR+17)
    add a,b
    ld e,a
    ld d,0
    add hl,de
    ld a,(NR+19)
    ld e,a
    push hl
    ld hl,glyph_codes
    add hl,de
    ld a,(hl)
    pop hl
    ld (hl),a
    ld bc,COLOUR-SCREEN
    add hl,bc
    push hl
    ld hl,tile_colours
    add hl,de
    ld a,(hl)
    pop hl
    ld (hl),a
    ret

The context is private and non-re-entrant. Keep the request, map, descriptors or picture data, and display configuration stable throughout the call. Your caller owns video setup, allocations and interrupt policy; the routine does not disable interrupts or negotiate with an OS. Interrupt handlers must preserve the used registers and must not change those resources halfway through drawing.

What was checked

All 18 calls completed: three accepted draws and fifteen map/bounds/ID rejections. Each rejected request also ran in isolation after the same three accepted draws, preserving complete destination allocations and both mapped guards. The source selection crosses a CPU page boundary.

The observer compares every destination byte, unused allocation tails and stored colour bits where present, with an independent model. It compares every native capture pixel with a separate composition, including borders and scaling. Completed wrong-layout and unsafe-destination controls fail the same memory checks; wrong-layout pictures also fail the pixel check.

These results qualify the selected Emu198x 0.39.0 configuration with Asm198x 0.0.71 and the original reset fixture. They do not establish stock-OS loading, original-hardware timing, interrupt coexistence, tear-free updates or throughput.