One object can use one saved cell. A second object needs its own saved bytes, particularly when the two occupy the same cell. These helpers take an explicit buffer pointer while reusing the existing masked drawing routine.
Original Code198x program captured in Emu198x. Objects are 8×8 and byte aligned.
Code status: maintained helpers and a running BBC Model B MOS1.20/BASICII caller. Execution record. See the shared lifecycle for the same restoration problem on Spectrum and Amiga.
Select the cell and its buffer
Include background.inc with the existing masked-sprites/masked.inc. screen_ptr (&70/&71) selects a complete byte-aligned character cell in the ordinary MODE4 bitmap. back_ptr (&74/&75) selects eight caller-owned saved bytes, separate from the bitmap, source, stack, code and other live buffers. pairs_ptr (&72/&73) still selects the interleaved keep-mask/image data for draw_masked_cell.
A/Y/flags change; X survives. IRQ code must preserve the working registers and owned zero-page. Separate buffers support sequential objects; shared pointer bytes still require exclusive use.
Copy a MODE4 cell to and from an explicit buffer
; SPDX-License-Identifier: MIT
; Reuse screen_ptr=$70, pairs_ptr=$72 and draw_masked_cell from masked.inc.
; back_ptr=$74/$75 selects EIGHT caller-owned bytes, one buffer per live object.
; Whole MODE4 character cell, byte aligned. Buffer must not overlap bitmap,
; source, code or stack. Save/restore A/Y/flags change; X survives.
; No static save slot: caller retains both old screen address and buffer pointer.
back_ptr=$74
save_cell_to_buffer:
ldy #7
save_cell_byte:
lda (screen_ptr),y
sta (back_ptr),y
dey
bpl save_cell_byte
rts
restore_cell_from_buffer:
ldy #7
restore_cell_byte:
lda (back_ptr),y
sta (screen_ptr),y
dey
bpl restore_cell_byte
rtsThe helpers remember neither an address nor an object. Keep the old cell address with the corresponding buffer. Restore at that address before selecting the new one.
; Select A's cell and under_a buffer.
jsr save_cell_to_buffer
jsr draw_masked_cell
; Select B and under_b, then save/draw B.
; Next update: restore OLD B, then OLD A; only then choose new positions.The earlier single-object entry keeps its original interface. Its one global save slot is not used by this caller; its masked drawing routine is. The unused single-slot helpers remain in that include, a small code/state cost of keeping the existing component intact.
Run two objects together
make produces build/loader.bas. Load or type it and RUN. The loader selects MODE4, reserves HIMEM=&3000 and installs the original code. MOS IRQ remains active; OSBYTE19 supplies vertical sync. Reset to leave.
The moving object crosses the fixed object’s cell. Save/draw A then B, restore B then A. When they share a cell, B’s buffer contains part of A; restoring in the opposite order brings those pixels back.
Two cells, two save buffers and explicit old addresses
*=$3000
OSBYTE=$fff4
start:
cld
cli
lda #$6c
sta screen_ptr+1
lda #0
sta screen_ptr
ldy #0
paint:
tya
and #7
bne paint_ready
lda #$55
paint_ready:
sta (screen_ptr),y
iny
bne paint
jsr draw_objects
main:
lda #19
jsr OSBYTE
inc tick
lda tick
and #7
bne main
; Reverse draw order; restore at OLD addresses before moving either object.
lda #$80
sta screen_ptr
lda #$6c
sta screen_ptr+1
lda #<under_b
sta back_ptr
lda #>under_b
sta back_ptr+1
jsr restore_cell_from_buffer
lda old_a
sta screen_ptr
lda #<under_a
sta back_ptr
lda #>under_a
sta back_ptr+1
jsr restore_cell_from_buffer
lda column
clc
adc #8
cmp #$c0
bcc next_column
lda #$40
next_column:
sta column
jsr draw_objects
inc updates
jmp main
draw_objects:
lda column
sta old_a
sta screen_ptr
lda #$6c
sta screen_ptr+1
lda #<under_a
sta back_ptr
lda #>under_a
sta back_ptr+1
jsr save_cell_to_buffer
lda #<shape_a
sta pairs_ptr
lda #>shape_a
sta pairs_ptr+1
jsr draw_masked_cell
lda #$80
sta screen_ptr
lda #<under_b
sta back_ptr
lda #>under_b
sta back_ptr+1
jsr save_cell_to_buffer
lda #<shape_b
sta pairs_ptr
lda #>shape_b
sta pairs_ptr+1
jsr draw_masked_cell
rts
column: !byte $40
old_a: !byte 0
tick: !byte 0
updates: !byte 0
under_a: !fill 8,0
under_b: !fill 8,0
shape_a: !byte $e7,$18,$c3,$3c,$81,$7e,$24,$db,0,$ff,$a5,$5a,$db,$24,$ff,0
shape_b: !byte $81,$7e,$bd,$42,$bd,$42,$81,$7e,$e7,$18,$e7,$18,$e7,$18,$c3,$3c
!source "../masked-sprites/masked.inc"
!source "background.inc"Three independent composition/restoration cases cover shared cells, adjacent cells and the bitmap end. The wrong-order version must fail the same byte comparison, and image sources must survive unchanged. A separate displayed caller check observes progression. This is not an original-hardware or deadline qualification.
The component remains 8×8 and byte aligned. It does not clip or translate a CRTC-scrolled screen. Different modes and arbitrary pixel positions need further implementations. A changing background also makes saved bytes stale: remove objects before changing the scenery, or rebuild damaged tiles from the world. The shared lifecycle explains those choices.
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.