Skip to content

Masked Sprites with Background Restoration

Draw an 8×8 software object over BBC Micro MODE4 scenery, restore what it covered, then move it. A small reusable primitive with a complete caller.

An object moves across a patterned strip without leaving its previous image behind. There is no sprite engine doing that job: the CPU saves the eight background bytes, combines the image with them, and restores them before moving.

A small masked shape over a patterned band in BBC Micro MODE4

Original Code198x program captured in Emu198x. This is an eight-pixel primitive; larger and pixel-aligned objects are onward techniques.

Code status: maintained 6502 routine, executed through a BBC BASIC loader on the Model B configuration. Execution record. The sprite family connects it to Spectrum masking, C64 hardware sprites and multiplexing, NES OAM and Amiga bobs.

The useful small case

MODE4 is a 320×256 two-colour bitmap at &5800–&7FFF. Its eight scanlines within one character cell are consecutive bytes. This routine draws one 8×8 object at a byte-aligned character-cell position; it does not pretend that arbitrary pixel positions have the same addressing.

Include masked.inc once. Set screen_ptr (&70/&71) to a complete cell in that bitmap, and pairs_ptr (&72/&73) to 16 readable mask/image bytes. Keep source data separate from the destination, saved state and stack. Clear decimal mode. A/Y/flags change; X survives. IRQ code must preserve the owned zero-page and working registers.

Assembly
    ; Set screen_ptr and pairs_ptr first.
    jsr save_background
    jsr draw_masked_cell
    ; Before moving the object:
    jsr restore_background
    ; Set the NEW screen_ptr, save it, and draw there.

Save, mask and restore one BBC character cell

masked.inc
; SPDX-License-Identifier: MIT
; BBC MODE4, byte-aligned 8x8 object. A character cell is eight adjacent bytes.
; Caller sets screen_ptr=$70/$71 to one cell wholly in $5800..$7fff.
; pairs_ptr=$72/$73 addresses 16 interleaved mask/image bytes.
; saved_background[8] and saved_screen[2] belong to ONE live object.
; save -> draw -> restore before moving. Do not overlap independently saved
; objects: restore all in reverse order, then capture/draw the next frame.
; Mask 1 keeps background; image must be 0 there. No clipping/attributes/shift.
; A/Y/flags change, X preserved. Decimal mode clear. MOS IRQ must preserve ZP.
save_background:
    lda screen_ptr
    sta saved_screen
    lda screen_ptr+1
    sta saved_screen+1
    ldy #7
save_background_row:
    lda (screen_ptr),y
    sta saved_background,y
    dey
    bpl save_background_row
    rts
restore_background:
    lda saved_screen
    sta screen_ptr
    lda saved_screen+1
    sta screen_ptr+1
    ldy #7
restore_background_row:
    lda saved_background,y
    sta (screen_ptr),y
    dey
    bpl restore_background_row
    rts
draw_masked_cell:
    ldy #0
masked_cell_row:
    tya
    pha
    asl
    tay
    lda (pairs_ptr),y
    sta mask_byte
    iny
    lda (pairs_ptr),y
    sta image_byte
    pla
    tay
    lda (screen_ptr),y
    and mask_byte
    ora image_byte
    sta (screen_ptr),y
    iny
    cpy #8
    bne masked_cell_row
    rts
screen_ptr=$70
pairs_ptr=$72
saved_screen: !word 0
saved_background: !fill 8,0
mask_byte: !byte 0
image_byte: !byte 0

A mask bit of one preserves background. A mask bit of zero clears that position before the image is ORed into it. Image bits must be zero wherever the mask preserves scenery. The component owns one saved address, eight saved bytes and two scratch bytes; one save slot means one live saved object.

restore_background also restores the saved address into screen_ptr. Choose the next address after restoring, rather than accidentally restoring at the new position.

Build and run the example

make creates build/loader.bas. Load or type it into BBC BASIC and RUN. It selects MODE4, reserves HIMEM=&3000, installs the original machine code and moves the object one cell per eight vertical syncs. MOS IRQ remains active; OSBYTE19 provides the sync. Reset to leave.

Complete patterned-strip caller

demo.asm
; MODE4 is selected by the BASIC loader. MOS IRQ remains active.
*=$3000
OSBYTE=$fff4
start:
    cld
    cli
    lda #<shape
    sta pairs_ptr
    lda #>shape
    sta pairs_ptr+1
    lda #$6c
    sta screen_ptr+1
    lda #0
    sta screen_ptr
    ldy #0
paint:
    tya
    and #7
    bne paint_pixel
    lda #$aa
paint_pixel:
    sta (screen_ptr),y
    iny
    bne paint
    lda #$a0
    sta screen_ptr
    jsr save_background
    jsr draw_masked_cell
main:
    lda #19
    jsr OSBYTE
    ; Move one character cell every eight vertical syncs.
    inc tick
    lda tick
    and #7
    bne main
    jsr restore_background
    lda screen_ptr
    clc
    adc #8
    cmp #$f8
    bcc next_cell
    lda #0
next_cell:
    sta screen_ptr
    jsr save_background
    jsr draw_masked_cell
    inc updates
    jmp main
tick: !byte 0
updates: !byte 0
shape: !byte $e7,$18,$c3,$3c,$81,$7e,$24,$db,$00,$ff,$a5,$5a,$db,$24,$ff,0
!source "masked.inc"

Grow it without losing the background

The checks draw and restore at five cell addresses, including the bitmap’s first and last cells. They compare the rendered and restored bytes independently, check that the source survives and reject a broken mask operation. The running caller is checked separately; this is not an original-hardware or frame-budget certification.

For several saved objects, restore them in reverse drawing order before saving and drawing the next frame. If scenery changed, old saved pixels can undo that change; redraw affected tiles from the world instead. The shared sprite guide explains the choice.

Further versions can add larger rectangles, clipping, pre-shifted images and arbitrary Y positions. CRTC start-address scrolling also changes how logical positions map into physical screen RAM, including wrap. This primitive requires the ordinary MODE4 mapping; a scrolling renderer must supply its own correct mapping.

The source manual is the BBC Microcomputer Advanced User Guide, the chapter on the screen display and its MODE4 memory organisation. The routine itself uses no MOS drawing calls; the demo uses MOS only for synchronisation.

Explore the cross-platform sprite family for software drawing, hardware limits, restoration and overload policies.

For a complete caller that removes old objects and handles overlapping saved backgrounds, follow the software-object lifecycle.