Skip to content

Draw Lines, Circles and Ellipses on Spectrum 48K

Reusable clipped integer geometry, span-filled curves and explicit endpoint ownership for Spectrum 48K.

Draw a sight line, a circular scanner or a filled oval without building a renderer first. These are five callable assembly routines over the existing pixel/span interface. You choose the bitmap, own the memory and call only the pieces your game needs.

Native Spectrum 48K output with filled and outlined ellipses, a circle, and a clipped diagonal line
Original caller in Emu198x 0.39.0. This is the machine’s native output; the diagnostic scene contains no third-party art.

Include it and make a call

BP at $7000 and G at $7200 in the complete caller. Include the existing pixel_addr helper after the backend. Attributes at $5800–$5AFF are separate: these routines change bitmap bits, not cell colours. The bitmap uses the Spectrum’s interleaved scanline address; incrementing Y is not adding 32 to an address.

Define GEO_WIDTH and GEO_HEIGHT as 256 and 192. Set GEO_PEN_MAX to 255 (pen ignored). Include the geometry z80.inc and the backend exactly once, as shown at the bottom of the complete caller. Reserve 160 bytes at G plus 48 at BP, separately from code, stack, tables and display. Scratch needs no initialisation; public inputs do.

After the caller’s video setup, this is an ordinary direct call. Your game does not need the verification mailbox that follows it.

Outline an ellipse centred at (80,70), radii (48,24)

demo.asm, excerpt
; A normal game call: an outlined ellipse. Setup and mailbox are separate.
    ld hl,80
    ld (G+0),hl
    ld hl,70
    ld (G+2),hl
    ld hl,48
    ld (G+4),hl
    ld hl,24
    ld (G+6),hl
    ld hl,0
    ld (G+12),hl
    ld hl,0
    ld (G+14),hl
    ld hl,256
    ld (G+16),hl
    ld hl,192
    ld (G+18),hl
    ld a,0
    ld (G+8),a
    ld a,0
    ld (G+9),a
    ld a,1
    ld (G+11),a
    call geo_ellipse
    ld a,0
    ld (COMMAND),a
    ld a,239
    ld (COMMAND+1),a

Read G+10 after return: 1 means accepted, 0 means rejected. An accepted shape can be completely outside the scissor and draw nothing. Change the inputs and call again; G holds no hidden scene or list of shapes.

The calling contract

All words use little-endian byte order. Screen origin is top left; Y increases downwards. Pen is ignored by this monochrome backend; initialise it to 1 for a caller that also ports to CPC.

G offset Supply or read
0,2 Signed x0,y0 for a line; centre cx,cy for curves
4,6 Signed x1,y1 for a line; radii rx,ry for an ellipse; a circle uses rx and ignores ry
8 Operation: 0 set/replace, 1 clear, 2 XOR
9 Line: 0 includes both ends, 1 omits the original caller’s final end; curves require 0
10 Output: 1 accepted, 0 rejected
11 Pen index (ignored on mono backends)
12,14,16,18 Unsigned scissor left,top,right,bottom; right/bottom exclusive
20 Output: 32-bit destination-byte writes
24 Output: 16-bit backend calls
26–159 Private scratch

Call geo_line, geo_circle, geo_ellipse, geo_fill_circle or geo_fill_ellipse. Coordinates must be −512..511, radii 0..255. The scissor must be nonempty and inside the 256×192 display. Invalid bounds, radii, operation, flags or pen reject before any destination write, with zero counters. Inputs at 0–9 and 11–19 survive. Unused circle ry is ignored. A zero radius becomes a point or straight span rather than an error.

Keep the line you asked for

The line uses accumulated integer error in all eight octants. It orders endpoints consistently before choosing half-way pixels, so reversing a line produces the same pixels. A horizontal or vertical line uses a span fast path. A zero-length inclusive line draws one pixel.

For a closed XOR polyline, give every edge flag 1. Each omits its original final endpoint, so the next edge owns that shared vertex. Leaving both endpoints on both edges would toggle the vertex twice and erase it. A zero-length omit-final line draws nothing.

The scissor discards outside pixels from the original raster, preserving its rounding phase. It does not replace the endpoints with rounded intersections. Offscreen steps still cost work: the accepted coordinate range bounds the major-axis delta at 1023. Filled curves instead intersect each row span with the scissor before calling the backend.

Draw with integer error works through a short line, symmetry, span filling and overflow. Place a point explains the origin difference; for integer screen rows here, convert an upwards world Y with screenY=height-1-worldY.

Fill rows, not thousands of point calls

Outline ellipses update an integer decision and reflect points into four quadrants. Axis points are emitted once. Filled ellipses select the widest interval for each row and emit that span once; they do not repeatedly XOR the same row as the edge advances. Circles use equal ellipse radii through this same kernel. A specialised eight-way circle implementation could reduce work further.

An ellipse with radii (37,15) covers 1,833 logical pixels here but needs only 31 span calls when filled. Those calls made 258 byte writes on this backend. That is actual destination work, not elapsed cycles. Four-pixel CPC packing and eight-pixel mono packing produce different counts, and partial edge bytes still need protection.

