in-c
A program of Phases.
main.ngp
include "atari/800xl.ngp"
modules { "print.asm" "intro.ngc" "level.ngc" }
resident { print }
phase intro { needs intro then level entry introStart }
phase level { needs level entry levelStart }
entry intro
container atr
800xl.ngp project
; An Atari 800XL with a double-density disk drive: sixty-four kilobytes and no
; banking, so storage is the diskette's own sectors, which nothing maps into
; memory. There is no unit set and no Window for that reason, and the driver
; shows nothing. The Container is the `.atr`, a bootable diskette whose first
; three sectors the tool writes itself.
target {
cpu "6502"
; A diskette it boots, or a `.xex` a DOS loads. A Project naming any other
; is refused here rather than at the writer.
containers atr, xex
}
; The diskette's sectors, from the fourth: the boot record has the first three
; and the load image whatever is left after the storage a program uses. A unit
; is 256 sectors, which is the one size the `.atr` takes, and three of them
; reach past the 180 KB a double-density diskette holds. It is a ceiling and
; not a reservation: what the image costs is what the program's Payloads came
; to.
storage { units 3 size 65536 }
target {
region ram $0000 .. $06FF ram
; $0700 to $087F is the boot record's. It stands in no Region, so the solver
; never allocates there and the record's own pins are the author's word.
region $0880 .. $CFFF ram
region $D800 .. $FFFF ram
region stack $0100 .. $01FF reserved
region io $D000 .. $D7FF register
; A Project that wants more of the hardware by name declares it, as it
; declares the memory its DOS holds.
register PORTB $D301
}
; The OS, the driver for a diskette and the decoders the tool ships, listed as
; a file of the Project's own would be. Every Transition calls the driver and
; the decoders, so they are resident, and the OS is resident because the driver
; calls into it; a decoder nothing uses is dropped.
modules { "atari/os.asm" "atari/charsets.asm" "atari/disk.asm" "stream/zx0.asm" }
resident { os, charsets, disk, zx0 }
os.asm os
; The Atari OS as a Module: what it occupies while it is in memory, and the
; names a program reaches it by. Sections that hold no bytes, pinned where
; the OS lives, so the solver sees memory that is taken rather than a Region
; it may never allocate from — which is what lets a Phase that switches the
; ROM out have those addresses back, once there is a way to say so. See
; 0038 for why what occupies memory for a while is a Module, and 0051 for
; what this first cut settles and what it leaves.
;
; Every Section here is `root`: the OS and the hardware reach them with no
; Reference in any Chunk, so nothing else would keep them. None holds bytes,
; so none has a Payload and no Transition loads one.
;
; This is the OS as an XL or XE ships it. A machine whose OS lives elsewhere
; declares a Module of its own; the variant is what chooses.
.export RTCLOK, SDMCTL, SDLSTL, SDLSTH, CH, SETVBV, XITVBV
; The OS's half of the zero page. The program's own variables live above it,
; in the half the variant leaves to the solver.
.section zeropage at $0000, root
osZero
.res $12
RTCLOK .res 3 ; $0012: the frame counter, three bytes
.res $6B
.ends
; The OS's variables and buffers. The shadow registers are among them and
; are Labels here rather than registers of the variant: they are the OS's
; memory, which the OS copies to the hardware on every vertical blank, and
; a `register` Region inside this Section would be an address the Section
; may not cover.
.section absolute at $0200, root
osRam
.res $2F
SDMCTL .res 1 ; $022F: shadows DMACTL
SDLSTL .res 1 ; $0230: the display list address, low
SDLSTH .res 1 ; $0231: and high
.res $CA
CH .res 1 ; $02FC: the last key pressed, $FF for none
.res $403
.ends
; The ROM, in the two ranges the hardware registers leave between them, and
; the entry points a program calls in the second. The RAM underneath is what
; a Phase without the OS would be given.
.section absolute at $C000, root
osRomLow
.res $1000
.ends
.section absolute at $D800, root
osRomHigh
.res $C5C
SETVBV .res 3 ; $E45C: A = the stage, X/Y = the routine
.res 3
XITVBV .res 3 ; $E462: the end of a deferred routine
.res $1B9B
.ends
charsets.asm charsets
; The Atari's two codes for the same letters, as Charsets.
;
; `atascii` is what the character I/O takes: the machine's own code, with the
; graphics characters where an ASCII machine keeps its control codes, and the
; letters where ASCII has them — which is why an unprefixed literal of plain
; letters has always worked and why nothing else has. `\n` is `$9B`, the end of
; line, and not `$0A`.
;
; `screen` is what the display reads out of screen memory, which is the same
; letters at other numbers. A program that writes where the display looks
; rather than through the OS wants this one.
;
; The graphics characters are written here as the Unicode the box-drawing and
; block characters have, so a picture drawn in the source is the picture the
; machine draws. A letter in inverse video is one of those too — Unicode squares
; them off in a negative, and `atascii"PRESS 🆂"` is seven bytes with the last
; one inverse. A whole line of inverse text is better asked of a Charset
; derived from this one, `.charset bright : atascii ^ $80`, which is what a
; derivation is for. Two of them have no exact Unicode: `$02` and `$0D` are a
; quarter of a cell where the nearest character is an eighth, and are written
; as `▕` and `▔`. Everything else, the inverse entries included, was read off
; the machine's own font and matches it.
;
; This Module emits nothing: it is two names and two tables.
.export atascii, screen
; tag atascii
.charset atascii
"♥├▕┘┤┐╱╲◢▗◣▝▘▔▂▖♣┌─┼●▄▎┬┴▌└" = $00
"↑↓←→" = $1C
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" = $20
"♦" = $60
"abcdefghijklmnopqrstuvwxyz" = $61
"♠" = $7B
"│" = $7C
"\n" = $9B
; Inverse video is the same glyph with the bits the other way round, which
; for the blocks and the triangles is another character Unicode draws. Every
; one of these was read off the machine's own font and is exact; the rest of
; the inverse half has no glyph to be written as, and is reached by adding
; $80 or by a Charset derived from this one.
"▊" = $82 ; ▕
"◤" = $88 ; ◢
"▛" = $89 ; ▗
"◥" = $8A ; ◣
"▙" = $8B ; ▝
"▟" = $8C ; ▘
"▆" = $8D ; ▔
"▜" = $8F ; ▖
"▀" = $95 ; ▄
"▐" = $99 ; ▌
"█" = $A0 ; the space
; And the letters, which Unicode squares off in a negative.
"🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉" = $C1
.endch
; end atascii
.charset screen
" !\"#$%&'()*+,-./0123456789:;<=>?" = $00
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" = $20
"♥├▕┘┤┐╱╲◢▗◣▝▘▔▂▖♣┌─┼●▄▎┬┴▌└" = $40
"♦" = $60
"abcdefghijklmnopqrstuvwxyz" = $61
"♠" = $7B
"│" = $7C
; The same inverses, at the display's numbers.
"▊" = $C2
"◤" = $C8
"▛" = $C9
"◥" = $CA
"▙" = $CB
"▟" = $CC
"▆" = $CD
"▜" = $CF
"▀" = $D5
"▐" = $D9
"█" = $80
"🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉" = $A1
.endch
disk.asm disk
; The storage driver for a diskette: a unit of storage is 256 sectors of 256
; bytes, and unit zero begins at sector 4, the first after the boot record.
; That size is what makes the three bytes the model addresses storage with —
; a unit and a two-byte offset — the address the hardware takes, so this driver
; does no arithmetic: the unit is the sector number's high byte, the offset's
; high byte is its low one, and the offset's low byte is the position in the
; sector. Reading a sector is the OS's own disk handler, called through
; `DSKINV` with the request in the device control block, which is why this
; driver needs no register of its own and knows nothing about a drive.
;
; The medium maps nothing, so this driver names no Window: `stream`, `show`
; and `showAt` are not declared, and the two Procs that put base memory back
; after a Transition are a `rts`.
;
; Resident, since every Transition calls it; the variant lists it so.
DSKINV = $E453 ; the OS's disk handler: one sector per call
DUNIT = $0301 ; the drive, 1 for D1:
DCOMND = $0302 ; 'R' reads
DBUFLO = $0304
DBUFHI = $0305
DAUX1 = $030A ; the sector, two bytes
DAUX2 = $030B
diskFirst = 4 ; the sector unit zero begins at
.driver open diskOpen
.driver read diskRead
.transform copy diskCopy
.macro diskOpen
jsr diskOpenStream
.endm
.macro diskRead
jsr diskReadByte
.endm
; The stream: which sector the buffer holds, where in it the next byte is, and
; whether the position has run off its end. A plain Section rather than
; Temporaries, because the value has to survive between one call and the next
; while nothing here is running.
.section zeropage
diskAt .res 2
diskPos .res 1
diskNeed .res 1
.ends
; The sector the stream reads through. A Section of reservations alone, so it
; is in no Container and no load writes it. `within 256` keeps it off a page
; boundary: every byte of it is read as `diskBuffer,y`, and an index that
; carries into the next page costs a cycle.
.section absolute within 256
diskBuffer
.res 256
.ends
; A = the unit, X/Y = the offset in it: the stream stands there. An offset past
; the unit's end carries into the units after, since the routine counts on in a
; Frame without knowing where a unit ends — and here it carries by itself, the
; three bytes being one position that the sector number takes the top two of.
.proc diskOpenStream
stx diskPos
sta diskAt+1 ; the unit is the sector number's high byte
tya ; and the offset's high byte is its low one
clc
adc #<diskFirst
sta diskAt
lda diskAt+1
adc #>diskFirst
sta diskAt+1
jmp diskFill
.endp
; A = the next byte of the stream. X and Y are not preserved.
.proc diskReadByte
lda diskNeed
beq @have
inc diskAt
bne @next
inc diskAt+1
@next
jsr diskFill
@have
ldy diskPos
inc diskPos
bne @within
sty diskNeed ; Y is $FF here, and any non-zero will do
@within
lda diskBuffer,y
rts
.endp
; The sector in diskAt into the buffer, asked for again until the handler says
; it has it: a Transition that cannot read cannot go on, and the drive answered
; when the OS read the boot record off this diskette.
.proc diskFill
lda #0
sta diskNeed
lda diskAt
sta DAUX1
lda diskAt+1
sta DAUX2
@again
lda #1
sta DUNIT
lda #$52
sta DCOMND
lda #<diskBuffer
sta DBUFLO
lda #>diskBuffer
sta DBUFHI
jsr DSKINV
bmi @again
rts
.endp
; The decoder of `copy`: X/Y = the destination, the stream at the stored size
; and then the bytes. A byte at a time through the stream, because what this
; costs is the sector reads underneath it — a run copied out of the buffer
; would save a few hundred cycles against the tens of thousands a sector takes
; to arrive.
diskDst .ztemp 2
diskLeft .ztemp 2
.proc diskCopy
stx diskDst
sty diskDst+1
jsr diskReadByte
sta diskLeft
jsr diskReadByte
sta diskLeft+1
@byte
lda diskLeft
ora diskLeft+1
beq @done
jsr diskReadByte
ldy #0
sta (diskDst),y
inc diskDst
bne @counted
inc diskDst+1
@counted
lda diskLeft
bne @low
dec diskLeft+1
@low
dec diskLeft
jmp @byte
@done
rts
.endp
zx0.asm zx0
; The decoder of `zx0` over any driver's stream: Einar Saukas's ZX0, version
; 2's standard forward stream, decoded as the reference dzx0.c does, driven
; by the stream's own end marker. Written here from the format, not ported:
; nothing of the reference's text is in it, and it is licensed as everything
; under lib/ is, see lib/LICENSE. Entered with X/Y = the destination and the
; stream at the stored size, which it reads past. A match is copied a byte
; at a time, forward, from what was written, which is what makes an offset
; shorter than its length — the run — come out right. Resident, and outside
; the driver's window.
;
; zx0Bits the bit buffer, a sentinel one above the bits still unread
; zx0Offset the last offset
; zx0Length the length in hand, or an offset's MSB while one is read
; zx0Invert one while an offset's MSB is read, whose data bits the
; stream carries complemented; zero otherwise
.transform zx0 zx0Decode
zx0Dst .ztemp 2
zx0Src .ztemp 2
zx0Bits .ztemp 1
zx0Offset .ztemp 2
zx0Length .ztemp 2
zx0Invert .ztemp 1
; The next bit of the stream, in A as zero or one and in the Z flag. The
; buffer holds a sentinel above the unread bits, so shifting it to nothing is
; the signal to fetch the next byte and put the sentinel back below it.
.proc zx0Bit
asl zx0Bits
bne @have
nga.read
rol ; the carry the asl left is the sentinel
sta zx0Bits
@have
lda #0
rol
rts
.endp
; An interlaced Elias gamma value into zx0Length: a control bit says whether
; a data bit follows, and the value begins at one. Three Procs chained by
; `then` because the value after an offset is entered with its first control
; bit already read, at zx0EliasData: the first falls through into the
; second, and the second branches into the third, which the chain is what
; allows.
.proc zx0Elias
lda #1
sta zx0Length
lda #0
sta zx0Length+1
.endp then zx0EliasMore
.proc zx0EliasMore
jsr zx0Bit
beq zx0EliasData ; zero: a data bit follows
rts
.endp then zx0EliasData
.proc zx0EliasData
jsr zx0Bit
eor zx0Invert
lsr
rol zx0Length
rol zx0Length+1
jmp zx0EliasMore
.endp
; One byte written: the destination moves on and the length in hand comes
; down, leaving Z set when it reaches zero.
.proc zx0Step
inc zx0Dst
bne @moved
inc zx0Dst+1
@moved
lda zx0Length
bne @low
dec zx0Length+1
@low
dec zx0Length
lda zx0Length
ora zx0Length+1
rts
.endp
; The length in hand copied from zx0Dst less the last offset to zx0Dst.
.proc zx0Copy
sec
lda zx0Dst
sbc zx0Offset
sta zx0Src
lda zx0Dst+1
sbc zx0Offset+1
sta zx0Src+1
@byte
ldy #0
lda (zx0Src),y
sta (zx0Dst),y
inc zx0Src
bne @from
inc zx0Src+1
@from
jsr zx0Step
bne @byte
rts
.endp
.proc zx0Decode
stx zx0Dst
sty zx0Dst+1
nga.read ; the stored size, which the end marker makes unnecessary
nga.read
lda #$80
sta zx0Bits ; an empty buffer: the sentinel alone
lda #1
sta zx0Offset
lda #0
sta zx0Offset+1 ; the last offset begins at one
sta zx0Invert
@literals
jsr zx0Elias
@literal
nga.read
ldy #0
sta (zx0Dst),y
jsr zx0Step
bne @literal
jsr zx0Bit
bne @offset
jsr zx0Elias ; a match at the last offset
jsr zx0Copy
jsr zx0Bit
beq @literals
@offset
lda #1
sta zx0Invert
jsr zx0Elias ; the new offset's MSB, complemented in the stream
lda #0
sta zx0Invert
lda zx0Length
beq @done ; 256 is the end marker, and the one value with a low byte of zero
lsr ; offset = MSB * 128 - LSB / 2
sta zx0Offset+1
lda #0
ror
sta zx0Offset
nga.read
lsr ; the LSB's low bit is the length's first control bit
php
eor #$FF
sec
adc zx0Offset
sta zx0Offset
bcs @subtracted
dec zx0Offset+1
@subtracted
plp
lda #1
sta zx0Length
lda #0
sta zx0Length+1
bcs @counted ; a control bit of one: the value is one
jsr zx0EliasData
@counted
inc zx0Length ; a match at a new offset is one longer than written
bne @copy
inc zx0Length+1
@copy
jsr zx0Copy
jsr zx0Bit
bne @offset
jmp @literals
@done
rts
.endp
print.asm
; The one thing both Phases need: a line on the screen, through the character
; I/O the OS opens on channel 0 before a program starts. `PUTREC` writes a
; record, so it ends the line itself.
CIOV = $E456
ICCOM = $0342
ICBAL = $0344
ICBLL = $0348
PUTREC = 9
; Both Phases call it, so both have to see the name.
.export printLine
; Where the line stands, and how long it is. Declaring where the two arguments
; are is what lets a Module of C call this one as a function.
.proc printLine
.declare arg xy
.declare arg a
stx ICBAL
sty ICBAL+1
sta ICBLL
lda #0
sta ICBLL+1
lda #PUTREC
sta ICCOM
ldx #0
jsr CIOV
rts
.endp
intro.ngc
static const u8 introText[] = "INTRO";
void introStart()
{
printLine( (u16)introText, sizeof( introText ) - 1 );
[[transition(level)]] return;
}
intro, as the tool compiled it intro
.source "intro.ngc", 1
.section readonly
introText
.byte 73, 78, 84, 82, 79, 0
.ends
.source "intro.ngc", 3
.export introStart
.proc introStart
.source "intro.ngc", 5
ldx #<introText
ldy #>introText
lda #5
jsr printLine
.source "intro.ngc", 6
.transition level
.endp
level.ngc
static const u8 levelMap[] =
"#####################################"
"#...................................#"
"#...................................#"
"#......########.....................#"
"#......#......#.....................#"
"#......#......#.....................#"
"#......########.....................#"
"#...................................#"
"#...................................#"
"#####################################";
void levelStart()
{
u16 at = (u16)levelMap;
for ( u8 left = 10; left != 0; --left )
{
printLine( at, 37 );
at += 37;
}
for ( ;; )
{
}
}
level, as the tool compiled it level
.source "level.ngc", 1
.section readonly
levelMap
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 35, 35, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 35, 35
.byte 46, 46, 46, 46, 46, 46, 35, 35
.byte 35, 35, 35, 35, 35, 35, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 35, 35, 46, 46, 46
.byte 46, 46, 46, 35, 46, 46, 46, 46
.byte 46, 46, 35, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 35, 35, 46, 46, 46, 46, 46, 46
.byte 35, 46, 46, 46, 46, 46, 46, 35
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 35, 35, 46
.byte 46, 46, 46, 46, 46, 35, 35, 35
.byte 35, 35, 35, 35, 35, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 35, 35, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 35
.byte 35, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 46, 46, 46, 46
.byte 46, 46, 46, 46, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 35, 35, 35, 35, 35, 35
.byte 35, 35, 0
.ends
.source "level.ngc", 13
.export levelStart
.proc levelStart
.source "level.ngc", 15
lda #<levelMap
sta __0at
lda #>levelMap
sta __0at+1
.source "level.ngc", 16
lda #10
sta __1left
@l1
lda __1left
jeq @l5
.source "level.ngc", 18
ldx __0at
ldy __0at+1
lda #37
jsr printLine
.source "level.ngc", 19
clc
lda __0at
adc #37
sta __0at
lda __0at+1
adc #0
sta __0at+1
.source "level.ngc", 16
dec __1left
jmp @l1
@l4
@l5
.source "level.ngc", 21
jmp @l4
__0at .ztemp 2
__1left .ztemp 1
.endp
transition.asm nga.transition
; The Transition routine. Called by `jsr` from a `.transition`, with the
; statement's list right behind the call: the Phase to enter, and per Phase
; the code may be in, the unit and offset where the edge's Frame waits.
; nga.open and nga.read are the driver's roles, macros of the driver's
; Module expanded here; ngaShowBases is the Proc the tool generates beside
; the dispatcher, which reads from the Frame the base the entered Phase gives
; every Window and shows it, the stream's last; and ngaCurrentPhase is the
; Cell the tool generates. Everything else the edge has to say waits in the
; Frame, in storage. See docs/spec/transition.md.
;
; Its zero page is Temporaries: nothing of it is needed once the routine has
; jumped to the entered Phase's entry, and a `.transition` is a jump into the
; routine, so whatever the statement's Section had on the zero page is dead by
; then too. Two Temporaries never live at once share an address — see
; docs/decisions/0034-trace.md.
.export ngaTransition
ngaPtr .ztemp 2
ngaEntry .ztemp 2
ngaDst .ztemp 2
ngaValue .ztemp 2
ngaOffset .ztemp 2
ngaFramePos .ztemp 2
ngaWanted .ztemp 1
ngaCount .ztemp 1
ngaFrameUnit .ztemp 1
ngaUnit .ztemp 1
.proc ngaTransition
pla
sta ngaPtr
pla
sta ngaPtr+1 ; the return address: one below the statement's list
ldy #1
lda (ngaPtr),y
sta ngaWanted ; the Phase to enter
iny
lda (ngaPtr),y
sta ngaCount ; entries that follow
lda ngaPtr
clc
adc #3
sta ngaPtr
bcc @scan
inc ngaPtr+1
@scan
lda ngaCount
bne @check
brk ; no entry for the current Phase: unreachable while the static rule holds
@check
ldy #0
lda (ngaPtr),y
cmp ngaCurrentPhase
beq @found
lda ngaPtr ; the next entry: four bytes on
clc
adc #4
sta ngaPtr
bcc @skipped
inc ngaPtr+1
@skipped
dec ngaCount
jmp @scan
@found
iny
lda (ngaPtr),y
sta ngaFrameUnit ; the unit the Frame waits in
iny
lda (ngaPtr),y
sta ngaFramePos
iny
lda (ngaPtr),y
sta ngaFramePos+1 ; and where in it
.endp then ngaEnter
; The rest of it, and the way in for a Container that has no current Phase to
; look one up by: ngaWanted holds the Phase to enter and ngaFrameUnit and
; ngaFramePos where its Frame waits. The cold start comes here — see
; docs/decisions/0216-a-car-names-its-format-and-the-cold-start-is-an-edge.md.
.export ngaEnter
.proc ngaEnter
jsr ngaFrameOpen
nga.read
sta ngaEntry
nga.read
sta ngaEntry+1
nga.read
sta ngaCount ; blocks that follow
lda #3
jsr ngaFrameSkip
@block
lda ngaCount
beq @cells
jsr ngaFrameOpen ; back to the Frame: a block's stream replaced it
nga.read
sta ngaUnit
nga.read
sta ngaOffset
nga.read
sta ngaOffset+1
nga.read
sta ngaDst
nga.read
sta ngaDst+1
nga.read
pha ; the decoder's number
lda #6
jsr ngaFrameSkip
lda ngaUnit
ldx ngaOffset
ldy ngaOffset+1
nga.open ; the block's stream: its stored size, then its bytes
pla
ldx ngaDst
ldy ngaDst+1
jsr ngaTransform
dec ngaCount
jmp @block
@cells
jsr ngaFrameOpen
nga.read
sta ngaCount ; Cell writes that follow, read in one stream
@cell
lda ngaCount
beq @enter
nga.read
sta ngaDst
nga.read
sta ngaDst+1
nga.read
sta ngaValue
nga.read
sta ngaValue+1
ldy #0
lda ngaValue
sta (ngaDst),y ; a Cell stands outside the driver's window
iny
lda ngaValue+1
sta (ngaDst),y
dec ngaCount
jmp @cell
@enter
jsr ngaShowBases ; the entered Phase's base in every Window, read from the Frame
lda ngaWanted
sta ngaCurrentPhase ; the Phase the program is in from here on
jmp (ngaEntry)
.endp
; The Frame's stream, from where the routine last left it.
.proc ngaFrameOpen
lda ngaFrameUnit
ldx ngaFramePos
ldy ngaFramePos+1
nga.open
rts
.endp
; A bytes on in the Frame, which is where its stream is opened next.
.proc ngaFrameSkip
clc
adc ngaFramePos
sta ngaFramePos
bcc @done
inc ngaFramePos+1
@done
rts
.endp
cell nga.cell
ngaCurrentPhaseboot.asm nga.boot
; The boot record of an .atr: the first three sectors, which the OS reads to
; $0700 and calls at $0706. Those three hold 128 bytes, because the OS boots
; with DSCTLN at 128; every sector after them holds 256, so the first thing the
; loader does is say so. What it loads is the load image, the stream of segments
; a `.xex` is made of with $FFFF closing it, waiting in the sectors after
; storage. See docs/spec/atr.md.
DSKINV = $E453
DUNIT = $0301
DCOMND = $0302
DBUFLO = $0304
DBUFHI = $0305
DAUX1 = $030A
DAUX2 = $030B
DSCTLN = $02D5 ; what a sector holds, which DSKINV transfers
RUNAD = $02E0
DOSVEC = $000A
bootBuf = $0400 ; the cassette buffer and the spare page after
; it, both free once the record is in: 256
; bytes, which is what a sector holds here
; The zero page the OS leaves once it has booted: RAMLO and CASINI were the
; boot's own scratch and TSTDAT is spare. TRAMSZ, $06, is not among them —
; coldstart reads it after the boot has returned.
bootDst = $0000 ; where the segment being read lands
bootLeft = $0002 ; bytes of it still to come
bootSector = $0004 ; the sector the buffer holds
bootPos = $0007 ; where in the buffer the stream stands, a byte
; that wraps because a sector holds 256
.section absolute at $0700, root
ngaBootRecord
.byte 0 ; the OS ignores this byte
.byte 3 ; sectors it reads, from the first
.word $0700 ; where it puts them
.root ; the OS calls what stands here
.word ngaBootIdle ; DOSINI, called once the load returns and on reset
.ends
; The first sector of the load image, which the tool writes here once it knows
; how much storage the program came to use, and one byte of the loader's own.
; At the end of the record, out of the loader's way; the zero page the OS
; leaves is spoken for, and $0006 is TRAMSZ, which the cold start reads after
; the boot has returned.
.export ngaBootImage
.section absolute at $087D, root
ngaBootImage
.word 0
bootNeed
.res 1 ; set once the position has wrapped: the next
.ends ; byte stands in the sector after
; The OS jsr's here once the three sectors stand at $0700, and a carry set on
; return is a boot it tries again.
.proc ngaBootLoad, absolute at $0706, root
lda #0 ; every sector from here on holds 256 bytes
sta DSCTLN
lda #1
sta DSCTLN+1
lda ngaBootImage
sta bootSector
lda ngaBootImage+1
sta bootSector+1
jsr fill
@segment
jsr byte
sta bootDst
jsr byte
sta bootDst+1
and bootDst ; $FFFF closes the stream, and no segment starts there
cmp #$FF
beq @run
jsr byte ; the end address, inclusive
sta bootLeft
jsr byte
sta bootLeft+1
sec ; the count is end - start + 1
lda bootLeft
sbc bootDst
sta bootLeft
lda bootLeft+1
sbc bootDst+1
sta bootLeft+1
inc bootLeft
bne @bytes
inc bootLeft+1
@bytes
lda bootLeft
ora bootLeft+1
beq @segment
jsr byte
ldy #0
sta (bootDst),y
inc bootDst
bne @counted
inc bootDst+1
@counted
lda bootLeft
bne @low
dec bootLeft+1
@low
dec bootLeft
jmp @bytes
@run
lda RUNAD ; where the program starts, which the OS jumps to
sta DOSVEC
lda RUNAD+1
sta DOSVEC+1
clc ; a boot the OS keeps
rts
; A = the next byte of the image, which runs from one sector into the next.
; The position wraps to zero at the sector's end, and bootNeed says the buffer
; no longer holds what stands there.
byte
lda bootNeed
beq @have
inc bootSector
bne @next
inc bootSector+1
@next
jsr fill
@have
ldy bootPos
inc bootPos
bne @within
sty bootNeed ; Y is $FF here, and any non-zero will do
@within
lda bootBuf,y
rts
; The sector in bootSector into the buffer, tried again until it is read: the
; OS read this record off this diskette, so the drive is there.
fill
lda #0
sta bootPos
sta bootNeed
lda bootSector
sta DAUX1
lda bootSector+1
sta DAUX2
@again
lda #1
sta DUNIT
lda #$52
sta DCOMND
lda #<bootBuf
sta DBUFLO
lda #>bootBuf
sta DBUFHI
jsr DSKINV
bmi @again
rts
.endp
; DOSINI: the OS calls it once the load has returned and on every reset, so it
; is in memory by then and the solver places it as it places any Proc.
.proc ngaBootIdle, root
rts
.endp
runtime.asm nga.runtime
__small = 0
; The C runtime: what `*`, `/` and `%` call where no constant makes them
; shifts, adds and masks. Each Proc is called as any Proc of the assembler is:
; its `.declare arg`s written, `jsr`, its `.declare ret` read. A product is the
; low half, one for either sign; a quotient and a remainder truncate towards
; zero, as C does. See docs/decisions/0095-literals-and-the-runtime.md.
.export __mul8, __udiv8, __umod8, __sdiv8, __smod8
.export __mul16, __udiv16, __umod16, __sdiv16, __smod16
.export __mul8to16
; ---------------------------------------------------------------------------
; 8 bits
; left * right: the bits of `left` from the top, the product doubled before
; each and `right` added where the bit is set. The product is left in `A`,
; where it was made — see docs/decisions/0146-the-runtime-in-registers.md.
.proc __mul8, absolute within 256
.declare arg u8
left .ztemp 1
.declare arg u8
right .ztemp 1
.declare ret a
lda #0
ldx #8
loop asl ; product * 2
asl left ; the next bit of `left` into the carry
bcc skip
clc
adc right
skip dex
bne loop
rts
.endp
; left * right, both bytes, the whole product in two: the bits of `left` from
; the bottom, `right` doubled after each and added where the bit is set, the
; product kept in `Y` below and `X` above. Done once no bit of `left` is left,
; so a `left` below 16 takes at most four rounds where every one took eight:
; the compiler hands the operand the ranges hold smaller as `left`. `right` is
; doubled at most seven times, so its pair never carries out of the top — see
; docs/decisions/0144-a-multiply-stops-when-its-multiplier-does.md, and
; docs/decisions/0122-a-multiply-of-two-bytes.md for why there is a Proc of
; two bytes at all. `left` comes in `A`, which is where it is walked — see
; docs/decisions/0146-the-runtime-in-registers.md.
;
; Shaped as Oscar64's `mul16by8` is, which the benchmarks measured it against
; — see docs/decisions/0154-a-multiply-shaped-by-its-bits.md. `A` always holds
; a set bit at `next`, so a clear bit needs no test for zero; the first bit,
; where set, makes the product `right` rather than adding it to zero; and the
; last set bit adds straight into `product`.
.proc __mul8to16, absolute within 256
.declare arg a
.declare arg u8
right .ztemp 1
.declare ret u16
product .ztemp 2
high .ztemp 1 ; `right`'s high byte as it is doubled
left .ztemp 1 ; what is left of `left`, while `A` adds
ldx #0
lsr ; bit 0 of `left` into the carry
beq last ; no bit above it: the product is `right` or 0
stx high
ldy #0
bcc next
ldy right ; bit 0 set: the product so far is `right`
bcs next ; always
add sta left ; what is left of it, while `A` adds
clc
tya
adc right
tay
txa
adc high
tax
lda left
next asl right
rol high
lsr ; the next bit into the carry
bcc next ; clear: `A` still holds a set bit
bne add ; set, and more above it
clc ; set, and the last: added into the product
tya
adc right
sta product
txa
adc high
sta product+1
rts
last bcc zero ; `A` and `X` are zero
lda right
zero sta product
stx product+1
rts
.endp
; dividend / divisor: the dividend shifted into the remainder in `A` a bit at a
; time, and the divisor subtracted where it fits, which sets the bit of the
; quotient the shift left clear in the dividend's byte. A carry out of the
; shift is a remainder of nine bits, which always fits. A divisor of zero
; always fits: the quotient is all ones.
;
; The quotient is built where the dividend was, a bit a turn, so that byte is
; both the argument and the result and its name says so — see
; docs/decisions/0119-one-temporary-carries-two-roles.md.
.proc __udiv8, absolute within 256
.declare arg u8
.declare ret u8
dividendAndQuotient .ztemp 1
.declare arg u8
divisor .ztemp 1
lda #0
ldx #8
loop asl dividendAndQuotient
rol ; the remainder takes the dividend's top bit
bcs subtract
cmp divisor
bcc skip
subtract
sbc divisor ; the carry is set on both ways here
inc dividendAndQuotient ; the quotient's bit
skip dex
bne loop
rts
.endp
; dividend % divisor: as `__udiv8`, the remainder kept. A divisor of zero leaves
; the dividend.
;
; The remainder is left in `A`, where it was made — see
; docs/decisions/0146-the-runtime-in-registers.md.
.proc __umod8, absolute within 256
.declare arg u8
dividend .ztemp 1
.declare arg u8
divisor .ztemp 1
.declare ret a
lda #0
ldx #8
loop asl dividend
rol
bcs subtract
cmp divisor
bcc skip
subtract
sbc divisor
inc dividend
skip dex
bne loop
rts
.endp
; dividend / divisor, signed: the unsigned quotient of the magnitudes, negated
; where the signs differ.
;
; The dividend comes in `A` and the quotient goes back in it — see
; docs/decisions/0146-the-runtime-in-registers.md.
.proc __sdiv8, absolute within 256
.declare ret a
.declare arg a
.declare arg i8
divisor .ztemp 1
sign .ztemp 1
tax ; the dividend, while the signs are compared
eor divisor
sta sign ; bit 7: the signs differ
txa
bpl dividendKept
eor #$FF
clc
adc #1
dividendKept
sta __udiv8.dividendAndQuotient
lda divisor
bpl divisorKept
eor #$FF
clc
adc #1
divisorKept
sta __udiv8.divisor
jsr __udiv8
lda __udiv8.dividendAndQuotient
bit sign
bpl done
eor #$FF
clc
adc #1
done rts
.endp
; dividend % divisor, signed: the unsigned remainder of the magnitudes, negated
; where the dividend is negative.
;
; The dividend comes in `A` and the remainder goes back in it, as it comes
; back from `__umod8` — see docs/decisions/0146-the-runtime-in-registers.md.
.proc __smod8, absolute within 256
.declare ret a
.declare arg a
.declare arg i8
divisor .ztemp 1
sign .ztemp 1
sta sign ; bit 7: the dividend is negative
tax ; `N` from the dividend: the caller promises none
bpl dividendKept
eor #$FF
clc
adc #1
dividendKept
sta __umod8.dividend
lda divisor
bpl divisorKept
eor #$FF
clc
adc #1
divisorKept
sta __umod8.divisor
jsr __umod8 ; the remainder in `A`
bit sign
bpl done
eor #$FF
clc
adc #1
done rts
.endp
; ---------------------------------------------------------------------------
; 16 bits
; left * right, the low half: the bits of `left` from the bottom, `right`
; doubled after each and added where the bit is set, the product kept in `Y`
; below and `high` above. Done once no bit of `left` is left, which is
; docs/decisions/0144-a-multiply-stops-when-its-multiplier-does.md carried from
; `__mul8to16` to the wider Proc: a multiplier whose high byte is zero takes at
; most eight rounds where every one took sixteen, and one of four bits takes
; four. The round is cheaper as well, `right` being doubled where
; the product was.
;
; `A` walks what is left of the multiplier. Over the low byte a set bit is
; rolled in above it first, so that `A` running out says the byte is done and
; not that the bits above are clear; over the high byte, which is walked at
; `narrow` and is where a multiplier of one byte starts, `A` running out ends
; the Proc. See docs/decisions/0170-a-wide-multiply-stops-when-its-multiplier-does.md.
.proc __mul16, absolute within 256
.declare arg u16
left .ztemp 2
.declare arg u16
right .ztemp 2
.declare ret u16
product .ztemp 2
high .ztemp 1 ; the product's high byte, while `Y` holds its low
.if __small
; Sixteen rounds, always, the multiplier walked from the bottom out of
; its own bytes and the product added up in memory: half the bytes of
; the shape above and none of what makes that one quick.
lda #0
sta product
sta product+1
ldx #16
@loop
lsr left+1
ror left
bcc @+skip
clc
lda product
adc right
sta product
lda product+1
adc right+1
sta product+1
@skip
asl right
rol right+1
dex
bne @-loop
rts
.else
ldy #0
sty high
lda left
ldx left+1
beq @+narrow ; one byte of multiplier: `A` holds all of it
sec
ror ; bit 0 out, and a set bit in above the rest
bcc @+lowNext
@lowAdd
tax ; what is left of the byte, while `A` adds
clc
tya
adc right
tay
lda high
adc right+1
sta high
txa
@lowNext
asl right
rol right+1
lsr ; the next bit into the carry
bcc @-lowNext ; clear: `A` still holds a set bit
bne @-lowAdd ; set, and more above it
lda left+1 ; set, and the mark: the low byte is done
@narrow
lsr
bcc @+next
@add
tax
clc
tya
adc right
tay
lda high
adc right+1
sta high
txa
@next
asl right
rol right+1
lsr
bcs @-add
bne @-next
sty product
lda high
sta product+1
rts
.endif
.endp
; dividend / divisor, as `__udiv8` with the remainder in `rest`: the quotient
; is built where the dividend was, so that byte is both the argument and the
; result — see docs/decisions/0119-one-temporary-carries-two-roles.md.
;
; A divisor of one byte below $80 takes the loop at `narrow`, where the
; remainder lives in `A` alone: it stays below the divisor, so doubling it and
; taking a bit of the dividend keeps it inside a byte, and the compare is one
; instruction rather than four. The dividend's own pair carries the quotient
; in at the bottom as it carries the next bit out at the top, so the two
; rotations serve both and nothing counts the bit separately. Half the cycles
; of the wide loop, and it is the loop every call of `prime` takes — see
; docs/decisions/0171-a-divisor-of-one-byte.md. A divisor of zero
; goes wide, which is where the quotient of all ones and the remainder of the
; dividend that docs/spec/c-subset.md states come from.
.proc __udiv16, absolute within 256
.declare arg u16
.declare ret u16
dividendAndQuotient .ztemp 2
.declare arg u16
divisor .ztemp 2
rest .ztemp 2
.if !__small
lda divisor+1
bne wide
lda divisor
beq wide
bmi wide ; $80 and above: a remainder of nine bits
ldx #16
asl dividendAndQuotient
rol dividendAndQuotient+1
lda #0
@narrow
rol ; the remainder takes the dividend's top bit
cmp divisor
bcc @+narrowSkip
sbc divisor ; the carry is set on both ways here
@narrowSkip
rol dividendAndQuotient ; the quotient's bit in, the next dividend bit out
rol dividendAndQuotient+1
dex
bne @-narrow
rts
.endif
wide lda #0
sta rest
sta rest+1
ldx #16
loop asl dividendAndQuotient
rol dividendAndQuotient+1
rol rest ; the remainder takes the dividend's top bit
rol rest+1
bcs subtract
lda rest ; rest >= divisor?
cmp divisor
lda rest+1
sbc divisor+1
bcc skip
subtract
lda rest
sec
sbc divisor
sta rest
lda rest+1
sbc divisor+1
sta rest+1
inc dividendAndQuotient ; the quotient's bit
skip dex
bne loop
rts
.endp
; dividend % divisor, as `__udiv16`, the remainder kept: it is built in the
; result's own bytes, and the dividend's, which hold the quotient nothing
; here wants, are the Proc's own. The narrow loop is `__udiv16`'s, and leaves
; in `A` the remainder that Proc throws away — see
; docs/decisions/0171-a-divisor-of-one-byte.md.
.proc __umod16, absolute within 256
.declare arg u16
dividend .ztemp 2
.declare arg u16
divisor .ztemp 2
.declare ret u16
remainder .ztemp 2
.if !__small
lda divisor+1
bne wide
lda divisor
beq wide
bmi wide ; $80 and above: a remainder of nine bits
lda #0
sta remainder+1 ; a remainder below the divisor is one byte
ldx #16
asl dividend
rol dividend+1
@narrow
rol
cmp divisor
bcc @+narrowSkip
sbc divisor
@narrowSkip
rol dividend
rol dividend+1
dex
bne @-narrow
sta remainder
rts
.endif
wide lda #0
sta remainder
sta remainder+1
ldx #16
loop asl dividend
rol dividend+1
rol remainder
rol remainder+1
bcs subtract
lda remainder
cmp divisor
lda remainder+1
sbc divisor+1
bcc skip
subtract
lda remainder
sec
sbc divisor
sta remainder
lda remainder+1
sbc divisor+1
sta remainder+1
inc dividend
skip dex
bne loop
rts
.endp
; dividend / divisor, signed, as `__sdiv8`.
.proc __sdiv16, absolute within 256
.declare arg i16
dividend .ztemp 2
.declare arg i16
divisor .ztemp 2
.declare ret i16
quotient .ztemp 2
sign .ztemp 1
lda dividend+1
eor divisor+1
sta sign ; bit 7: the signs differ
lda dividend+1
bmi dividendNegative
lda dividend
sta __udiv16.dividendAndQuotient
lda dividend+1
sta __udiv16.dividendAndQuotient+1
jmp dividendDone
dividendNegative
lda dividend
eor #$FF
clc
adc #1
sta __udiv16.dividendAndQuotient
lda dividend+1
eor #$FF
adc #0
sta __udiv16.dividendAndQuotient+1
dividendDone
lda divisor+1
bmi divisorNegative
lda divisor
sta __udiv16.divisor
lda divisor+1
sta __udiv16.divisor+1
jmp divisorDone
divisorNegative
lda divisor
eor #$FF
clc
adc #1
sta __udiv16.divisor
lda divisor+1
eor #$FF
adc #0
sta __udiv16.divisor+1
divisorDone
jsr __udiv16
bit sign
bmi negative
lda __udiv16.dividendAndQuotient
sta quotient
lda __udiv16.dividendAndQuotient+1
sta quotient+1
rts
negative
lda __udiv16.dividendAndQuotient
eor #$FF
clc
adc #1
sta quotient
lda __udiv16.dividendAndQuotient+1
eor #$FF
adc #0
sta quotient+1
rts
.endp
; dividend % divisor, signed, as `__smod8`.
.proc __smod16, absolute within 256
.declare arg i16
dividend .ztemp 2
.declare arg i16
divisor .ztemp 2
.declare ret i16
remainder .ztemp 2
sign .ztemp 1
lda dividend+1
sta sign ; bit 7: the dividend is negative
lda dividend+1
bmi dividendNegative
lda dividend
sta __umod16.dividend
lda dividend+1
sta __umod16.dividend+1
jmp dividendDone
dividendNegative
lda dividend
eor #$FF
clc
adc #1
sta __umod16.dividend
lda dividend+1
eor #$FF
adc #0
sta __umod16.dividend+1
dividendDone
lda divisor+1
bmi divisorNegative
lda divisor
sta __umod16.divisor
lda divisor+1
sta __umod16.divisor+1
jmp divisorDone
divisorNegative
lda divisor
eor #$FF
clc
adc #1
sta __umod16.divisor
lda divisor+1
eor #$FF
adc #0
sta __umod16.divisor+1
divisorDone
jsr __umod16
bit sign
bmi negative
lda __umod16.remainder
sta remainder
lda __umod16.remainder+1
sta remainder+1
rts
negative
lda __umod16.remainder
eor #$FF
clc
adc #1
sta remainder
lda __umod16.remainder+1
eor #$FF
adc #0
sta remainder+1
rts
.endp
transforms.asm nga.transforms
; The dispatcher over the decoders the program declared, in the order the
; tool numbered them. See docs/spec/transition.md.
.export ngaShowBases
.proc ngaShowBases
rts
.endp
.export ngaRestore
.proc ngaRestore
rts
.endp
.export ngaTransform
.proc ngaTransform
cmp #0
bne @not0
jmp diskCopy
@not0
brk
.endp
Memory map
- intro144 zero page, 16488 bytes
- level144 zero page, 16881 bytes
| Address | Bytes | Section | Module | Kind | Phases | Waits |
|---|---|---|---|---|---|---|
| $0000–$007F | 128 | osZero | os | section | 0..1 | |
| $0080–$0083 | 4 | diskAt | disk | section | 0..1 | |
| $0084–$0085 | 2 | diskDst | disk | temporary | 0..1 | |
| $0084–$0085 | 2 | ngaDst | nga.transition | temporary | 0..1 | |
| $0084–$0085 | 2 | ngaPtr | nga.transition | temporary | 0..1 | |
| $0086–$0087 | 2 | diskLeft | disk | temporary | 0..1 | |
| $0086–$0087 | 2 | ngaOffset | nga.transition | temporary | 0..1 | |
| $0086–$0087 | 2 | ngaValue | nga.transition | temporary | 0..1 | |
| $0088–$0089 | 2 | ngaEntry | nga.transition | temporary | 0..1 | |
| $008A–$008B | 2 | ngaFramePos | nga.transition | temporary | 0..1 | |
| $008C–$008C | 1 | ngaWanted | nga.transition | temporary | 0..1 | |
| $008D–$008D | 1 | ngaCount | nga.transition | temporary | 0..1 | |
| $008E–$008E | 1 | ngaFrameUnit | nga.transition | temporary | 0..1 | |
| $008F–$008F | 1 | ngaUnit | nga.transition | temporary | 0..1 | |
| $0200–$06FF | 1280 | osRam | os | section | 0..1 | |
| $0700–$0705 | 6 | ngaBootRecord | nga.boot | section | 0..1 | |
| $0706–$07B9 | 180 | ngaBootLoad | nga.boot | proc | 0..1 | |
| $087D–$087F | 3 | ngaBootImage | nga.boot | section | 0..1 | |
| $0880–$0892 | 19 | diskOpenStream | disk | proc | 0..1 | |
| $0893–$08AB | 25 | diskReadByte | disk | proc | 0..1 | |
| $08AC–$08D3 | 40 | diskFill | disk | proc | 0..1 | |
| $08D4–$08EC | 25 | printLine | proc | 0..1 | ||
| $08ED–$08F2 | 6 | introText | intro | section | 0 | |
| $08F3–$08FC | 10 | ngaFrameOpen | nga.transition | proc | 0..1 | |
| $08FD–$08FD | 1 | ngaCurrentPhase | nga.cell | section | 0..1 | |
| $08FE–$08FE | 1 | ngaBootIdle | nga.boot | proc | 0..1 | |
| $08FF–$08FF | 1 | ngaShowBases | nga.transforms | proc | 0..1 | |
| $0900–$09FF | 256 | diskBuffer | disk | section | 0..1 | |
| $0A00–$0A2C | 45 | diskCopy | disk | proc | 0..1 | |
| $0A2D–$0A3E | 18 | introStart | intro | proc | 0 | |
| $0BCE–$0C16 | 73 | ngaTransition | nga.transition | proc | 0..1 | |
| $0C17–$0CA7 | 145 | ngaEnter | nga.transition | proc | 0..1 | |
| $0CA8–$0CB1 | 10 | ngaFrameSkip | nga.transition | proc | 0..1 | |
| $0CB2–$0CB9 | 8 | ngaTransform | nga.transforms | proc | 0..1 | |
| $C000–$CFFF | 4096 | osRomLow | os | section | 0..1 | |
| $D800–$FFFF | 10240 | osRomHigh | os | section | 0..1 | |
| $0084–$0085 | 2 | levelStart.__0at | level | temporary | 1 | |
| $0086–$0086 | 1 | levelStart.__1left | level | temporary | 1 | |
| $0A2D–$0B9F | 371 | levelMap | level | section | 1 | unit 0 +$0010 (copy, 373 B) |
| $0BA0–$0BCD | 46 | levelStart | level | proc | 1 | unit 0 +$0185 (copy, 48 B) |
The atr
183952 bytes, 12 segments, 0 unaccounted.
| 0 | 16 | container header | |
| 837 | 75 | unused sectors | |
| 1381 | 2 | end of image | |
| 1383 | 182569 | unused sectors |
$0700–$087F 384 bytes at 16
| $0700 | 1 | nga.boot.ngaBootRecord | boot.asm |
| $0701 | 1 | nga.boot.ngaBootRecord | boot.asm |
| $0702 | 2 | nga.boot.ngaBootRecord | boot.asm |
| $0704 | 2 | nga.boot.ngaBootRecord | boot.asm |
| $0706 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0708 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $070B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $070D | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0710 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0713 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0715 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0718 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $071A | 3 | nga.boot.ngaBootLoad | boot.asm |
| $071D | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0720 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0722 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0725 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0727 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0729 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $072B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $072D | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0730 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0732 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0735 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0737 | 1 | nga.boot.ngaBootLoad | boot.asm |
| $0738 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $073A | 2 | nga.boot.ngaBootLoad | boot.asm |
| $073C | 2 | nga.boot.ngaBootLoad | boot.asm |
| $073E | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0740 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0742 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0744 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0746 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0748 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $074A | 2 | nga.boot.ngaBootLoad | boot.asm |
| $074C | 2 | nga.boot.ngaBootLoad | boot.asm |
| $074E | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0750 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0753 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0755 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0757 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0759 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $075B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $075D | 2 | nga.boot.ngaBootLoad | boot.asm |
| $075F | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0761 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0763 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0765 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0768 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $076B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $076D | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0770 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0772 | 1 | nga.boot.ngaBootLoad | boot.asm |
| $0773 | 1 | nga.boot.ngaBootLoad | boot.asm |
| $0774 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0777 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0779 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $077B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $077D | 2 | nga.boot.ngaBootLoad | boot.asm |
| $077F | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0782 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0784 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0786 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0788 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $078B | 3 | nga.boot.ngaBootLoad | boot.asm |
| $078E | 1 | nga.boot.ngaBootLoad | boot.asm |
| $078F | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0791 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0793 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $0796 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $0798 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $079B | 2 | nga.boot.ngaBootLoad | boot.asm |
| $079D | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07A0 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $07A2 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07A5 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $07A7 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07AA | 2 | nga.boot.ngaBootLoad | boot.asm |
| $07AC | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07AF | 2 | nga.boot.ngaBootLoad | boot.asm |
| $07B1 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07B4 | 3 | nga.boot.ngaBootLoad | boot.asm |
| $07B7 | 2 | nga.boot.ngaBootLoad | boot.asm |
| $07B9 | 1 | nga.boot.ngaBootLoad | boot.asm |
| $07BA | 195 | storage image | |
| $087D | 2 | nga.boot.ngaBootImage | boot.asm |
| $087F | 1 | nga.boot.ngaBootImage | boot.asm |
Storage 3 images at 400
| $0000 | 16 | frame | |
| $0A2D | 373 | payload, copy | |
| $0BA0 | 48 | payload, copy |
$0880–$08D3 84 bytes at 912
| $0880 | 2 | disk.diskOpenStream | disk.asm |
| $0882 | 2 | disk.diskOpenStream | disk.asm |
| $0884 | 1 | disk.diskOpenStream | disk.asm |
| $0885 | 1 | disk.diskOpenStream | disk.asm |
| $0886 | 2 | disk.diskOpenStream | disk.asm |
| $0888 | 2 | disk.diskOpenStream | disk.asm |
| $088A | 2 | disk.diskOpenStream | disk.asm |
| $088C | 2 | disk.diskOpenStream | disk.asm |
| $088E | 2 | disk.diskOpenStream | disk.asm |
| $0890 | 3 | disk.diskOpenStream | disk.asm |
| $0893 | 2 | disk.diskReadByte | disk.asm |
| $0895 | 2 | disk.diskReadByte | disk.asm |
| $0897 | 2 | disk.diskReadByte | disk.asm |
| $0899 | 2 | disk.diskReadByte | disk.asm |
| $089B | 2 | disk.diskReadByte | disk.asm |
| $089D | 3 | disk.diskReadByte | disk.asm |
| $08A0 | 2 | disk.diskReadByte | disk.asm |
| $08A2 | 2 | disk.diskReadByte | disk.asm |
| $08A4 | 2 | disk.diskReadByte | disk.asm |
| $08A6 | 2 | disk.diskReadByte | disk.asm |
| $08A8 | 3 | disk.diskReadByte | disk.asm |
| $08AB | 1 | disk.diskReadByte | disk.asm |
| $08AC | 2 | disk.diskFill | disk.asm |
| $08AE | 2 | disk.diskFill | disk.asm |
| $08B0 | 2 | disk.diskFill | disk.asm |
| $08B2 | 3 | disk.diskFill | disk.asm |
| $08B5 | 2 | disk.diskFill | disk.asm |
| $08B7 | 3 | disk.diskFill | disk.asm |
| $08BA | 2 | disk.diskFill | disk.asm |
| $08BC | 3 | disk.diskFill | disk.asm |
| $08BF | 2 | disk.diskFill | disk.asm |
| $08C1 | 3 | disk.diskFill | disk.asm |
| $08C4 | 2 | disk.diskFill | disk.asm |
| $08C6 | 3 | disk.diskFill | disk.asm |
| $08C9 | 2 | disk.diskFill | disk.asm |
| $08CB | 3 | disk.diskFill | disk.asm |
| $08CE | 3 | disk.diskFill | disk.asm |
| $08D1 | 2 | disk.diskFill | disk.asm |
| $08D3 | 1 | disk.diskFill | disk.asm |
$0A00–$0A2C 45 bytes at 1000
| $0A00 | 2 | disk.diskCopy | disk.asm |
| $0A02 | 2 | disk.diskCopy | disk.asm |
| $0A04 | 3 | disk.diskCopy | disk.asm |
| $0A07 | 2 | disk.diskCopy | disk.asm |
| $0A09 | 3 | disk.diskCopy | disk.asm |
| $0A0C | 2 | disk.diskCopy | disk.asm |
| $0A0E | 2 | disk.diskCopy | disk.asm |
| $0A10 | 2 | disk.diskCopy | disk.asm |
| $0A12 | 2 | disk.diskCopy | disk.asm |
| $0A14 | 3 | disk.diskCopy | disk.asm |
| $0A17 | 2 | disk.diskCopy | disk.asm |
| $0A19 | 2 | disk.diskCopy | disk.asm |
| $0A1B | 2 | disk.diskCopy | disk.asm |
| $0A1D | 2 | disk.diskCopy | disk.asm |
| $0A1F | 2 | disk.diskCopy | disk.asm |
| $0A21 | 2 | disk.diskCopy | disk.asm |
| $0A23 | 2 | disk.diskCopy | disk.asm |
| $0A25 | 2 | disk.diskCopy | disk.asm |
| $0A27 | 2 | disk.diskCopy | disk.asm |
| $0A29 | 3 | disk.diskCopy | disk.asm |
| $0A2C | 1 | disk.diskCopy | disk.asm |
$08D4–$08F2 31 bytes at 1049
| $08D4 | 3 | print.printLine | print.asm |
| $08D7 | 3 | print.printLine | print.asm |
| $08DA | 3 | print.printLine | print.asm |
| $08DD | 2 | print.printLine | print.asm |
| $08DF | 3 | print.printLine | print.asm |
| $08E2 | 2 | print.printLine | print.asm |
| $08E4 | 3 | print.printLine | print.asm |
| $08E7 | 2 | print.printLine | print.asm |
| $08E9 | 3 | print.printLine | print.asm |
| $08EC | 1 | print.printLine | print.asm |
| $08ED | 6 | intro.introText | intro.asm |
$0A2D–$0A3E 18 bytes at 1084
$0BCE–$0CA7 218 bytes at 1106
| $0BCE | 1 | nga.transition.ngaTransition | transition.asm |
| $0BCF | 2 | nga.transition.ngaTransition | transition.asm |
| $0BD1 | 1 | nga.transition.ngaTransition | transition.asm |
| $0BD2 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BD4 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BD6 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BD8 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BDA | 1 | nga.transition.ngaTransition | transition.asm |
| $0BDB | 2 | nga.transition.ngaTransition | transition.asm |
| $0BDD | 2 | nga.transition.ngaTransition | transition.asm |
| $0BDF | 2 | nga.transition.ngaTransition | transition.asm |
| $0BE1 | 1 | nga.transition.ngaTransition | transition.asm |
| $0BE2 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BE4 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BE6 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BE8 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BEA | 2 | nga.transition.ngaTransition | transition.asm |
| $0BEC | 2 | nga.transition.ngaTransition | transition.asm |
| $0BEE | 1 | nga.transition.ngaTransition | transition.asm |
| $0BEF | 2 | nga.transition.ngaTransition | transition.asm |
| $0BF1 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BF3 | 3 | nga.transition.ngaTransition | transition.asm |
| $0BF6 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BF8 | 2 | nga.transition.ngaTransition | transition.asm |
| $0BFA | 1 | nga.transition.ngaTransition | transition.asm |
| $0BFB | 2 | nga.transition.ngaTransition | transition.asm |
| $0BFD | 2 | nga.transition.ngaTransition | transition.asm |
| $0BFF | 2 | nga.transition.ngaTransition | transition.asm |
| $0C01 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C03 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C05 | 3 | nga.transition.ngaTransition | transition.asm |
| $0C08 | 1 | nga.transition.ngaTransition | transition.asm |
| $0C09 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C0B | 2 | nga.transition.ngaTransition | transition.asm |
| $0C0D | 1 | nga.transition.ngaTransition | transition.asm |
| $0C0E | 2 | nga.transition.ngaTransition | transition.asm |
| $0C10 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C12 | 1 | nga.transition.ngaTransition | transition.asm |
| $0C13 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C15 | 2 | nga.transition.ngaTransition | transition.asm |
| $0C17 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C1A | 3 | nga.transition.ngaEnter | transition.asm |
| $0C1D | 2 | nga.transition.ngaEnter | transition.asm |
| $0C1F | 3 | nga.transition.ngaEnter | transition.asm |
| $0C22 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C24 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C27 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C29 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C2B | 3 | nga.transition.ngaEnter | transition.asm |
| $0C2E | 2 | nga.transition.ngaEnter | transition.asm |
| $0C30 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C32 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C35 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C38 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C3A | 3 | nga.transition.ngaEnter | transition.asm |
| $0C3D | 2 | nga.transition.ngaEnter | transition.asm |
| $0C3F | 3 | nga.transition.ngaEnter | transition.asm |
| $0C42 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C44 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C47 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C49 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C4C | 2 | nga.transition.ngaEnter | transition.asm |
| $0C4E | 3 | nga.transition.ngaEnter | transition.asm |
| $0C51 | 1 | nga.transition.ngaEnter | transition.asm |
| $0C52 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C54 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C57 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C59 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C5B | 2 | nga.transition.ngaEnter | transition.asm |
| $0C5D | 3 | nga.transition.ngaEnter | transition.asm |
| $0C60 | 1 | nga.transition.ngaEnter | transition.asm |
| $0C61 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C63 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C65 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C68 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C6A | 3 | nga.transition.ngaEnter | transition.asm |
| $0C6D | 3 | nga.transition.ngaEnter | transition.asm |
| $0C70 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C73 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C75 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C77 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C79 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C7C | 2 | nga.transition.ngaEnter | transition.asm |
| $0C7E | 3 | nga.transition.ngaEnter | transition.asm |
| $0C81 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C83 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C86 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C88 | 3 | nga.transition.ngaEnter | transition.asm |
| $0C8B | 2 | nga.transition.ngaEnter | transition.asm |
| $0C8D | 2 | nga.transition.ngaEnter | transition.asm |
| $0C8F | 2 | nga.transition.ngaEnter | transition.asm |
| $0C91 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C93 | 1 | nga.transition.ngaEnter | transition.asm |
| $0C94 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C96 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C98 | 2 | nga.transition.ngaEnter | transition.asm |
| $0C9A | 3 | nga.transition.ngaEnter | transition.asm |
| $0C9D | 3 | nga.transition.ngaEnter | transition.asm |
| $0CA0 | 2 | nga.transition.ngaEnter | transition.asm |
| $0CA2 | 3 | nga.transition.ngaEnter | transition.asm |
| $0CA5 | 3 | nga.transition.ngaEnter | transition.asm |
$08F3–$08FC 10 bytes at 1328
| $08F3 | 2 | nga.transition.ngaFrameOpen | transition.asm |
| $08F5 | 2 | nga.transition.ngaFrameOpen | transition.asm |
| $08F7 | 2 | nga.transition.ngaFrameOpen | transition.asm |
| $08F9 | 3 | nga.transition.ngaFrameOpen | transition.asm |
| $08FC | 1 | nga.transition.ngaFrameOpen | transition.asm |
$0CA8–$0CB1 10 bytes at 1342
| $0CA8 | 1 | nga.transition.ngaFrameSkip | transition.asm |
| $0CA9 | 2 | nga.transition.ngaFrameSkip | transition.asm |
| $0CAB | 2 | nga.transition.ngaFrameSkip | transition.asm |
| $0CAD | 2 | nga.transition.ngaFrameSkip | transition.asm |
| $0CAF | 2 | nga.transition.ngaFrameSkip | transition.asm |
| $0CB1 | 1 | nga.transition.ngaFrameSkip | transition.asm |
$08FD–$08FF 3 bytes at 1356
| $08FD | 1 | nga.cell.ngaCurrentPhase | |
| $08FE | 1 | nga.boot.ngaBootIdle | boot.asm |
| $08FF | 1 | nga.transforms.ngaShowBases | transforms.asm |
$0CB2–$0CB9 8 bytes at 1363
| $0CB2 | 2 | nga.transforms.ngaTransform | transforms.asm |
| $0CB4 | 2 | nga.transforms.ngaTransform | transforms.asm |
| $0CB6 | 3 | nga.transforms.ngaTransform | transforms.asm |
| $0CB9 | 1 | nga.transforms.ngaTransform | transforms.asm |
$02E0–$02E1 2 bytes at 1375
| $02E0 | 2 | runad |