Original Code198x program and artwork captured in Emu198x.
Code status: maintained routines and an emulator-executed phase gallery. Execution record.
Pixel X can belong to the object even though MODE4 memory is organised in byte columns and eight-row character cells. The phase wrapper selects a prepared source, then the drawing routine deals with that bitmap layout and viewport clipping.
Set up the call
Include phase.inc, draw.inc, the existing clipped-sprites/clip.inc and one generated bank. Set clip_ptr ($70/$71) to 32 exclusive caller bytes. Signed pixel X goes at +0, signed pixel-row Y at +14, and an eight-entry pointer table at +28.
The wrapper consumes X/table fields, replacing them with byte-column X and the selected source pointer. Reload those inputs before each call; keep the object’s logical coordinates separately. Use another generated table to draw a different image with the same routine.
A/X/Y/P change, and decimal mode must be clear. The routine owns zero-page $70..$78 and context; interrupt users must preserve them. Table, sources, context, code and stack must be separate from the bitmap and one another. It sets up no IRQs, screen mode or display timing.
Select the phase and divide signed pixel X towards negative infinity
; SPDX-License-Identifier: MIT
; draw_pixel_sprite: clip_ptr -> 32 bytes, signed PIXEL X +0, Y +14,
; pointer to generated eight-entry phase table +28. Reload before every call:
; X/table become byte X/source pointer. A/X/Y/P change; D=0.
; Owns $70..$78 plus context. Preserve these in interrupt users. No IRQ setup.
draw_pixel_sprite:
ldy #0
lda (clip_ptr),y
and #7
asl
sta phase_mask_byte
ldx #3
phase_divide:
ldy #1
lda (clip_ptr),y
cmp #$80
ror
sta (clip_ptr),y
dey
lda (clip_ptr),y
ror
sta (clip_ptr),y
dex
bne phase_divide
ldy #28
lda (clip_ptr),y
sta phase_source_ptr
iny
lda (clip_ptr),y
sta phase_source_ptr+1
ldy phase_mask_byte
lda (phase_source_ptr),y
pha
iny
lda (phase_source_ptr),y
ldy #29
sta (clip_ptr),y
pla
dey
sta (clip_ptr),y
jmp phase_draw_clippedFollow the prepared source through MODE4
Every prepared phase has three byte columns and sixteen rows: ninety-six interleaved keep-mask/image bytes. The mask retains background bits where it is 1; the corresponding image bits are zero. The bank contains eight phases and sixteen pointer bytes, totalling 784 bytes rather than 64 unshifted bytes.
The bounded renderer keeps the original six-byte source stride after cropping. It maps the normal unscrolled forty-column MODE4 display, including page carries within and between 320-byte character-row bands. A changed CRTC start needs a different mapper.
Draw the visible prepared mask/image pairs in MODE4
; SPDX-License-Identifier: MIT
; phase_draw_clipped: clip_ptr -> 32-byte context; +0 signed X in byte columns,
; +14 signed Y in pixel rows, +28 source pointer (96 interleaved KEEP/image
; bytes for a 24x16 sprite). Remaining context is work and clip outputs.
; MODE4 standard unscrolled $5800..$7fff. No attributes or OS/display setup.
; A/X/Y/P change. D=0. Own ZP $70..$78 and context, exclude interrupt users.
; This implementation pushes TWO source bytes per
; row, in addition to JSR returns and IRQ use. No static background storage.
phase_source_ptr=$72
phase_screen_ptr=$74
phase_row_ptr=$76
phase_mask_byte=$78
phase_draw_clipped:
ldy #2
lda #3
sta (clip_ptr),y
iny
lda #0
sta (clip_ptr),y
iny
lda #40
sta (clip_ptr),y
iny
lda #0
sta (clip_ptr),y
jsr clip_axis
ldy #10
lda (clip_ptr),y
bne phase_draw_clip_y
rts
phase_draw_clip_y:
clc
lda clip_ptr
adc #14
sta clip_ptr
bcc phase_draw_y_pointer
inc clip_ptr+1
phase_draw_y_pointer:
ldy #2
lda #16
sta (clip_ptr),y
iny
lda #0
sta (clip_ptr),y
iny
sta (clip_ptr),y
iny
lda #1
sta (clip_ptr),y
jsr clip_axis
sec
lda clip_ptr
sbc #14
sta clip_ptr
bcs phase_draw_context_restored
dec clip_ptr+1
phase_draw_context_restored:
ldy #24
lda (clip_ptr),y
bne phase_draw_visible
rts
phase_draw_visible:
ldy #30
sta (clip_ptr),y
; source = original + skipped_rows*6 + skipped_columns*2.
ldy #22
lda (clip_ptr),y
asl
sta phase_mask_byte
asl
clc
adc phase_mask_byte
tax
ldy #8
lda (clip_ptr),y
asl
sta phase_mask_byte
txa
clc
adc phase_mask_byte
ldy #28
adc (clip_ptr),y
sta phase_source_ptr
iny
lda (clip_ptr),y
adc #0
sta phase_source_ptr+1
; MODE4: base + (y/8)*320 + x*8 + (y mod 8).
lda #0
sta phase_row_ptr+1
ldy #20
lda (clip_ptr),y
and #7
sta phase_mask_byte
ldy #6
lda (clip_ptr),y
asl
rol phase_row_ptr+1
asl
rol phase_row_ptr+1
asl
rol phase_row_ptr+1
ora phase_mask_byte
sta phase_row_ptr
lda phase_row_ptr+1
clc
adc #$58
sta phase_row_ptr+1
ldy #20
lda (clip_ptr),y
lsr
lsr
lsr
tax
beq phase_draw_clipped_row
phase_draw_cell_row:
clc
lda phase_row_ptr
adc #64
sta phase_row_ptr
lda phase_row_ptr+1
adc #1
sta phase_row_ptr+1
dex
bne phase_draw_cell_row
phase_draw_clipped_row:
lda phase_row_ptr
sta phase_screen_ptr
lda phase_row_ptr+1
sta phase_screen_ptr+1
lda phase_source_ptr+1
pha
lda phase_source_ptr
pha
ldy #10
lda (clip_ptr),y
tax
phase_draw_clipped_byte:
ldy #0
lda (phase_source_ptr),y
sta phase_mask_byte
lda (phase_screen_ptr),y
and phase_mask_byte
iny
ora (phase_source_ptr),y
dey
sta (phase_screen_ptr),y
clc
lda phase_source_ptr
adc #2
sta phase_source_ptr
bcc phase_draw_source_ready
inc phase_source_ptr+1
phase_draw_source_ready:
clc
lda phase_screen_ptr
adc #8
sta phase_screen_ptr
bcc phase_draw_screen_ready
inc phase_screen_ptr+1
phase_draw_screen_ready:
dex
bne phase_draw_clipped_byte
pla
sta phase_source_ptr
pla
sta phase_source_ptr+1
ldy #30
lda (clip_ptr),y
sec
sbc #1
sta (clip_ptr),y
bne phase_draw_next_row
rts
phase_draw_next_row:
clc
lda phase_source_ptr
adc #6
sta phase_source_ptr
bcc phase_draw_source_row
inc phase_source_ptr+1
phase_draw_source_row:
inc phase_row_ptr
bne phase_draw_row_carry_ready
inc phase_row_ptr+1
phase_draw_row_carry_ready:
lda phase_row_ptr
and #7
bne phase_draw_clipped_row
clc
lda phase_row_ptr
adc #$38
sta phase_row_ptr
lda phase_row_ptr+1
adc #1
sta phase_row_ptr+1
jmp phase_draw_clipped_rowThe loop pushes two source-pointer bytes per row, plus JSR returns and interrupt requirements. It shares the existing axis clipping helper while leaving the old fixed-size renderer intact.
Run the gallery
Build with make ASM198X=/path/to/asm198x and type build/loader.bas on Model B MOS1.20/BASICII. Its compact hexadecimal DATA loader reserves HIMEM=&4000 and enters machine code there, below the bitmap at &5800. The caller uses OSBYTE19 with MOS IRQs active after drawing. Reset to leave.
Complete MODE4 caller with all eight phases and cropped edges
*=$4000
start:
cld
cli
lda #0
sta $74
lda #$58
sta $75
ldy #0
paint:
tya
and #7
beq paint_grid
lda #0
jmp paint_value
paint_grid:
lda #$11
paint_value:
sta ($74),y
iny
bne paint
inc $75
lda $75
cmp #$80
bne paint
lda #<8
sta context
lda #>8
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<33
sta context
lda #>33
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<58
sta context
lda #>58
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<83
sta context
lda #>83
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<108
sta context
lda #>108
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<133
sta context
lda #>133
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<158
sta context
lda #>158
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<183
sta context
lda #>183
sta context+1
lda #<40
sta context+14
lda #>40
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<8
sta context
lda #>8
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<33
sta context
lda #>33
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<58
sta context
lda #>58
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<83
sta context
lda #>83
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<108
sta context
lda #>108
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<133
sta context
lda #>133
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<158
sta context
lda #>158
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<183
sta context
lda #>183
sta context+1
lda #<65528
sta context+14
lda #>65528
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<8
sta context
lda #>8
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<33
sta context
lda #>33
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<58
sta context
lda #>58
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<83
sta context
lda #>83
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<108
sta context
lda #>108
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<133
sta context
lda #>133
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<158
sta context
lda #>158
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<183
sta context
lda #>183
sta context+1
lda #<248
sta context+14
lda #>248
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65528
sta context
lda #>65528
sta context+1
lda #<80
sta context+14
lda #>80
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65529
sta context
lda #>65529
sta context+1
lda #<90
sta context+14
lda #>90
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65530
sta context
lda #>65530
sta context+1
lda #<100
sta context+14
lda #>100
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65531
sta context
lda #>65531
sta context+1
lda #<110
sta context+14
lda #>110
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65532
sta context
lda #>65532
sta context+1
lda #<120
sta context+14
lda #>120
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65533
sta context
lda #>65533
sta context+1
lda #<130
sta context+14
lda #>130
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65534
sta context
lda #>65534
sta context+1
lda #<140
sta context+14
lda #>140
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<65535
sta context
lda #>65535
sta context+1
lda #<150
sta context+14
lda #>150
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<312
sta context
lda #>312
sta context+1
lda #<80
sta context+14
lda #>80
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<313
sta context
lda #>313
sta context+1
lda #<90
sta context+14
lda #>90
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<314
sta context
lda #>314
sta context+1
lda #<100
sta context+14
lda #>100
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<315
sta context
lda #>315
sta context+1
lda #<110
sta context+14
lda #>110
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<316
sta context
lda #>316
sta context+1
lda #<120
sta context+14
lda #>120
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<317
sta context
lda #>317
sta context+1
lda #<130
sta context+14
lda #>130
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<318
sta context
lda #>318
sta context+1
lda #<140
sta context+14
lda #>140
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #<319
sta context
lda #>319
sta context+1
lda #<150
sta context+14
lda #>150
sta context+15
lda #<sprite_phase_table
sta context+28
lda #>sprite_phase_table
sta context+29
lda #<context
sta clip_ptr
lda #>context
sta clip_ptr+1
jsr draw_pixel_sprite
lda #$ca
sta ready
main:
lda #19
jsr $fff4
inc updates
jmp main
ready: !byte 0
updates: !byte 0
context: !fill 32,0
!source "phase.inc"
!source "draw.inc"
!source "../clipped-sprites/clip.inc"
!source "phases.inc"The gallery is static and keeps servicing frame waits. It verifies placement and masking, rather than an animation deadline. Run make regenerate after editing the original artwork; make check-assets rejects a stale bank.
For movement, provide a backing store for the old padded/clipped rectangle or redraw from tile/world data. The existing 8×8 saved-cell routine is too small for a 24×16 prepared footprint. The shared article explains the memory choice and alternatives.