The geometry occupies 5,110 assembled bytes, the backend 533, plus the 208 RAM bytes for G and BP. The existing pixel_addr helper is additional. Reset code, stack, display and assets are additional. The checked 32-bit curve arithmetic buys a clear range contract but has a code/time cost. Cache static shapes or specialise a hot path when measurement shows a need. No frame-budget claim has been measured here.

A logical circle has equal X/Y radii. Its displayed shape still depends on the display mode, pixel aspect and capture presentation. Choose the presentation deliberately; changing radius to compensate is a separate mapping decision, not a change to the circle equation.

Own the resources

Clobbered: AF/BC/DE/HL and flags. Reserve a conservative 64-byte stack budget, plus your other calls and interrupts. The component is non-re-entrant. It does not change interrupt state or negotiate display access. Keep G/BP private and coordinate another renderer or interrupt user yourself. Reset fixtures own the machine; integration under a live OS needs its own loading and coexistence checks.

Open the complete reusable Z80 geometry include

Include this CPU component once

z80.inc
; SPDX-License-Identifier: MIT
; Copyright (c) 2020 zingl; assembly adaptation Code198x (2026).
; Integer geometry over the existing bp_pixel/bp_hspan/bp_vspan backend.
; G is 160 caller-owned RAM bytes. BP is a separate 48-byte workspace.
; Read README.md for signed inputs, scissor, endpoint flags and ownership.
; Bresenham/ellipse recurrences adapted from Alois Zingl; see LICENSE-Zingl.txt.
g_a equ G+0
g_b equ G+2
g_c equ G+4
g_d equ G+6
g_op equ G+8
g_flags equ G+9
g_status equ G+10
g_pen equ G+11
g_clip_l equ G+12
g_clip_t equ G+14
g_clip_r equ G+16
g_clip_b equ G+18
g_writes equ G+20
g_calls equ G+24
g_mode equ G+32
g_first equ G+33
g_swap equ G+34
g_fill equ G+35
g_x equ G+40
g_y equ G+42
g_end_x equ G+44
g_end_y equ G+46
g_dx equ G+48
g_dy equ G+50
g_sy equ G+54
g_err equ G+56
g_e2 equ G+58
g_temp equ G+60
g_last_y equ G+62
g_rx equ G+64
g_ry equ G+66
g_px equ G+68
g_py equ G+70
g_right equ G+72
g_bottom equ G+74
g_a2 equ G+80
g_b2 equ G+84
g_curve_dx equ G+88
g_curve_dy equ G+92
g_curve_err equ G+96
g_curve_e2 equ G+100
g_step_x equ G+104
g_step_y equ G+108
g_work equ G+112
g_mul equ G+158
g_cmp equ G+159
bp_writes equ BP+40
bp_status equ BP+9
bp_op equ BP+8
bp_pen equ BP+11
bp_x0 equ BP+0
bp_y0 equ BP+2
bp_x1 equ BP+4
bp_y1 equ BP+6
geo_line:
    ld a,$00
    ld (g_mode),a
    jp geo_enter
geo_circle:
    ld a,$01
    ld (g_mode),a
    jp geo_enter
geo_ellipse:
    ld a,$02
    ld (g_mode),a
    jp geo_enter
geo_fill_circle:
    ld a,$03
    ld (g_mode),a
    jp geo_enter
geo_fill_ellipse:
    ld a,$04
    ld (g_mode),a
    jp geo_enter
geo_enter:
    ld a,$00
    ld (g_status),a
    ld a,$00
    ld (g_writes),a
    ld a,$00
    ld (g_writes+1),a
    ld a,$00
    ld (g_writes+2),a
    ld a,$00
    ld (g_writes+3),a
    ld hl,$0000
    ld (g_calls),hl
; Validate the entire request before the first backend write.
    ld a,$02
    ld (g_cmp),a
    ld a,(g_op)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_1
    jp nz,geo_done
geo_branch_1:
    ld a,(g_pen)
    cp GEO_PEN_MAX
    jp c,geo_branch_2
    jp z,geo_branch_2
    jp geo_done
