Skip to content

Place Spectrum Sprites at Pixel Positions

Select one of eight prepared masks, keep signed pixel positions correct at the left edge, and draw the visible three-byte footprint.

Original spectrum sprite gallery with prepared pixel phases on a sparse background grid, including cropped screen edges

Original Code198x program and artwork captured in Emu198x.

Code status: maintained routines and an emulator-executed phase gallery. Execution record.

The aligned renderer can draw a byte column. This wrapper lets the object keep a pixel X position and selects the prepared source that belongs there. The gallery exercises every phase in the interior and against each screen edge.

Supply pixel position and a bank

Include phase.inc, this folder’s draw.inc, the existing clipped-sprites/clip.inc and bitmap-addressing helper once. Include a generated phase bank once. draw_pixel_sprite takes IX pointing at 32 exclusive caller bytes: signed pixel X at +0, signed Y at +14, and an eight-entry phase pointer table at +28.

X and table fields are consumed: they become the aligned byte X and chosen source pointer. Reload all three inputs for each call, retaining logical game coordinates elsewhere. The complete caller below shows those assignments before every draw. This also lets several objects use the routine with different generated banks.

AF/BC/DE/HL change; IX/IY survive. Keep table, source, context, code and stack separate from the bitmap and one another. The include changes neither attributes nor interrupt setup. Exclude concurrent users of the working state.

Split signed pixel X and select a prepared source

phase.inc
; SPDX-License-Identifier: MIT
; draw_pixel_sprite: IX -> 32 bytes, +0 signed PIXEL X, +14 signed Y,
; +28 pointer to an 8-entry phase pointer table (generated KEEP/image rows).
; Consumes X/table fields: they become byte X/source pointer. Reload each call.
; AF/BC/DE/HL change, IX/IY survive. Ownership: same as phase_draw_clipped.
; Include qualified clip_axis, bitmap addressing and this folder's draw.inc once.
draw_pixel_sprite:
    ld l,(ix+0)
    ld h,(ix+1)
    ld a,l
    and 7
    ; Arithmetic division rounds toward -infinity, including negative X.
    sra h
    rr l
    sra h
    rr l
    sra h
    rr l
    ld (ix+0),l
    ld (ix+1),h
    ld l,(ix+28)
    ld h,(ix+29)
    ld e,a
    ld d,0
    add hl,de
    add hl,de
    ld e,(hl)
    inc hl
    ld d,(hl)
    ld (ix+28),e
    ld (ix+29),d
    jp phase_draw_clipped

The low three bits choose a phase. Arithmetic shifts turn −1 into byte column −1, rather than zero or a large unsigned address. The shared article shows why that distinction matters.

Draw the selected padded image

The prepared source spans 24×16 pixels: three mask/image pairs per row, ninety-six bytes per phase. Transparent padding makes that fixed shape useful at every phase, including zero. The bounded copy loop clips the padded rectangle, advances through the correct source samples and keeps the original six-byte row stride.

Copy clipped prepared rows without losing source stride

draw.inc
; SPDX-License-Identifier: MIT
; phase_draw_clipped: IX -> 32 bytes. +0 signed X in 8-pixel byte columns,
; +14 signed Y in pixel rows, +28 pointer to 24x16 interleaved KEEP-mask/image.
; Other fields are work/output: two clip_axis blocks, +30 row counter.
; 96 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.
phase_draw_clipped:
    ld (ix+2),3
    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
    ld d,h
    ld e,l
    add hl,hl
    add hl,de
    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
phase_draw_clipped_row:
    push hl
    push de
    ld b,(ix+10)
phase_draw_clipped_byte:
    ld c,(hl)
    inc hl
    ld a,(de)
    and c
    or (hl)
    ld (de),a
    inc hl
    inc de
    djnz phase_draw_clipped_byte
    pop de
    pop hl
    dec (ix+30)
    ret z
    ld bc,6
    add hl,bc
    ex de,hl
    call pixel_down
    ex de,hl
    jr phase_draw_clipped_row

A bank uses 784 bytes including pointers, versus 64 unshifted bytes. The drawing loop pushes four row-pointer bytes, plus call-return and interrupt requirements. The clipping helper keeps its existing contract; this is a separate renderer for the wider prepared rows.

Build and adapt the caller

Build with make ASM198X=/path/to/asm198x; load build/demo.tap on a 48K PAL Spectrum. The caller owns the bitmap, attributes and a private stack at $ff00, then uses ROM IM1/HALT to service frames. Reset to leave. It draws a static gallery, so it does not promise a smooth animation cadence.

Complete tape caller for every phase and all four edges

demo.asm
org $8000
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,0
paint_line:
    ld e,0
    call pixel_addr
    ld b,32
paint_byte:
    ld (hl),$11
    inc hl
    djnz paint_byte
    ld a,d
    add a,8
    ld d,a
    cp 192
    jr c,paint_line
    ld hl,8
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,33
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,58
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,83
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,108
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,133
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,158
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,183
    ld (context),hl
    ld hl,40
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,8
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,33
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,58
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,83
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,108
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,133
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,158
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,183
    ld (context),hl
    ld hl,-8
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,8
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,33
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,58
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,83
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,108
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,133
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,158
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,183
    ld (context),hl
    ld hl,184
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-8
    ld (context),hl
    ld hl,80
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-7
    ld (context),hl
    ld hl,90
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-6
    ld (context),hl
    ld hl,100
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-5
    ld (context),hl
    ld hl,110
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-4
    ld (context),hl
    ld hl,120
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-3
    ld (context),hl
    ld hl,130
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-2
    ld (context),hl
    ld hl,140
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,-1
    ld (context),hl
    ld hl,150
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,248
    ld (context),hl
    ld hl,80
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,249
    ld (context),hl
    ld hl,90
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,250
    ld (context),hl
    ld hl,100
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,251
    ld (context),hl
    ld hl,110
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,252
    ld (context),hl
    ld hl,120
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,253
    ld (context),hl
    ld hl,130
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,254
    ld (context),hl
    ld hl,140
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    ld hl,255
    ld (context),hl
    ld hl,150
    ld (context+14),hl
    ld hl,sprite_phase_table
    ld (context+28),hl
    ld ix,context
    call draw_pixel_sprite
    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 "phase.inc"
include "draw.inc"
include "../clipped-sprites/clip.inc"
include "../bitmap-addressing/bitmap-addressing.inc"
include "phases.inc"
end start

Change the original sprite.json, then run make regenerate; make check-assets verifies the committed bank. For a moving object, the existing rectangle save/restore helpers accept the three-byte width. Allow up to 48 saved background bytes per object and keep its old clipped address and dimensions. Source preparation, position updates and removal of the old picture remain separate jobs.