Skip to content

Draw a Native Region on Oric Atmos

Reusable Oric Atmos code for drawing a room, changed tile or strip of scenery, with explicit display ownership.

Open a door, break a wall, reveal the next stretch of scenery. The map changes first; this routine makes the picture catch up. Give it a rectangle of byte-map IDs and a destination, and it draws just that part of the world.

Oric Atmos native output showing a patterned map patch and a single-cell redraw
Original diagnostic in Emu198x 0.39.0. A 5×3 patch and a single-cell erase are visible; a separate logical bottom-right write is also checked in memory.

Six pixels, then a colour decision

Oric HIRES packs six pixels into each bitmap byte. Bit 6 must stay set for these picture bytes; clearing it can turn a byte into a serial attribute which changes how the rest of the line is displayed. Even a blank tile therefore contains $40, rather than zero.

This caller reserves the first two physical columns of every line for PAPER and INK. That leaves 38 drawable columns, each six pixels wide, and 25 eight-scanline rows. The scanline tables start at $A002+y*40, safely beyond those attribute slots. Do not use an eight-pixel X conversion from another machine’s map example: a cell here is 6×8.

The full bitmap occupies $A000–$BF3F: 200 lines of forty bytes. The separate bottom text window is blanked by the driver and left alone by the renderer. Its video setup selects HIRES and provides the attributes; the routine only copies encoded picture bytes.

Make it part of your project

Reserve the complete 32-byte NR context in zero page and clear decimal mode before calling. Define MAP, its width and height, TILES, TILE_COUNT, screen dimensions, CELL_BYTES=1, X_STEP=1 and the bitmap_lo/bitmap_hi tables, then include the maintained 6502 routine once. No scratch initialisation call is needed.

This example’s source map is twelve bytes wide and eight high. Each of its four pictures takes eight bytes. ID zero is an opaque blank, so it erases a cell rather than skipping it. Keep assets, tables and the map stable throughout the call. The complete caller below supplies working assets and setup; its absolute addresses are allocations to adapt, not addresses every game must adopt.

A/X/Y and flags are clobbered. Allow four stack bytes for the direct JSR and paint helper, plus your caller and interrupt work. The two address tables cost 400 bytes. Each accepted cell copies eight picture bytes; a 5×3 patch copies 120, in addition to the full ID preflight.

For more colour, design an attribute-aware renderer with a clear rule for which cells own colour changes. Copying arbitrary colour bytes through this pixel-only variant would break that rule. Masked sprites, clipped cameras and timed updates need their own contracts.

Draw the patch

These are the example caller’s allocations and constants. Use addresses which fit your own memory plan.

The caller owns its memory and display configuration

demo.asm, excerpt
NR=112
RESULT=1536
MAP=1264
MAP_W=12
MAP_H=8
TILE_COUNT=4
CELL_BYTES=1
SCREEN_COLS=38
SCREEN_ROWS=25
X_STEP=1

Supply six unsigned bytes: source column, source row, destination column, destination row, width and height. Then call region_draw. NR+6 is one for a completed draw 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, inspect the completion flag and continue with your own logic. The routine rejects zero sizes, invalid starts, source/destination overruns and IDs outside the configured tile set. It checks every selected ID before writing any destination bytes, so a bad ID cannot leave half a patch behind. It rejects rather than clips.

Keep the reusable code

Checked native 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 non-re-entrant. Your caller owns its memory, video setup and interrupt policy. Keep all inputs and display resources stable throughout the call. Drawing an opaque zero tile erases a cell; transparent sprites need masking or background restoration instead.

What was checked

All 18 calls completed on this machine in the original reset diagnostic. Fifteen rejected map/bounds/ID requests were also executed in isolation, each after the same three accepted draws. The complete destination and both mapped guards were compared with an independent model, and every native capture pixel was compared with a separate composition. Completed wrong-layout and unsafe-destination controls fail those checks.

The source selection crosses a CPU page boundary. Reserved attributes and allocation gaps are included in the memory comparison.

These results qualify the selected Emu198x 0.39.0 configuration and Asm198x 0.0.71 fixture. They do not establish original-hardware timing, stock-OS loading, interrupt coexistence or a whole-game frame budget. The complete caller keeps reset setup separate from reusable drawing. Copy counts above are not measured timing claims.