two-phases

A program of Phases.

main.ngp

include "atari/800xl.ngp"

modules { "print.asm" "intro.asm" "level.asm" }

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.asm

.section
introText
        .byte "INTRO"
introTextEnd
.ends

.proc introStart
        ldx #<introText
        ldy #>introText
        lda #introTextEnd - introText
        jsr printLine
        .transition level
.endp

level.asm

; What the Phase is for: a map of ten rows, which is what waits in storage
; while the program is in `intro`. Thirty-seven characters is what a row of
; the screen holds once the OS's left margin is taken off.

levelWidth = 37

.section
levelMap
        .byte "#####################################"
        .byte "#...................................#"
        .byte "#...................................#"
        .byte "#......########.....................#"
        .byte "#......#......#.....................#"
        .byte "#......#......#.....................#"
        .byte "#......########.....................#"
        .byte "#...................................#"
        .byte "#...................................#"
        .byte "#####################################"
levelMapEnd
.ends

; A row at a time. `at` and `left` are Temporaries of this Proc, as chapter
; two's were, and `printLine` keeps neither of them.
.proc levelStart
at      .ztemp 2
left    .ztemp 1

        lda #<levelMap
        sta at
        lda #>levelMap
        sta at+1
        lda #( levelMapEnd - levelMap ) / levelWidth
        sta left
@row
        ldx at
        ldy at+1
        lda #levelWidth
        jsr printLine
        clc
        lda at
        adc #levelWidth
        sta at
        bcc @counted
        inc at+1
@counted
        dec left
        bne @row
@stop   jmp @stop
.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
ngaCurrentPhase
boot.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
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

  1. intro144 zero page, 16487 bytes
  2. level144 zero page, 16873 bytes
AddressBytesSectionModuleKindPhasesWaits
$0000–$007F128osZeroossection0..1
$0080–$00834diskAtdisksection0..1
$0084–$00852diskDstdisktemporary0..1
$0084–$00852ngaDstnga.transitiontemporary0..1
$0084–$00852ngaPtrnga.transitiontemporary0..1
$0086–$00872diskLeftdisktemporary0..1
$0086–$00872ngaOffsetnga.transitiontemporary0..1
$0086–$00872ngaValuenga.transitiontemporary0..1
$0088–$00892ngaEntrynga.transitiontemporary0..1
$008A–$008B2ngaFramePosnga.transitiontemporary0..1
$008C–$008C1ngaWantednga.transitiontemporary0..1
$008D–$008D1ngaCountnga.transitiontemporary0..1
$008E–$008E1ngaFrameUnitnga.transitiontemporary0..1
$008F–$008F1ngaUnitnga.transitiontemporary0..1
$0200–$06FF1280osRamossection0..1
$0700–$07056ngaBootRecordnga.bootsection0..1
$0706–$07B9180ngaBootLoadnga.bootproc0..1
$087D–$087F3ngaBootImagenga.bootsection0..1
$0880–$089219diskOpenStreamdiskproc0..1
$0893–$08AB25diskReadBytediskproc0..1
$08AC–$08D340diskFilldiskproc0..1
$08D4–$08EC25printLineprintproc0..1
$08ED–$08F15introTextintrosection0
$08F2–$08FB10ngaFrameOpennga.transitionproc0..1
$08FC–$08FC1ngaCurrentPhasenga.cellsection0..1
$08FD–$08FD1ngaBootIdlenga.bootproc0..1
$08FE–$08FE1ngaShowBasesnga.transformsproc0..1
$0900–$09FF256diskBufferdisksection0..1
$0A00–$0A2C45diskCopydiskproc0..1
$0A2D–$0A3E18introStartintroproc0
$0BC6–$0C0E73ngaTransitionnga.transitionproc0..1
$0C0F–$0C9F145ngaEnternga.transitionproc0..1
$0CA0–$0CA910ngaFrameSkipnga.transitionproc0..1
$0CAA–$0CB18ngaTransformnga.transformsproc0..1
$C000–$CFFF4096osRomLowossection0..1
$D800–$FFFF10240osRomHighossection0..1
$0084–$00852levelStart.atleveltemporary1
$0086–$00861levelStart.leftleveltemporary1
$0A2D–$0B9E370levelMaplevelsection1unit 0 +$0010 (copy, 372 B)
$0B9F–$0BC539levelStartlevelproc1unit 0 +$0184 (copy, 41 B)

