Skip to content

Draw a Native Region on Acorn Electron

Reusable Acorn Electron code for a room, changed tile or strip of scenery, with its display layout and ownership explicit.

A door opens, and one map cell changes. This routine redraws its picture without rebuilding the whole screen. The same call can paint a room or the strip of scenery revealed by a camera: choose a source rectangle and a destination, then let the machine’s renderer do the copying.

Acorn Electron native diagnostic showing a small patterned region, a single-cell redraw and a separate corner tile
Original reset diagnostic in Emu198x 0.39.0: MODE 4 bitmap. Complete display memory and every native pixel are checked separately.

Eight lines together

Electron MODE 4 gives you 320×256 logical pixels: forty 8×8 cells across and thirty-two down. The bitmap occupies $5800–$7FFF. Eight scanlines of a cell sit together; the next cell starts eight bytes later. Move down a character row and the address advances by 320 bytes.

The low/high scanline tables therefore hold $5800 + (y/8)*320 + (y mod 8). Set CELL_BYTES=1 and X_STEP=8. This is the same maintained 6502 bitmap renderer used on other machines, with Electron’s addresses made explicit. Its two 256-byte tables cost 512 bytes.

The original caller copies four eight-byte pictures to $0800, sets ULA mode/base/palette, and then draws. A zero tile is an opaque blank picture, so it erases a cell. A different screen base, mode or hardware-scroll layout needs matching tables; changing just one address constant is insufficient.

Reserve your workspace

Put all 32 bytes of NR in zero page and clear decimal mode before calling. Define the map, tile table, dimensions and scanline tables before including bitmap-regions/6502.inc once. The map has twelve byte IDs per row and eight rows; this readable bitmap variant allows 1–16 tile pictures. Allocations must fit accessible memory without wrapping or aliasing the destination.

A/X/Y and flags are clobbered. Four stack bytes cover the direct JSR and its paint helper, beyond your caller and interrupt needs. The six request bytes survive; scratch may change. No context initialisation call is needed.

A 5×3 patch copies 120 bitmap bytes, plus a complete ID preflight and address work. Cached map rows or longer span copies can save work; masks and clipping change the job. The native capture duplicates pixels horizontally into 640×256 output, while game coordinates remain 320×256. These counts are not measured cycle budgets.

Draw the rectangle

The constants belong to the original caller’s working memory plan. Choose allocations which fit your own project, keeping setup and drawing separate.

Caller-owned memory and display constants

demo.asm, excerpt
NR=$0070
RESULT=$0300
MAP=$04f0
MAP_W=12
MAP_H=8
TILE_COUNT=4
SCREEN_COLS=40
SCREEN_ROWS=32
SCREEN=$5800
CELL_BYTES=1
X_STEP=8
TILES=$0800

Supply six unsigned bytes at NR+0..5: source column, source row, destination column, destination row, width and height. Call region_draw; NR+6 is one for completion or zero for rejection. The six request bytes survive.

Draw a 5×3 patch from map cell (2,1) at display cell (4,3)

demo.asm, excerpt
case_0:
    lda #2
    sta NR+0
    lda #1
    sta NR+1
    lda #4
    sta NR+2
    lda #3
    sta NR+3
    lda #5
    sta NR+4
    lda #3
    sta NR+5
    jsr region_draw
    lda NR+6
    ora #128
    sta RESULT+1

The result store after the call belongs to the diagnostic. In a game, read the completion flag and carry on with your own logic. The routine rejects zero sizes, invalid starts, source/destination overruns and IDs outside the configured table. Every selected ID is checked before painting begins, so a bad tile cannot leave half a patch behind. This variant rejects rather than clips.

Keep the routine

Checked bitmap region drawing

6502.inc
; Caller defines NR (32 zero-page bytes), MAP/MAP_W/MAP_H, TILES,
; TILE_COUNT (1..16), CELL_BYTES (1/2), SCREEN_COLS/ROWS, X_STEP (1/2/8),
; and bitmap_lo/bitmap_hi (one address per pixel scanline).
; Decimal mode must be clear. A/X/Y/flags and NR+6..31 clobbered.
region_draw:
    lda #0
    sta NR+6
    lda NR+4
    bne bounds_nonzero_0
    rts
bounds_nonzero_0:
    lda NR+0
    cmp #MAP_W
    bcc bounds_start_0
    rts
bounds_start_0:
    lda #MAP_W
    sec
    sbc NR+0
    cmp NR+4
    bcs bounds_fit_0
    rts
bounds_fit_0:
    lda NR+5
    bne bounds_nonzero_1
    rts