geo_branch_2:
    ld a,$fe
    xor $80
    ld (g_cmp),a
    ld a,(g_a+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_3
    ld a,$00
    ld (g_cmp),a
    ld a,(g_a)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_3:
    ld a,$01
    xor $80
    ld (g_cmp),a
    ld a,(g_a+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_4
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_a)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_4
    jp nz,geo_done
geo_branch_4:
    ld a,$fe
    xor $80
    ld (g_cmp),a
    ld a,(g_b+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_5
    ld a,$00
    ld (g_cmp),a
    ld a,(g_b)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_5:
    ld a,$01
    xor $80
    ld (g_cmp),a
    ld a,(g_b+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_6
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_b)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_6
    jp nz,geo_done
geo_branch_6:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_l+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_7
    ld a,$00
    ld (g_cmp),a
    ld a,(g_clip_l)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_7:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_t+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_8
    ld a,$00
    ld (g_cmp),a
    ld a,(g_clip_t)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_8:
    ld a,(g_clip_r+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_l+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_9
    jp nz,geo_done
    ld a,(g_clip_r)
    ld (g_cmp),a
    ld a,(g_clip_l)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_9
    jp geo_done
geo_branch_9:
    ld a,(g_clip_b+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_t+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_10
    jp nz,geo_done
    ld a,(g_clip_b)
    ld (g_cmp),a
    ld a,(g_clip_t)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_10
    jp geo_done
geo_branch_10:
    ld hl,GEO_WIDTH
    ld (g_temp),hl
    ld a,(g_temp+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_r+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_11
    jp nz,geo_done
    ld a,(g_temp)
    ld (g_cmp),a
    ld a,(g_clip_r)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_11
    jp nz,geo_done
geo_branch_11:
    ld hl,GEO_HEIGHT
    ld (g_temp),hl
    ld a,(g_temp+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_clip_b+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_12
    jp nz,geo_done
    ld a,(g_temp)
    ld (g_cmp),a
    ld a,(g_clip_b)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_12
    jp nz,geo_done
geo_branch_12:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_mode)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_curve_validate
geo_branch_13:
    ld a,$01
    ld (g_cmp),a
    ld a,(g_flags)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_14
    jp nz,geo_done
geo_branch_14:
    ld a,$fe
    xor $80
    ld (g_cmp),a
    ld a,(g_c+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_15
    ld a,$00
    ld (g_cmp),a
    ld a,(g_c)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_15:
    ld a,$01
    xor $80
    ld (g_cmp),a
    ld a,(g_c+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_16
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_c)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_16
    jp nz,geo_done
geo_branch_16:
    ld a,$fe
    xor $80
    ld (g_cmp),a
    ld a,(g_d+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_17
    ld a,$00
    ld (g_cmp),a
    ld a,(g_d)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_17:
    ld a,$01
    xor $80
    ld (g_cmp),a
    ld a,(g_d+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_18
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_d)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_18
    jp nz,geo_done
geo_branch_18:
    ld a,$01
    ld (g_status),a
    jp geo_line_start
geo_curve_validate:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_flags)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_done
geo_branch_19:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_c+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_20
    ld a,$00
    ld (g_cmp),a
    ld a,(g_c)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_20:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_c+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_21
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_c)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_21
    jp nz,geo_done
geo_branch_21:
    ld hl,(g_c)
    ld (g_rx),hl
    ld a,$01
    ld (g_cmp),a
    ld a,(g_mode)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_22
    jp geo_equal_axes
geo_branch_22:
    ld a,$03
    ld (g_cmp),a
    ld a,(g_mode)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_23
    jp geo_equal_axes
geo_branch_23:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_d+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
    jp nz,geo_branch_24
    ld a,$00
    ld (g_cmp),a
    ld a,(g_d)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_done
geo_branch_24:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_d+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_25
    jp nz,geo_done
    ld a,$ff
    ld (g_cmp),a
    ld a,(g_d)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_25
    jp nz,geo_done
geo_branch_25:
    ld hl,(g_d)
    ld (g_ry),hl
    jp geo_axes_ready
geo_equal_axes:
    ld hl,(g_c)
    ld (g_ry),hl
geo_axes_ready:
    ld a,$01
    ld (g_status),a
    ld a,$00
    ld (g_fill),a
    ld a,$03
    ld (g_cmp),a
    ld a,(g_mode)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_curve_start
geo_branch_26:
    ld a,$01
    ld (g_fill),a
    jp geo_curve_start
geo_done:
    ret
geo_line_start:
; Canonical ordering gives the same tie pixels when endpoints are reversed.
    ld hl,(g_a)
    ld (g_x),hl
    ld hl,(g_b)
    ld (g_y),hl
    ld hl,(g_c)
    ld (g_end_x),hl
    ld hl,(g_d)
    ld (g_end_y),hl
    ld a,$00
    ld (g_swap),a
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_27
    jp nz,geo_swap_endpoints
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_27
    jp nz,geo_swap_endpoints
geo_branch_27:
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_line_ordered
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_line_ordered
geo_branch_28:
    ld a,(g_end_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_line_ordered
    jp nz,geo_branch_29
    ld a,(g_end_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_line_ordered
    jp z,geo_line_ordered
geo_branch_29:
geo_swap_endpoints:
    ld hl,(g_x)
    ld (g_temp),hl
    ld hl,(g_end_x)
    ld (g_x),hl
    ld hl,(g_temp)
    ld (g_end_x),hl
    ld hl,(g_y)
    ld (g_temp),hl
    ld hl,(g_end_y)
    ld (g_y),hl
    ld hl,(g_temp)
    ld (g_end_y),hl
    ld a,$01
    ld (g_swap),a
geo_line_ordered:
    ld a,(g_end_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_30
    ld a,(g_end_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_30
    jp geo_horizontal
geo_branch_30:
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_31
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_31
    jp geo_vertical_line
geo_branch_31:
    ld hl,(g_end_x)
    ld (g_dx),hl
    ld hl,(g_dx)
    ld de,(g_x)
    or a
    sbc hl,de
    ld (g_dx),hl
    ld hl,(g_end_y)
    ld (g_dy),hl
    ld hl,(g_dy)
    ld de,(g_y)
    or a
    sbc hl,de
    ld (g_dy),hl
    ld hl,$0001
    ld (g_sy),hl
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_dy+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_33
    jp nz,geo_branch_32
    ld a,$00
    ld (g_cmp),a
    ld a,(g_dy)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_33
    jp geo_branch_32
geo_branch_33:
    ld hl,$ffff
    ld (g_sy),hl
    ld a,(g_dy)
    xor $ff
    ld (g_dy),a
    ld a,(g_dy+1)
    xor $ff
    ld (g_dy+1),a
    ld hl,(g_dy)
    ld de,$0001
    add hl,de
    ld (g_dy),hl
geo_branch_32:
    ld a,(g_dy)
    xor $ff
    ld (g_dy),a
    ld a,(g_dy+1)
    xor $ff
    ld (g_dy+1),a
    ld hl,(g_dy)
    ld de,$0001
    add hl,de
    ld (g_dy),hl
    ld hl,(g_dx)
    ld (g_err),hl
    ld hl,(g_err)
    ld de,(g_dy)
    add hl,de
    ld (g_err),hl
    ld a,$01
    ld (g_first),a
geo_line_loop:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_flags)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_36
    jp geo_branch_34
geo_branch_36:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_swap)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_38
    jp geo_branch_37
geo_branch_38:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_first)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_35
geo_branch_39:
    jp geo_branch_34
geo_branch_37:
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_34
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_34
geo_branch_40:
    ld a,(g_end_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_41
    ld a,(g_end_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_41
    jp geo_done
geo_branch_41:
geo_branch_34:
    ld hl,(g_x)
    ld (g_px),hl
    ld hl,(g_y)
    ld (g_py),hl
    call geo_point
geo_branch_35:
    ld a,$00
    ld (g_first),a
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_42
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_42
geo_branch_43:
    ld a,(g_end_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_44
    ld a,(g_end_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_44
    jp geo_done
geo_branch_44:
geo_branch_42:
    ld hl,(g_err)
    ld (g_e2),hl
    ld hl,g_e2
    sla (hl)
    inc hl
    rl (hl)
    ld a,(g_dy+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_e2+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_45
    jp nz,geo_branch_46
    ld a,(g_dy)
    ld (g_cmp),a
    ld a,(g_e2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_45
geo_branch_46:
    ld hl,(g_err)
    ld de,(g_dy)
    add hl,de
    ld (g_err),hl
    ld hl,(g_x)
    ld de,$0001
    add hl,de
    ld (g_x),hl
geo_branch_45:
    ld a,(g_dx+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_e2+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_48
    jp nz,geo_branch_47
    ld a,(g_dx)
    ld (g_cmp),a
    ld a,(g_e2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_48
    jp nz,geo_branch_47
geo_branch_48:
    ld hl,(g_err)
    ld de,(g_dx)
    add hl,de
    ld (g_err),hl
    ld hl,(g_y)
    ld de,(g_sy)
    add hl,de
    ld (g_y),hl
geo_branch_47:
    jp geo_line_loop
geo_horizontal:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_flags)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_50
    jp geo_branch_49
geo_branch_50:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_swap)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_52
    jp geo_branch_51
geo_branch_52:
    ld hl,(g_x)
    ld de,$0001
    add hl,de
    ld (g_x),hl
    jp geo_branch_49
geo_branch_51:
    ld hl,(g_end_x)
    ld de,$ffff
    add hl,de
    ld (g_end_x),hl
geo_branch_49:
    ld a,(g_end_x+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_53
    jp nz,geo_done
    ld a,(g_end_x)
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_53
    jp nz,geo_done
geo_branch_53:
    ld hl,(g_x)
    ld (g_px),hl
    ld hl,(g_end_x)
    ld (g_right),hl
    ld hl,(g_y)
    ld (g_py),hl
    call geo_span
    jp geo_done
geo_vertical_line:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_flags)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_55
    jp geo_branch_54
geo_branch_55:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_swap)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_57
    jp geo_branch_56
geo_branch_57:
    ld hl,(g_y)
    ld de,$0001
    add hl,de
    ld (g_y),hl
    jp geo_branch_54
geo_branch_56:
    ld hl,(g_end_y)
    ld de,$ffff
    add hl,de
    ld (g_end_y),hl
geo_branch_54:
    ld a,(g_end_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_58
    jp nz,geo_done
    ld a,(g_end_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_58
    jp nz,geo_done
geo_branch_58:
    ld hl,(g_x)
    ld (g_px),hl
    ld hl,(g_y)
    ld (g_py),hl
    ld hl,(g_end_y)
    ld (g_bottom),hl
    call geo_vspan
    jp geo_done
geo_curve_start:
; Collapsed axes are an inclusive point or straight span, never a division by zero.
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_rx+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_59
    ld a,$00
    ld (g_cmp),a
    ld a,(g_rx)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_59
    jp geo_curve_vertical
geo_branch_59:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_ry+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_60
    ld a,$00
    ld (g_cmp),a
    ld a,(g_ry)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_60
    jp geo_curve_horizontal
geo_branch_60:
    ld hl,(g_rx)
    ld (g_x),hl
    ld a,(g_x)
    xor $ff
    ld (g_x),a
    ld a,(g_x+1)
    xor $ff
    ld (g_x+1),a
    ld hl,(g_x)
    ld de,$0001
    add hl,de
    ld (g_x),hl
    ld hl,$0000
    ld (g_y),hl
    ld hl,$ffff
    ld (g_last_y),hl
; Squared radii and signed error increments use 32-bit storage. Radii <=255.
    ld a,$00
    ld (g_a2),a
    ld a,$00
    ld (g_a2+1),a
    ld a,$00
    ld (g_a2+2),a
    ld a,$00
    ld (g_a2+3),a
    ld a,$00
    ld (g_work),a
    ld a,$00
    ld (g_work+1),a
    ld a,$00
    ld (g_work+2),a
    ld a,$00
    ld (g_work+3),a
    ld a,(g_rx)
    ld (g_work),a
    ld a,(g_rx+1)
    ld (g_work+1),a
    ld a,(g_rx)
    ld (g_mul),a
geo_branch_61:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_mul)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_63
    jp geo_branch_62
geo_branch_63:
    ld a,(g_mul)
    and 1
    jp z,geo_branch_64
    or a
    ld a,(g_work)
    ld b,a
    ld a,(g_a2)
    adc a,b
    ld (g_a2),a
    ld a,(g_work+1)
    ld b,a
    ld a,(g_a2+1)
    adc a,b
    ld (g_a2+1),a
    ld a,(g_work+2)
    ld b,a
    ld a,(g_a2+2)
    adc a,b
    ld (g_a2+2),a
    ld a,(g_work+3)
    ld b,a
    ld a,(g_a2+3)
    adc a,b
    ld (g_a2+3),a
geo_branch_64:
    ld hl,g_mul
    srl (hl)
    ld hl,g_work
    sla (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    jp geo_branch_61
geo_branch_62:
    ld a,$00
    ld (g_b2),a
    ld a,$00
    ld (g_b2+1),a
    ld a,$00
    ld (g_b2+2),a
    ld a,$00
    ld (g_b2+3),a
    ld a,$00
    ld (g_work),a
    ld a,$00
    ld (g_work+1),a
    ld a,$00
    ld (g_work+2),a
    ld a,$00
    ld (g_work+3),a
    ld a,(g_ry)
    ld (g_work),a
    ld a,(g_ry+1)
    ld (g_work+1),a
    ld a,(g_ry)
    ld (g_mul),a
geo_branch_65:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_mul)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_67
    jp geo_branch_66
geo_branch_67:
    ld a,(g_mul)
    and 1
    jp z,geo_branch_68
    or a
    ld a,(g_work)
    ld b,a
    ld a,(g_b2)
    adc a,b
    ld (g_b2),a
    ld a,(g_work+1)
    ld b,a
    ld a,(g_b2+1)
    adc a,b
    ld (g_b2+1),a
    ld a,(g_work+2)
    ld b,a
    ld a,(g_b2+2)
    adc a,b
    ld (g_b2+2),a
    ld a,(g_work+3)
    ld b,a
    ld a,(g_b2+3)
    adc a,b
    ld (g_b2+3),a
geo_branch_68:
    ld hl,g_mul
    srl (hl)
    ld hl,g_work
    sla (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    jp geo_branch_65
geo_branch_66:
    ld a,(g_b2)
    ld (g_step_x),a
    ld a,(g_b2+1)
    ld (g_step_x+1),a
    ld a,(g_b2+2)
    ld (g_step_x+2),a
    ld a,(g_b2+3)
    ld (g_step_x+3),a
    ld hl,g_step_x
    sla (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    ld a,(g_a2)
    ld (g_step_y),a
    ld a,(g_a2+1)
    ld (g_step_y+1),a
    ld a,(g_a2+2)
    ld (g_step_y+2),a
    ld a,(g_a2+3)
    ld (g_step_y+3),a
    ld hl,g_step_y
    sla (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    ld a,(g_b2)
    ld (g_curve_dx),a
    ld a,(g_b2+1)
    ld (g_curve_dx+1),a
    ld a,(g_b2+2)
    ld (g_curve_dx+2),a
    ld a,(g_b2+3)
    ld (g_curve_dx+3),a
    ld hl,(g_rx)
    ld (g_temp),hl
geo_branch_69:
    or a
    ld a,(g_step_x)
    ld b,a
    ld a,(g_curve_dx)
    sbc a,b
    ld (g_curve_dx),a
    ld a,(g_step_x+1)
    ld b,a
    ld a,(g_curve_dx+1)
    sbc a,b
    ld (g_curve_dx+1),a
    ld a,(g_step_x+2)
    ld b,a
    ld a,(g_curve_dx+2)
    sbc a,b
    ld (g_curve_dx+2),a
    ld a,(g_step_x+3)
    ld b,a
    ld a,(g_curve_dx+3)
    sbc a,b
    ld (g_curve_dx+3),a
    ld hl,(g_temp)
    ld de,$ffff
    add hl,de
    ld (g_temp),hl
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_temp+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_69
    ld a,$00
    ld (g_cmp),a
    ld a,(g_temp)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_69
geo_branch_70:
    ld a,(g_a2)
    ld (g_curve_dy),a
    ld a,(g_a2+1)
    ld (g_curve_dy+1),a
    ld a,(g_a2+2)
    ld (g_curve_dy+2),a
    ld a,(g_a2+3)
    ld (g_curve_dy+3),a
    ld a,(g_curve_dx)
    ld (g_curve_err),a
    ld a,(g_curve_dx+1)
    ld (g_curve_err+1),a
    ld a,(g_curve_dx+2)
    ld (g_curve_err+2),a
    ld a,(g_curve_dx+3)
    ld (g_curve_err+3),a
    or a
    ld a,(g_curve_dy)
    ld b,a
    ld a,(g_curve_err)
    adc a,b
    ld (g_curve_err),a
    ld a,(g_curve_dy+1)
    ld b,a
    ld a,(g_curve_err+1)
    adc a,b
    ld (g_curve_err+1),a
    ld a,(g_curve_dy+2)
    ld b,a
    ld a,(g_curve_err+2)
    adc a,b
    ld (g_curve_err+2),a
    ld a,(g_curve_dy+3)
    ld b,a
    ld a,(g_curve_err+3)
    adc a,b
    ld (g_curve_err+3),a
geo_curve_loop:
    call geo_emit_curve
    ld a,(g_curve_err)
    ld (g_curve_e2),a
    ld a,(g_curve_err+1)
    ld (g_curve_e2+1),a
    ld a,(g_curve_err+2)
    ld (g_curve_e2+2),a
    ld a,(g_curve_err+3)
    ld (g_curve_e2+3),a
    ld hl,g_curve_e2
    sla (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    inc hl
    rl (hl)
    ld a,(g_curve_dx+3)
    xor $80
    ld (g_cmp),a
    ld a,(g_curve_e2+3)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_71
    jp nz,geo_branch_72
    ld a,(g_curve_dx+2)
    ld (g_cmp),a
    ld a,(g_curve_e2+2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_71
    jp nz,geo_branch_72
    ld a,(g_curve_dx+1)
    ld (g_cmp),a
    ld a,(g_curve_e2+1)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_71
    jp nz,geo_branch_72
    ld a,(g_curve_dx)
    ld (g_cmp),a
    ld a,(g_curve_e2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_71
geo_branch_72:
    ld hl,(g_x)
    ld de,$0001
    add hl,de
    ld (g_x),hl
    or a
    ld a,(g_step_x)
    ld b,a
    ld a,(g_curve_dx)
    adc a,b
    ld (g_curve_dx),a
    ld a,(g_step_x+1)
    ld b,a
    ld a,(g_curve_dx+1)
    adc a,b
    ld (g_curve_dx+1),a
    ld a,(g_step_x+2)
    ld b,a
    ld a,(g_curve_dx+2)
    adc a,b
    ld (g_curve_dx+2),a
    ld a,(g_step_x+3)
    ld b,a
    ld a,(g_curve_dx+3)
    adc a,b
    ld (g_curve_dx+3),a
    or a
    ld a,(g_curve_dx)
    ld b,a
    ld a,(g_curve_err)
    adc a,b
    ld (g_curve_err),a
    ld a,(g_curve_dx+1)
    ld b,a
    ld a,(g_curve_err+1)
    adc a,b
    ld (g_curve_err+1),a
    ld a,(g_curve_dx+2)
    ld b,a
    ld a,(g_curve_err+2)
    adc a,b
    ld (g_curve_err+2),a
    ld a,(g_curve_dx+3)
    ld b,a
    ld a,(g_curve_err+3)
    adc a,b
    ld (g_curve_err+3),a
geo_branch_71:
    ld a,(g_curve_dy+3)
    xor $80
    ld (g_cmp),a
    ld a,(g_curve_e2+3)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_74
    jp nz,geo_branch_73
    ld a,(g_curve_dy+2)
    ld (g_cmp),a
    ld a,(g_curve_e2+2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_74
    jp nz,geo_branch_73
    ld a,(g_curve_dy+1)
    ld (g_cmp),a
    ld a,(g_curve_e2+1)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_74
    jp nz,geo_branch_73
    ld a,(g_curve_dy)
    ld (g_cmp),a
    ld a,(g_curve_e2)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_74
    jp nz,geo_branch_73
geo_branch_74:
    ld hl,(g_y)
    ld de,$0001
    add hl,de
    ld (g_y),hl
    or a
    ld a,(g_step_y)
    ld b,a
    ld a,(g_curve_dy)
    adc a,b
    ld (g_curve_dy),a
    ld a,(g_step_y+1)
    ld b,a
    ld a,(g_curve_dy+1)
    adc a,b
    ld (g_curve_dy+1),a
    ld a,(g_step_y+2)
    ld b,a
    ld a,(g_curve_dy+2)
    adc a,b
    ld (g_curve_dy+2),a
    ld a,(g_step_y+3)
    ld b,a
    ld a,(g_curve_dy+3)
    adc a,b
    ld (g_curve_dy+3),a
    or a
    ld a,(g_curve_dy)
    ld b,a
    ld a,(g_curve_err)
    adc a,b
    ld (g_curve_err),a
    ld a,(g_curve_dy+1)
    ld b,a
    ld a,(g_curve_err+1)
    adc a,b
    ld (g_curve_err+1),a
    ld a,(g_curve_dy+2)
    ld b,a
    ld a,(g_curve_err+2)
    adc a,b
    ld (g_curve_err+2),a
    ld a,(g_curve_dy+3)
    ld b,a
    ld a,(g_curve_err+3)
    adc a,b
    ld (g_curve_err+3),a
geo_branch_73:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_curve_loop
    jp nz,geo_branch_75
    ld a,$00
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_curve_loop
    jp z,geo_curve_loop
geo_branch_75:
    ld hl,$0000
    ld (g_x),hl
geo_curve_tip:
    ld a,(g_ry+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_76
    jp nz,geo_done
    ld a,(g_ry)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_76
    jp geo_done
geo_branch_76:
    ld hl,(g_y)
    ld de,$0001
    add hl,de
    ld (g_y),hl
    call geo_emit_curve
    jp geo_curve_tip
geo_curve_vertical:
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_ry)
    or a
    sbc hl,de
    ld (g_py),hl
    ld hl,(g_b)
    ld (g_bottom),hl
    ld hl,(g_bottom)
    ld de,(g_ry)
    add hl,de
    ld (g_bottom),hl
    call geo_vspan
    jp geo_done
geo_curve_horizontal:
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_rx)
    or a
    sbc hl,de
    ld (g_px),hl
    ld hl,(g_a)
    ld (g_right),hl
    ld hl,(g_right)
    ld de,(g_rx)
    add hl,de
    ld (g_right),hl
    call geo_span
    jp geo_done
geo_emit_curve:
    ld a,$00
    ld (g_cmp),a
    ld a,(g_fill)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_emit_fill
geo_branch_77:
; Axis tests remove duplicate symmetric pixels, including XOR at the centre.
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    or a
    sbc hl,de
    ld (g_px),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    add hl,de
    ld (g_py),hl
    call geo_point
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_79
    ld a,$00
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_79
    jp geo_branch_78
geo_branch_79:
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    add hl,de
    ld (g_px),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    add hl,de
    ld (g_py),hl
    call geo_point
geo_branch_78:
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_80
    ld a,$00
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_80
    jp geo_emit_return
geo_branch_80:
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    or a
    sbc hl,de
    ld (g_px),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    or a
    sbc hl,de
    ld (g_py),hl
    call geo_point
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_x+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_81
    ld a,$00
    ld (g_cmp),a
    ld a,(g_x)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_81
    jp geo_emit_return
geo_branch_81:
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    add hl,de
    ld (g_px),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    or a
    sbc hl,de
    ld (g_py),hl
    call geo_point
geo_emit_return:
    ret
geo_emit_fill:
; Only the first, widest extent for a row emits spans. Each row is written once.
    ld a,(g_last_y+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_82
    ld a,(g_last_y)
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_82
    jp geo_emit_return
geo_branch_82:
    ld hl,(g_y)
    ld (g_last_y),hl
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    add hl,de
    ld (g_px),hl
    ld hl,(g_a)
    ld (g_right),hl
    ld hl,(g_right)
    ld de,(g_x)
    or a
    sbc hl,de
    ld (g_right),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    add hl,de
    ld (g_py),hl
    call geo_span
    ld a,$00
    xor $80
    ld (g_cmp),a
    ld a,(g_y+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_83
    ld a,$00
    ld (g_cmp),a
    ld a,(g_y)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp nz,geo_branch_83
    jp geo_emit_return
geo_branch_83:
    ld hl,(g_a)
    ld (g_px),hl
    ld hl,(g_px)
    ld de,(g_x)
    add hl,de
    ld (g_px),hl
    ld hl,(g_a)
    ld (g_right),hl
    ld hl,(g_right)
    ld de,(g_x)
    or a
    sbc hl,de
    ld (g_right),hl
    ld hl,(g_b)
    ld (g_py),hl
    ld hl,(g_py)
    ld de,(g_y)
    or a
    sbc hl,de
    ld (g_py),hl
    call geo_span
    ret
geo_point:
; Scissor the original raster, preserving its decision phase outside the window.
    ld a,(g_clip_l+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_84
    ld a,(g_clip_l)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_84:
    ld a,(g_clip_r+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_85
    jp nz,geo_emit_return
    ld a,(g_clip_r)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_85
    jp geo_emit_return
geo_branch_85:
    ld a,(g_clip_t+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_86
    ld a,(g_clip_t)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_86:
    ld a,(g_clip_b+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_87
    jp nz,geo_emit_return
    ld a,(g_clip_b)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_87
    jp geo_emit_return
geo_branch_87:
    ld hl,(g_px)
    ld (bp_x0),hl
    ld hl,(g_py)
    ld (bp_y0),hl
    call geo_backend_inputs
    call bp_pixel
    jp geo_account
geo_span:
    ld a,(g_clip_t+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_88
    ld a,(g_clip_t)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_88:
    ld a,(g_clip_b+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_89
    jp nz,geo_emit_return
    ld a,(g_clip_b)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_89
    jp geo_emit_return
geo_branch_89:
    ld a,(g_clip_l+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_right+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_90
    ld a,(g_clip_l)
    ld (g_cmp),a
    ld a,(g_right)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_90:
    ld a,(g_clip_r+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_91
    jp nz,geo_emit_return
    ld a,(g_clip_r)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_91
    jp geo_emit_return
geo_branch_91:
    ld a,(g_clip_l+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_93
    jp nz,geo_branch_92
    ld a,(g_clip_l)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_93
    jp geo_branch_92
geo_branch_93:
    ld hl,(g_clip_l)
    ld (g_px),hl
geo_branch_92:
    ld a,(g_clip_r+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_right+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_94
    jp nz,geo_branch_95
    ld a,(g_clip_r)
    ld (g_cmp),a
    ld a,(g_right)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_94
geo_branch_95:
    ld hl,(g_clip_r)
    ld (g_right),hl
    ld hl,(g_right)
    ld de,$ffff
    add hl,de
    ld (g_right),hl
geo_branch_94:
    ld hl,(g_px)
    ld (bp_x0),hl
    ld hl,(g_py)
    ld (bp_y0),hl
    ld hl,(g_right)
    ld (bp_x1),hl
    ld hl,(bp_x1)
    ld de,$0001
    add hl,de
    ld (bp_x1),hl
    call geo_backend_inputs
    call bp_hspan
    jp geo_account
geo_vspan:
    ld a,(g_clip_l+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_96
    ld a,(g_clip_l)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_96:
    ld a,(g_clip_r+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_px+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_97
    jp nz,geo_emit_return
    ld a,(g_clip_r)
    ld (g_cmp),a
    ld a,(g_px)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_97
    jp geo_emit_return
geo_branch_97:
    ld a,(g_clip_t+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_bottom+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
    jp nz,geo_branch_98
    ld a,(g_clip_t)
    ld (g_cmp),a
    ld a,(g_bottom)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_emit_return
geo_branch_98:
    ld a,(g_clip_b+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_99
    jp nz,geo_emit_return
    ld a,(g_clip_b)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_99
    jp geo_emit_return
geo_branch_99:
    ld a,(g_clip_t+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_py+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_101
    jp nz,geo_branch_100
    ld a,(g_clip_t)
    ld (g_cmp),a
    ld a,(g_py)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_101
    jp geo_branch_100
geo_branch_101:
    ld hl,(g_clip_t)
    ld (g_py),hl
geo_branch_100:
    ld a,(g_clip_b+1)
    xor $80
    ld (g_cmp),a
    ld a,(g_bottom+1)
    xor $80
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_102
    jp nz,geo_branch_103
    ld a,(g_clip_b)
    ld (g_cmp),a
    ld a,(g_bottom)
    ld b,a
    ld a,(g_cmp)
    ld c,a
    ld a,b
    cp c
    jp c,geo_branch_102
geo_branch_103:
    ld hl,(g_clip_b)
    ld (g_bottom),hl
    ld hl,(g_bottom)
    ld de,$ffff
    add hl,de
    ld (g_bottom),hl
geo_branch_102:
    ld hl,(g_px)
    ld (bp_x0),hl
    ld hl,(g_py)
    ld (bp_y0),hl
    ld hl,(g_bottom)
    ld (bp_y1),hl
    ld hl,(bp_y1)
    ld de,$0001
    add hl,de
    ld (bp_y1),hl
    call geo_backend_inputs
    call bp_vspan
    jp geo_account
geo_backend_inputs:
    ld a,(g_op)
    ld (bp_op),a
    ld a,(g_pen)
    ld (bp_pen),a
    ret
geo_account:
    ld a,$00
    ld (g_work),a
    ld a,$00
    ld (g_work+1),a
    ld a,$00
    ld (g_work+2),a
    ld a,$00
    ld (g_work+3),a
    ld a,(bp_writes)
    ld (g_work),a
    ld a,(bp_writes+1)
    ld (g_work+1),a
    or a
    ld a,(g_work)
    ld b,a
    ld a,(g_writes)
    adc a,b
    ld (g_writes),a
    ld a,(g_work+1)
    ld b,a
    ld a,(g_writes+1)
    adc a,b
    ld (g_writes+1),a
    ld a,(g_work+2)
    ld b,a
    ld a,(g_writes+2)
    adc a,b
    ld (g_writes+2),a
    ld a,(g_work+3)
    ld b,a
    ld a,(g_writes+3)
    adc a,b
    ld (g_writes+3),a
    ld hl,(g_calls)
    ld de,$0001
    add hl,de
    ld (g_calls),hl
    ret
geometry_end:

What was verified

215 calls completed on this exact configuration using Asm198x 0.0.71 and Emu198x 0.39.0. Every call compares complete bitmap memory, neighbouring guards, public inputs, acceptance, byte writes and backend calls with an independent model. Cases cover all octants, reversal/ties, endpoint ownership, axes, zero radii, clipping, XOR, range extremes and rejected requests. Selected whole native frames match independently composed pixels. A wrong ellipse recurrence completed accepted draws and failed both memory and picture checks.

The radius bound also has a separate exhaustive arithmetic check: all 65,025 nonzero radius pairs fit the chosen 32-bit error path. That host check does not claim native execution of every pair. Real-hardware timing, stock-OS loading and tear-free live drawing remain separate.