The atr

183952 bytes, 12 segments, 0 unaccounted.

016container header
82983unused sectors
13802end of image
1382182570unused sectors
$0700–$087F 384 bytes at 16
$07001nga.boot.ngaBootRecordboot.asm
$07011nga.boot.ngaBootRecordboot.asm
$07022nga.boot.ngaBootRecordboot.asm
$07042nga.boot.ngaBootRecordboot.asm
$07062nga.boot.ngaBootLoadboot.asm
$07083nga.boot.ngaBootLoadboot.asm
$070B2nga.boot.ngaBootLoadboot.asm
$070D3nga.boot.ngaBootLoadboot.asm
$07103nga.boot.ngaBootLoadboot.asm
$07132nga.boot.ngaBootLoadboot.asm
$07153nga.boot.ngaBootLoadboot.asm
$07182nga.boot.ngaBootLoadboot.asm
$071A3nga.boot.ngaBootLoadboot.asm
$071D3nga.boot.ngaBootLoadboot.asm
$07202nga.boot.ngaBootLoadboot.asm
$07223nga.boot.ngaBootLoadboot.asm
$07252nga.boot.ngaBootLoadboot.asm
$07272nga.boot.ngaBootLoadboot.asm
$07292nga.boot.ngaBootLoadboot.asm
$072B2nga.boot.ngaBootLoadboot.asm
$072D3nga.boot.ngaBootLoadboot.asm
$07302nga.boot.ngaBootLoadboot.asm
$07323nga.boot.ngaBootLoadboot.asm
$07352nga.boot.ngaBootLoadboot.asm
$07371nga.boot.ngaBootLoadboot.asm
$07382nga.boot.ngaBootLoadboot.asm
$073A2nga.boot.ngaBootLoadboot.asm
$073C2nga.boot.ngaBootLoadboot.asm
$073E2nga.boot.ngaBootLoadboot.asm
$07402nga.boot.ngaBootLoadboot.asm
$07422nga.boot.ngaBootLoadboot.asm
$07442nga.boot.ngaBootLoadboot.asm
$07462nga.boot.ngaBootLoadboot.asm
$07482nga.boot.ngaBootLoadboot.asm
$074A2nga.boot.ngaBootLoadboot.asm
$074C2nga.boot.ngaBootLoadboot.asm
$074E2nga.boot.ngaBootLoadboot.asm
$07503nga.boot.ngaBootLoadboot.asm
$07532nga.boot.ngaBootLoadboot.asm
$07552nga.boot.ngaBootLoadboot.asm
$07572nga.boot.ngaBootLoadboot.asm
$07592nga.boot.ngaBootLoadboot.asm
$075B2nga.boot.ngaBootLoadboot.asm
$075D2nga.boot.ngaBootLoadboot.asm
$075F2nga.boot.ngaBootLoadboot.asm
$07612nga.boot.ngaBootLoadboot.asm
$07632nga.boot.ngaBootLoadboot.asm
$07653nga.boot.ngaBootLoadboot.asm
$07683nga.boot.ngaBootLoadboot.asm
$076B2nga.boot.ngaBootLoadboot.asm
$076D3nga.boot.ngaBootLoadboot.asm
$07702nga.boot.ngaBootLoadboot.asm
$07721nga.boot.ngaBootLoadboot.asm
$07731nga.boot.ngaBootLoadboot.asm
$07743nga.boot.ngaBootLoadboot.asm
$07772nga.boot.ngaBootLoadboot.asm
$07792nga.boot.ngaBootLoadboot.asm
$077B2nga.boot.ngaBootLoadboot.asm
$077D2nga.boot.ngaBootLoadboot.asm
$077F3nga.boot.ngaBootLoadboot.asm
$07822nga.boot.ngaBootLoadboot.asm
$07842nga.boot.ngaBootLoadboot.asm
$07862nga.boot.ngaBootLoadboot.asm
$07883nga.boot.ngaBootLoadboot.asm
$078B3nga.boot.ngaBootLoadboot.asm
$078E1nga.boot.ngaBootLoadboot.asm
$078F2nga.boot.ngaBootLoadboot.asm
$07912nga.boot.ngaBootLoadboot.asm
$07933nga.boot.ngaBootLoadboot.asm
$07962nga.boot.ngaBootLoadboot.asm
$07983nga.boot.ngaBootLoadboot.asm
$079B2nga.boot.ngaBootLoadboot.asm
$079D3nga.boot.ngaBootLoadboot.asm
$07A02nga.boot.ngaBootLoadboot.asm
$07A23nga.boot.ngaBootLoadboot.asm
$07A52nga.boot.ngaBootLoadboot.asm
$07A73nga.boot.ngaBootLoadboot.asm
$07AA2nga.boot.ngaBootLoadboot.asm
$07AC3nga.boot.ngaBootLoadboot.asm
$07AF2nga.boot.ngaBootLoadboot.asm
$07B13nga.boot.ngaBootLoadboot.asm
$07B43nga.boot.ngaBootLoadboot.asm
$07B72nga.boot.ngaBootLoadboot.asm
$07B91nga.boot.ngaBootLoadboot.asm
$07BA195storage image
$087D2nga.boot.ngaBootImageboot.asm
$087F1nga.boot.ngaBootImageboot.asm
Storage 3 images at 400
$000016frame
$0A2D372payload, copy
$0B9F41payload, copy
$0880–$08D3 84 bytes at 912
$08802disk.diskOpenStreamdisk.asm
$08822disk.diskOpenStreamdisk.asm
$08841disk.diskOpenStreamdisk.asm
$08851disk.diskOpenStreamdisk.asm
$08862disk.diskOpenStreamdisk.asm
$08882disk.diskOpenStreamdisk.asm
$088A2disk.diskOpenStreamdisk.asm
$088C2disk.diskOpenStreamdisk.asm
$088E2disk.diskOpenStreamdisk.asm
$08903disk.diskOpenStreamdisk.asm
$08932disk.diskReadBytedisk.asm
$08952disk.diskReadBytedisk.asm
$08972disk.diskReadBytedisk.asm
$08992disk.diskReadBytedisk.asm
$089B2disk.diskReadBytedisk.asm
$089D3disk.diskReadBytedisk.asm
$08A02disk.diskReadBytedisk.asm
$08A22disk.diskReadBytedisk.asm
$08A42disk.diskReadBytedisk.asm
$08A62disk.diskReadBytedisk.asm
$08A83disk.diskReadBytedisk.asm
$08AB1disk.diskReadBytedisk.asm
$08AC2disk.diskFilldisk.asm
$08AE2disk.diskFilldisk.asm
$08B02disk.diskFilldisk.asm
$08B23disk.diskFilldisk.asm
$08B52disk.diskFilldisk.asm
$08B73disk.diskFilldisk.asm
$08BA2disk.diskFilldisk.asm
$08BC3disk.diskFilldisk.asm
$08BF2disk.diskFilldisk.asm
$08C13disk.diskFilldisk.asm
$08C42disk.diskFilldisk.asm
$08C63disk.diskFilldisk.asm
$08C92disk.diskFilldisk.asm
$08CB3disk.diskFilldisk.asm
$08CE3disk.diskFilldisk.asm
$08D12disk.diskFilldisk.asm
$08D31disk.diskFilldisk.asm
$0A00–$0A2C 45 bytes at 1000
$0A002disk.diskCopydisk.asm
$0A022disk.diskCopydisk.asm
$0A043disk.diskCopydisk.asm
$0A072disk.diskCopydisk.asm
$0A093disk.diskCopydisk.asm
$0A0C2disk.diskCopydisk.asm
$0A0E2disk.diskCopydisk.asm
$0A102disk.diskCopydisk.asm
$0A122disk.diskCopydisk.asm
$0A143disk.diskCopydisk.asm
$0A172disk.diskCopydisk.asm
$0A192disk.diskCopydisk.asm
$0A1B2disk.diskCopydisk.asm
$0A1D2disk.diskCopydisk.asm
$0A1F2disk.diskCopydisk.asm
$0A212disk.diskCopydisk.asm
$0A232disk.diskCopydisk.asm
$0A252disk.diskCopydisk.asm
$0A272disk.diskCopydisk.asm
$0A293disk.diskCopydisk.asm
$0A2C1disk.diskCopydisk.asm
$08D4–$08F1 30 bytes at 1049
$08D43print.printLineprint.asm
$08D73print.printLineprint.asm
$08DA3print.printLineprint.asm
$08DD2print.printLineprint.asm
$08DF3print.printLineprint.asm
$08E22print.printLineprint.asm
$08E43print.printLineprint.asm
$08E72print.printLineprint.asm
$08E93print.printLineprint.asm
$08EC1print.printLineprint.asm
$08ED5intro.introTextintro.asm
$0A2D–$0A3E 18 bytes at 1083
$0A2D2intro.introStartintro.asm
$0A2F2intro.introStartintro.asm
$0A312intro.introStartintro.asm
$0A333intro.introStartintro.asm
$0A369intro.introStartintro.asm
$0BC6–$0C9F 218 bytes at 1105
$0BC61nga.transition.ngaTransitiontransition.asm
$0BC72nga.transition.ngaTransitiontransition.asm
$0BC91nga.transition.ngaTransitiontransition.asm
$0BCA2nga.transition.ngaTransitiontransition.asm
$0BCC2nga.transition.ngaTransitiontransition.asm
$0BCE2nga.transition.ngaTransitiontransition.asm
$0BD02nga.transition.ngaTransitiontransition.asm
$0BD21nga.transition.ngaTransitiontransition.asm
$0BD32nga.transition.ngaTransitiontransition.asm
$0BD52nga.transition.ngaTransitiontransition.asm
$0BD72nga.transition.ngaTransitiontransition.asm
$0BD91nga.transition.ngaTransitiontransition.asm
$0BDA2nga.transition.ngaTransitiontransition.asm
$0BDC2nga.transition.ngaTransitiontransition.asm
$0BDE2nga.transition.ngaTransitiontransition.asm
$0BE02nga.transition.ngaTransitiontransition.asm
$0BE22nga.transition.ngaTransitiontransition.asm
$0BE42nga.transition.ngaTransitiontransition.asm
$0BE61nga.transition.ngaTransitiontransition.asm
$0BE72nga.transition.ngaTransitiontransition.asm
$0BE92nga.transition.ngaTransitiontransition.asm
$0BEB3nga.transition.ngaTransitiontransition.asm
$0BEE2nga.transition.ngaTransitiontransition.asm
$0BF02nga.transition.ngaTransitiontransition.asm
$0BF21nga.transition.ngaTransitiontransition.asm
$0BF32nga.transition.ngaTransitiontransition.asm
$0BF52nga.transition.ngaTransitiontransition.asm
$0BF72nga.transition.ngaTransitiontransition.asm
$0BF92nga.transition.ngaTransitiontransition.asm
$0BFB2nga.transition.ngaTransitiontransition.asm
$0BFD3nga.transition.ngaTransitiontransition.asm
$0C001nga.transition.ngaTransitiontransition.asm
$0C012nga.transition.ngaTransitiontransition.asm
$0C032nga.transition.ngaTransitiontransition.asm
$0C051nga.transition.ngaTransitiontransition.asm
$0C062nga.transition.ngaTransitiontransition.asm
$0C082nga.transition.ngaTransitiontransition.asm
$0C0A1nga.transition.ngaTransitiontransition.asm
$0C0B2nga.transition.ngaTransitiontransition.asm
$0C0D2nga.transition.ngaTransitiontransition.asm
$0C0F3nga.transition.ngaEntertransition.asm
$0C123nga.transition.ngaEntertransition.asm
$0C152nga.transition.ngaEntertransition.asm
$0C173nga.transition.ngaEntertransition.asm
$0C1A2nga.transition.ngaEntertransition.asm
$0C1C3nga.transition.ngaEntertransition.asm
$0C1F2nga.transition.ngaEntertransition.asm
$0C212nga.transition.ngaEntertransition.asm
$0C233nga.transition.ngaEntertransition.asm
$0C262nga.transition.ngaEntertransition.asm
$0C282nga.transition.ngaEntertransition.asm
$0C2A3nga.transition.ngaEntertransition.asm
$0C2D3nga.transition.ngaEntertransition.asm
$0C302nga.transition.ngaEntertransition.asm
$0C323nga.transition.ngaEntertransition.asm
$0C352nga.transition.ngaEntertransition.asm
$0C373nga.transition.ngaEntertransition.asm
$0C3A2nga.transition.ngaEntertransition.asm
$0C3C3nga.transition.ngaEntertransition.asm
$0C3F2nga.transition.ngaEntertransition.asm
$0C413nga.transition.ngaEntertransition.asm
$0C442nga.transition.ngaEntertransition.asm
$0C463nga.transition.ngaEntertransition.asm
$0C491nga.transition.ngaEntertransition.asm
$0C4A2nga.transition.ngaEntertransition.asm
$0C4C3nga.transition.ngaEntertransition.asm
$0C4F2nga.transition.ngaEntertransition.asm
$0C512nga.transition.ngaEntertransition.asm
$0C532nga.transition.ngaEntertransition.asm
$0C553nga.transition.ngaEntertransition.asm
$0C581nga.transition.ngaEntertransition.asm
$0C592nga.transition.ngaEntertransition.asm
$0C5B2nga.transition.ngaEntertransition.asm
$0C5D3nga.transition.ngaEntertransition.asm
$0C602nga.transition.ngaEntertransition.asm
$0C623nga.transition.ngaEntertransition.asm
$0C653nga.transition.ngaEntertransition.asm
$0C683nga.transition.ngaEntertransition.asm
$0C6B2nga.transition.ngaEntertransition.asm
$0C6D2nga.transition.ngaEntertransition.asm
$0C6F2nga.transition.ngaEntertransition.asm
$0C713nga.transition.ngaEntertransition.asm
$0C742nga.transition.ngaEntertransition.asm
$0C763nga.transition.ngaEntertransition.asm
$0C792nga.transition.ngaEntertransition.asm
$0C7B3nga.transition.ngaEntertransition.asm
$0C7E2nga.transition.ngaEntertransition.asm
$0C803nga.transition.ngaEntertransition.asm
$0C832nga.transition.ngaEntertransition.asm
$0C852nga.transition.ngaEntertransition.asm
$0C872nga.transition.ngaEntertransition.asm
$0C892nga.transition.ngaEntertransition.asm
$0C8B1nga.transition.ngaEntertransition.asm
$0C8C2nga.transition.ngaEntertransition.asm
$0C8E2nga.transition.ngaEntertransition.asm
$0C902nga.transition.ngaEntertransition.asm
$0C923nga.transition.ngaEntertransition.asm
$0C953nga.transition.ngaEntertransition.asm
$0C982nga.transition.ngaEntertransition.asm
$0C9A3nga.transition.ngaEntertransition.asm
$0C9D3nga.transition.ngaEntertransition.asm
$08F2–$08FB 10 bytes at 1327
$08F22nga.transition.ngaFrameOpentransition.asm
$08F42nga.transition.ngaFrameOpentransition.asm
$08F62nga.transition.ngaFrameOpentransition.asm
$08F83nga.transition.ngaFrameOpentransition.asm
$08FB1nga.transition.ngaFrameOpentransition.asm
$0CA0–$0CA9 10 bytes at 1341
$0CA01nga.transition.ngaFrameSkiptransition.asm
$0CA12nga.transition.ngaFrameSkiptransition.asm
$0CA32nga.transition.ngaFrameSkiptransition.asm
$0CA52nga.transition.ngaFrameSkiptransition.asm
$0CA72nga.transition.ngaFrameSkiptransition.asm
$0CA91nga.transition.ngaFrameSkiptransition.asm
$08FC–$08FE 3 bytes at 1355
$08FC1nga.cell.ngaCurrentPhase
$08FD1nga.boot.ngaBootIdleboot.asm
$08FE1nga.transforms.ngaShowBasestransforms.asm
$0CAA–$0CB1 8 bytes at 1362
$0CAA2nga.transforms.ngaTransformtransforms.asm
$0CAC2nga.transforms.ngaTransformtransforms.asm
$0CAE3nga.transforms.ngaTransformtransforms.asm
$0CB11nga.transforms.ngaTransformtransforms.asm
$02E0–$02E1 2 bytes at 1374
$02E02runad