bounds_nonzero_1:
    lda NR+1
    cmp #MAP_H
    bcc bounds_start_1
    rts
bounds_start_1:
    lda #MAP_H
    sec
    sbc NR+1
    cmp NR+5
    bcs bounds_fit_1
    rts
bounds_fit_1:
    lda NR+4
    bne bounds_nonzero_2
    rts
bounds_nonzero_2:
    lda NR+2
    cmp #SCREEN_COLS
    bcc bounds_start_2
    rts
bounds_start_2:
    lda #SCREEN_COLS
    sec
    sbc NR+2
    cmp NR+4
    bcs bounds_fit_2
    rts
bounds_fit_2:
    lda NR+5
    bne bounds_nonzero_3
    rts
bounds_nonzero_3:
    lda NR+3
    cmp #SCREEN_ROWS
    bcc bounds_start_3
    rts
bounds_start_3:
    lda #SCREEN_ROWS
    sec
    sbc NR+3
    cmp NR+5
    bcs bounds_fit_3
    rts
bounds_fit_3:
    lda #0
    sta NR+29
region_pass:
    lda #0
    sta NR+16
region_row:
    lda #0
    sta NR+17
region_cell:
    lda #<MAP
    sta NR+20
    lda #>MAP
    sta NR+21
    lda NR+1
    clc
    adc NR+16
    tax
    beq source_column
source_row:
    clc
    lda NR+20
    adc #MAP_W
    sta NR+20
    bcc source_no_carry
    inc NR+21
source_no_carry:
    dex
    bne source_row
source_column:
    lda NR
    clc
    adc NR+17
    tay
    lda (NR+20),y
    sta NR+19
    cmp #TILE_COUNT
    bcc region_id_ok
    rts
region_id_ok:
    lda NR+29
    beq region_next
    jsr region_paint
region_next:
    inc NR+17
    lda NR+17
    cmp NR+4
    beq region_row_done
    jmp region_cell
region_row_done:
    inc NR+16
    lda NR+16
    cmp NR+5
    beq region_pass_done
    jmp region_row
region_pass_done:
    lda NR+29
    bne region_success
    inc NR+29
    jmp region_pass
region_success:
    lda #1
    sta NR+6
    rts
region_paint:
    lda NR+19
    asl
    asl
    asl
!if CELL_BYTES = 2 {
    asl
}
    clc
    adc #<TILES
    sta NR+24
    lda #>TILES
    adc #0
    sta NR+25
    lda #0
    sta NR+18
region_scanline:
    lda NR+3
    clc
    adc NR+16
    asl
    asl
    asl
    clc
    adc NR+18
    tay
    lda bitmap_lo,y
    sta NR+22
    lda bitmap_hi,y
    sta NR+23
    lda #0
    sta NR+28
    lda NR+2
    clc
    adc NR+17
!if X_STEP >= 2 {
    asl
    rol NR+28
}
!if X_STEP = 8 {
    asl
    rol NR+28
    asl
    rol NR+28
}
    clc
    adc NR+22
    sta NR+22
    lda NR+23
    adc NR+28
    sta NR+23
    ldy #0
    lda (NR+24),y
    sta (NR+22),y
!if CELL_BYTES = 2 {
    iny
    lda (NR+24),y
    sta (NR+22),y
}
    clc
    lda NR+24
    adc #CELL_BYTES
    sta NR+24
    bcc paint_no_carry
    inc NR+25
paint_no_carry:
    inc NR+18
    lda NR+18
    cmp #8
    bne region_scanline
    rts

The context is private and non-re-entrant. Keep the request, map, descriptors or picture data, and display configuration stable throughout the call. Your caller owns video setup, allocations and interrupt policy; the routine does not disable interrupts or negotiate with an OS. Interrupt handlers must preserve the used registers and must not change those resources halfway through drawing.

What was checked

All 18 calls completed: three accepted draws and fifteen map/bounds/ID rejections. Each rejected request also ran in isolation after the same three accepted draws, preserving complete destination allocations and both mapped guards. The source selection crosses a CPU page boundary.

The observer compares every destination byte, including all bitmap bytes, with an independent model. It compares every native capture pixel with a separate composition, including borders and scaling. Completed wrong-layout and unsafe-destination controls fail the same memory checks; wrong-layout pictures also fail the pixel check.

These results qualify the selected Emu198x 0.39.0 configuration with Asm198x 0.0.71 and the original reset fixture. They do not establish stock-OS loading, original-hardware timing, interrupt coexistence, tear-free updates or throughput.

For individual dots and shapes rather than tile copies, pixels, spans and boxes supply separately verified reusable bitmap routines.