Original Code198x program and artwork captured in Emu198x.
Code status: maintained routines and an emulator-executed caller. Execution record.
The original masked renderer expects a complete on-screen rectangle. This wrapper lets an object begin outside the bitmap, selects the surviving source bytes and rows, then draws only that part. Its sample scene shows four clipped corners and three interior objects; two further calls are fully invisible.
Include the routines and give them a context
Include clip.inc, draw.inc and the existing bitmap-addressing helper once. draw_clipped takes IX pointing at 32 caller-owned bytes. Put the signed X position at +0, signed Y at +14, and source pointer at +28. X is measured in eight-pixel byte columns, Y in pixel rows. X = −1 crops one column from the left; X = 32 is fully outside.
The source is fixed at 16×16: 64 interleaved keep-mask/image bytes, four bytes per original row. Keep-mask 1 preserves the old bitmap bit; the corresponding image bit must be zero. Source, context, stack and code must be separate from the bitmap and one another.
The wrapper fills in its two clipping blocks. Within each 14-byte block, destination is +6, source skip +8 and visible count +10. +12..13 is scratch; context +30 counts drawn rows. Positions and source pointer survive, but other workspace fields are outputs or scratch. AF/BC/DE/HL change; IX/IY survive. The wrapper does not touch attributes or set up interrupts.
Crop the source and draw the visible mask/image pairs
; SPDX-License-Identifier: MIT
; draw_clipped: IX -> 32 bytes. +0 signed X in 8-pixel byte columns,
; +14 signed Y in pixel rows, +28 pointer to 16x16 interleaved KEEP-mask/image.
; Other fields are work/output: two clip_axis blocks, +30 row counter.
; 64 source bytes remain caller-owned, separate from bitmap/code/stack/context.
; AF/BC/DE/HL change, IX/IY survive. Attributes untouched. Include clip.inc and
; bitmap-addressing.inc once. Exclusive workspace; no timing/IRQ setup here.
draw_clipped:
ld (ix+2),2
ld (ix+3),0
ld (ix+4),32
ld (ix+5),0
call clip_axis
ld a,(ix+10)
or a
ret z
push ix
pop hl
ld de,14
add hl,de
push ix
push hl
pop ix
ld (ix+2),16
ld (ix+3),0
ld (ix+4),192
ld (ix+5),0
call clip_axis
pop ix
ld a,(ix+24)
or a
ret z
ld (ix+30),a
ld l,(ix+22)
ld h,0
add hl,hl
add hl,hl
ld e,(ix+8)
ld d,0
add hl,de
add hl,de
ld e,(ix+28)
ld d,(ix+29)
add hl,de
push hl
ld d,(ix+20)
ld a,(ix+6)
add a,a
add a,a
add a,a
ld e,a
call pixel_addr
ex de,hl
pop hl
draw_clipped_row:
push hl
push de
ld b,(ix+10)
draw_clipped_byte:
ld c,(hl)
inc hl
ld a,(de)
and c
or (hl)
ld (de),a
inc hl
inc de
djnz draw_clipped_byte
pop de
pop hl
dec (ix+30)
ret z
ld bc,4
add hl,bc
ex de,hl
call pixel_down
ex de,hl
jr draw_clipped_rowThe axis helper is also usable on its own: IX selects a 14-byte block containing signed position at +0, extent at +2 and viewport limit at +4. Extent is 0..256, limit 1..256. All three results are zero when empty. Temporary two-byte pushes preserve comparisons; the drawing loop pushes four row-pointer bytes, in addition to call returns and interrupt needs.
Intersect a signed interval with the viewport
; SPDX-License-Identifier: MIT
; clip_axis: IX -> 14 caller-owned bytes, little-endian words:
; +0 signed position (-32768..32767), +2 extent (0..256), +4 limit (1..256).
; Outputs +6 destination, +8 source skip, +10 visible count; +12 scratch.
; Empty output has all three result words zero. Inputs survive.
; AF/BC/DE/HL change, IX/IY survive. No hardware or interrupt ownership.
clip_axis:
xor a
ld (ix+6),a
ld (ix+7),a
ld (ix+8),a
ld (ix+9),a
ld (ix+10),a
ld (ix+11),a
ld c,(ix+2)
ld b,(ix+3)
ld a,b
or c
ret z
ld l,(ix+0)
ld h,(ix+1)
bit 7,h
jr z,clip_positive
; Unsigned magnitude also handles -32768 without signed negation overflow.
xor a
sub l
ld e,a
sbc a,a
sub h
ld d,a
ld h,b
ld l,c
or a
sbc hl,de
ret c
ret z
ld (ix+8),e
ld (ix+9),d
jr clip_capacity
clip_positive:
ld e,(ix+4)
ld d,(ix+5)
push hl
or a
sbc hl,de
pop hl
ret nc
ld (ix+6),l
ld (ix+7),h
ld h,b
ld l,c
clip_capacity:
; HL remaining source count; DE = viewport capacity at destination.
ld e,(ix+4)
ld d,(ix+5)
ld c,(ix+6)
ld b,(ix+7)
ex de,hl
or a
sbc hl,bc
ex de,hl
push hl
or a
sbc hl,de
pop hl
jr c,clip_store
ex de,hl
clip_store:
ld (ix+10),l
ld (ix+11),h
retFit it into a running program
The complete caller initialises the display, sets context/source values and calls the wrapper for each object. It owns a private stack at $ff00 and uses ROM IM1/HALT afterwards. Build with make ASM198X=/path/to/asm198x and load build/demo.tap on a 48K PAL Spectrum. Reset to leave the caller.
Complete tape caller with cropped corners and invisible objects
org $8000
start:
di
ld sp,$ff00
ld hl,$4000
ld de,$4001
ld bc,6143
ld (hl),$55
ldir
ld hl,$5800
ld de,$5801
ld bc,767
ld (hl),71
ldir
ld hl,-1
ld (context),hl
ld hl,-8
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,31
ld (context),hl
ld hl,-8
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,-1
ld (context),hl
ld hl,184
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,31
ld (context),hl
ld hl,184
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,15
ld (context),hl
ld hl,88
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,0
ld (context),hl
ld hl,80
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,30
ld (context),hl
ld hl,112
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,-2
ld (context),hl
ld hl,32
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,32
ld (context),hl
ld hl,32
ld (context+14),hl
ld hl,shape_a
ld (context+28),hl
ld ix,context
call draw_clipped
ld hl,$cafe
ld (ready),hl
im 1
ei
main:
halt
ld hl,(updates)
inc hl
ld (updates),hl
jr main
ready: defw 0
updates: defw 0
context: defs 32,0
include "clip.inc"
include "draw.inc"
include "shapes.inc"
include "../bitmap-addressing/bitmap-addressing.inc"
end startMove it next
This draws at a position; it does not remove the previous image. Combine it with background restoration, retaining the previous clipped address and dimensions with the saved pixels. The shared clipping entry explains the order and the case where an object becomes fully invisible.
The sides are byte aligned, while top and bottom cropping work by pixel row. Arbitrary-X motion needs shifts or prepared image variants. There is no promised frame budget: the complete scene proves the stated drawing result and a running caller, while display scheduling belongs to the surrounding game.
For individual pixel X positions, select prepared source phases and keep the padded footprint and original source stride explicit.