Skip to content

Save and Restore Software Sprite Backgrounds

Keep a separate packed background for each Spectrum object, then restore overlaps in reverse order. Reusable rectangle helpers and a complete masked-sprite tape caller.

The existing mask renderer draws a shaped object. These helpers supply the other half of movement: save the background, then restore it at the old address before drawing again. The caller uses two objects that can overlap.

Two masked Spectrum objects over a patterned bitmap band

Original Code198x 48K tape program captured in Emu198x; motion is byte aligned.

Code status: maintained rectangle helpers, independently CPU-executed, plus a ROM-loaded tape caller. Execution record. The shared lifecycle explains why overlapping backgrounds must be restored in reverse order.

Include the helpers beside the renderer

Include background.inc and bitmap-addressing.inc once. save_rect and restore_rect take DE=top-left bitmap byte, HL=packed buffer, B=width in bytes and C=height in pixel rows. Either zero dimension is a no-op. Otherwise the whole rectangle must fit: width≤32, left column+width≤32 and top row+height≤192.

The caller supplies width×height buffer bytes, without address wrap or overlap with the bitmap, code, stack or another live save. AF/BC/DE/HL change; IX/IY survive. HL advances by width×height. Four temporary stack bytes are needed beyond the return address and any interrupt use. There is no static save state; IRQs must preserve active registers and other renderers must leave the owned region alone.

Packed rectangle saves and restores across the Spectrum bitmap

background.inc
; SPDX-License-Identifier: MIT
; B=width bytes (0..32), C=height rows, DE=top-left bitmap byte, HL=buffer.
; Zero width/height: no-op. Otherwise the rectangle must fit the 256x192 bitmap.
; Buffer has B*C readable/writable bytes and does not overlap bitmap/stack/code.
; Include bitmap-addressing.inc once for pixel_down (preserves BC/DE).
; AF/BC/DE/HL change; IX/IY preserved. HL advances by B*C. No static state.
; Four temporary stack bytes beyond the caller return and any interrupt use.
save_rect:
    ld a,b
    or a
    ret z
    ld a,c
    or a
    ret z
save_rect_row:
    push bc
    push de
save_rect_byte:
    ld a,(de)
    ld (hl),a
    inc hl
    inc de
    djnz save_rect_byte
    pop de
    pop bc
    dec c
    ret z
    ex de,hl
    call pixel_down
    ex de,hl
    jr save_rect_row
restore_rect:
    ld a,b
    or a
    ret z
    ld a,c
    or a
    ret z
restore_rect_row:
    push bc
    push de
restore_rect_byte:
    ld a,(hl)
    ld (de),a
    inc hl
    inc de
    djnz restore_rect_byte
    pop de
    pop bc
    dec c
    ret z
    ex de,hl
    call pixel_down
    ex de,hl
    jr restore_rect_row

The existing pixel_down routine handles the interleaved screen layout. It is not called after the last row, so a rectangle ending on row 191 stays within its contract. Attributes are separate and are not saved here.

Keep each buffer with its old address

Z80 assembly
    ld de,(old_b)
    ld hl,under_b
    ld bc,$0210          ; 2 bytes wide, 16 pixel rows
    call restore_rect
    ; Restore old A next, then choose new positions and save/draw A followed by B.

Drawing still uses masked.inc. In this caller SPRITE_W=2 configures that renderer; the rectangle helpers take their own width each call. Mask data consists of interleaved keep-mask/image byte pairs, with image bits zero where the mask preserves scenery.

make creates build/demo.tap; LOAD "" on a 48K Spectrum. The caller owns the bitmap, attributes, private stack and ROM IM1 wait. Two 16×16 objects each keep 32 saved bytes. One moves in eight-pixel steps; reset to leave.

Two objects, separate buffers and reverse restoration

demo.asm
org $8000
SPRITE_W equ 2
start:
    di
    ld sp,$ff00
    ld hl,$4000
    ld de,$4001
    ld bc,6143
    ld (hl),0
    ldir
    ld hl,$5800
    ld de,$5801
    ld bc,767
    ld (hl),71
    ldir
    ld d,64
    ld b,64
paint_line:
    push bc
    ld e,0
    call pixel_addr
    ld b,32
paint_byte:
    ld a,d
    and 7
    jr nz,paint_ready
    ld a,$55
paint_ready:
    ld (hl),a
    inc hl
    djnz paint_byte
    inc d
    pop bc
    djnz paint_line
    call draw_objects
    ld hl,$cafe
    ld (ready),hl
    im 1
    ei
main:
    halt
    ; Reverse the order in which the previous frame was saved/drawn.
    ld de,(old_b)
    ld hl,under_b
    ld bc,$0210
    call restore_rect
    ld de,(old_a)
    ld hl,under_a
    ld bc,$0210
    call restore_rect
    ld a,(tick)
    inc a
    ld (tick),a
    and 3
    jr nz,no_move
    ld a,(column)
    add a,8
    cp 176
    jr c,column_ready
    ld a,64
column_ready:
    ld (column),a
no_move:
    call draw_objects
    ld a,(updates)
    inc a
    ld (updates),a
    jr main
draw_objects:
    ld d,80
    ld a,(column)
    ld e,a
    call pixel_addr
    ld (old_a),hl
    ex de,hl
    ld hl,under_a
    ld bc,$0210
    call save_rect
    ld de,(old_a)
    ld hl,shape_a
    ld c,16
    call draw_sprite
    ld d,88
    ld e,120
    call pixel_addr
    ld (old_b),hl
    ex de,hl
    ld hl,under_b
    ld bc,$0210
    call save_rect
    ld de,(old_b)
    ld hl,shape_b
    ld c,16
    call draw_sprite
    ret
ready: defw 0
updates: defb 0
tick: defb 0
column: defb 64
old_a: defw 0
old_b: defw 0
under_a: defs 32,0
under_b: defs 32,0
include "background.inc"
include "shapes.inc"
include "../masked-sprites/masked.inc"
include "../bitmap-addressing/bitmap-addressing.inc"
end start

Grow the scene deliberately

The independent checks cover seven rectangle cases, including zero sizes, layout boundaries, a full bitmap and the last scanline. Four overlap cases compare the composed and restored bitmap. Broken saving and wrong restoration order must fail. Complete tape startup and progression are separate checks; no tear-free display or whole-game frame budget is claimed.

Saving and restoring add two transfers of width×height bytes per object around drawing. More objects can repeatedly touch the same screen area. Before increasing their size or count, measure the entire update and consider dirty-tile redraw or a clean back buffer.

These routines do not clip, shift or organise object lists. If the background changes, saved bytes can become stale. Restore old objects before scrolling, then capture and draw at the new screen positions. The shared guide connects that ordering to the BBC and Amiga implementations.

When an object can leave the screen, clip its visible rectangle before saving or drawing it. Keep its old clipped address and dimensions for the next restoration.