cartridge

A program of Cartridges.

main.ngp

include "atari/xegs128.ngp"

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

panes in switched { level }

resident { print, shared }

phase intro { needs intro  then level  entry introStart }
phase level { needs level             entry levelStart }
entry intro

container car "xegs128"
xegs128.ngp project
; An Atari 800XL holding a 128 KB XEGS cartridge: sixteen banks of eight
; kilobytes, of which the last is the fixed part the CPU always sees at
; $A000-$BFFF and the other fifteen are storage, brought into $8000-$9FFF one at
; a time by the byte written to $D5FF. A cartridge is ROM the CPU reads where it
; lies, so nothing is loaded and there is no DOS: the machine's whole RAM below
; the cartridge is the program's, $0700 included.
target {
  cpu "6502"

  ; The one Container a machine holding a cartridge takes. A `.xex` needs a DOS
  ; to load it, and this machine has none.
  containers car

  ; Fifteen and not sixteen: the sixteenth bank is the fixed part under another
  ; name, and one set of bytes with two names is one too many.
  units  banks 15
  window switched $8000 .. $9FFF  views banks
}
storage { units banks }

target {
  region ram    $0000 .. $7FFF  ram
  region stack  $0100 .. $01FF  reserved
  region io     $D000 .. $D7FF  register

  ; The part of the cartridge no switch takes away, which is where the solver
  ; puts everything nothing writes. $8000-$9FFF is in no Region at all: it is
  ; the window, it shows one Bank at a time, and only a Pane or storage stands
  ; there — as the boot record's page is in no Region of the diskette's variant.
  region cart   $A000 .. $BFFF  rom

  ; What the driver writes. Any address of $D500-$D5FF is the same register.
  register CARTSEL $D5FF
}

; The OS, the driver for this storage and the decoders the tool ships. Every
; Transition calls the driver and the decoders, so they are resident; a decoder
; nothing uses is dropped.
modules { "atari/os.asm"  "atari/charsets.asm"  "atari/cart.asm"  "stream/zx0.asm" }
resident { os, charsets, cart, 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
cart.asm cart
; The storage driver for a XEGS cartridge: the units are the Banks of the
; variant's `banks`, which the **byte written to `$D5FF`** brings into the
; $8000-$9FFF window — `switched` in the variant — and the stream is a pointer
; into that window that walks on to the next unit at its end. Each role is a
; macro declared with `.driver`, which the routine, the decoders and the tool's
; own Procs expand where they use it as `nga.open`, `nga.read`, `nga.show` and
; `nga.showAt`.
;
; A state of `switched` **is** a Bank number, so this driver holds no table
; where `portb.asm` holds one: the window has no named state to number before
; the Banks, since a cartridge of this kind cannot be switched off, and the
; board wires the byte written straight to the address lines above the window.
; Which is also why `show` is two instructions and nothing else.
;
; The register is written and never read — the board decodes an address and
; latches the data, and gives nothing back — so the bank a Transition left in
; is in this driver's own byte and nowhere else.
;
; Resident, and outside the window: the variant lists this Module in `resident`,
; and the tool holds it outside the window the stream reads through, since code
; that switched the window from inside it would switch itself away.

cartSelect     = $D5FF                  ; any address of $D500-$D5FF; the data is the bank
cartWindow     = $8000
cartWindowEnd  = cartWindow + $2000     ; one past the window: the high byte of the first address outside it
cartUnitPages  = $20                    ; a unit, in pages: what an offset carries by

.driver open    cartOpen
.driver read    cartRead
.driver stream  switched
.driver show    switched cartShow
.driver showAt  switched cartShowAt

.transform copy cartCopy

.macro cartOpen
        jsr cartOpenStream
.endm

.macro cartRead
        jsr cartReadByte
.endm

; The state to show, which is the Bank: the window shows nothing else.
.macro cartShow state
        lda #state
        sta cartSelect
.endm

; X = the state to show.
.macro cartShowAt
        stx cartSelect
.endm

; The stream: where the next byte is, and which unit is in. A plain zero-page
; Section rather than Temporaries, because the value has to survive between one
; call and the next while nothing here is running.
.section zeropage
cartPtr         .res 2
cartUnit        .res 1
.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: a unit is $2000 bytes, so the
; offset's top three bits are units.
.proc cartOpenStream
        sta cartUnit
        stx cartPtr
        tya
@carry
        cmp #cartUnitPages
        bcc @within
        sbc #cartUnitPages
        inc cartUnit
        jmp @carry
@within
        clc
        adc #>cartWindow
        sta cartPtr+1
        lda cartUnit
        sta cartSelect
        rts
.endp

; A = the next byte of the stream. X and Y are not preserved.
.proc cartReadByte
        ldy #0
        lda (cartPtr),y
        inc cartPtr
        bne @done
        inc cartPtr+1
        ldy cartPtr+1
        cpy #>cartWindowEnd
        bne @done
        pha
        jsr cartNextUnit
        pla
@done
        rts
.endp

; The window's end: the next unit in, and the pointer back at its start.
.proc cartNextUnit
        inc cartUnit
        lda cartUnit
        sta cartSelect
        lda #0
        sta cartPtr
        lda #>cartWindow
        sta cartPtr+1
        rts
.endp

; The decoder of `copy`: X/Y = the destination, the stream at the stored size
; and then the bytes. Reads the window through the pointer rather than through
; cartReadByte, which is what a driver's own decoder is for: a run at a time,
; where a run ends at the source's page end or at the size, so that the inner
; loop is `(zp),y` down to zero — the window's end is a page end, so a unit is
; never crossed inside a run.
cartDst  .ztemp 2
cartSize .ztemp 2
cartRun  .ztemp 1                       ; bytes in the run, zero for 256

.proc cartCopy
        stx cartDst
        sty cartDst+1
        jsr cartReadByte
        sta cartSize
        jsr cartReadByte
        sta cartSize+1
@run
        lda cartSize
        ora cartSize+1
        beq @done
        lda cartPtr                     ; to the end of the source's page
        eor #$FF
        clc
        adc #1
        sta cartRun
        lda cartSize+1
        bne @copy                       ; at least a page left: the run stands
        lda cartRun
        beq @cap                        ; a whole page, and less than one left
        cmp cartSize
        bcc @copy
@cap
        lda cartSize
        sta cartRun
@copy
        ldy cartRun
@byte
        dey
        lda (cartPtr),y
        sta (cartDst),y
        tya
        bne @byte
        ldx cartRun                     ; the run, as 256 where it is zero
        bne @counted
        inc cartPtr+1
        inc cartDst+1
        dec cartSize+1
        jmp @crossed
@counted
        txa
        clc
        adc cartPtr
        sta cartPtr
        bcc @source
        inc cartPtr+1
@source
        txa
        clc
        adc cartDst
        sta cartDst
        bcc @destination
        inc cartDst+1
@destination
        sec
        lda cartSize
        stx cartRun
        sbc cartRun
        sta cartSize
        bcs @crossed
        dec cartSize+1
@crossed
        lda cartPtr+1
        cmp #>cartWindowEnd
        bne @run
        jsr cartNextUnit                ; the window's end: the next unit in
        jmp @run
@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

shared.asm

; Nothing of the program uses this. It is pinned into the Window's range to
; show what the rule permits: a Section in no Pane is in the Window's base
; state, and the Pane's Sections are in another, so the two never collide
; however their Phases overlap. `root`, because nothing names it.

.section absolute at $4000, root
spare
        .res 64
.ends

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.
;
; It lives in the Pane `level`, so it stands in a Bank and is read there.

levelWidth = 37

; tag section
.section in level
levelMap
        .byte "#####################################"
        .byte "#...................................#"
        .byte "#...................................#"
        .byte "#......########.....................#"
        .byte "#......#......#.....................#"
        .byte "#......#......#.....................#"
        .byte "#......########.....................#"
        .byte "#...................................#"
        .byte "#...................................#"
        .byte "#####################################"
levelMapEnd
.ends
; end section

; tag draw
; A Trampoline: it stands in base RAM, out of the Window, but runs with `level`
; shown — which is what lets it name what the Pane holds. Chapter five's loop,
; moved here whole.
.proc drawMap, under level
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
        rts
.endp

.proc levelStart
        .with level
        jsr drawMap
@stop   jmp @stop
.endp
; end draw
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

; Where the cold start's Frame waits: a unit and an offset in it, which the
; Container writes here once PlaceStorage has put it somewhere. `readonly`
; because nothing writes it and it belongs in the ROM a cartridge is.
.export ngaColdFrame
.section absolute, readonly
ngaColdFrame
        .byte 0                 ; the unit
        .word 0                 ; and where in it
.ends

; What a cartridge's run vector points at, and what the OS therefore jumps to:
; the entry Phase is entered as any Phase is entered, by the routine, from a
; Frame. `root`, since the hardware reaches it and no Chunk of the program does,
; and exported because the Container writes its address into the run vector.
.export ngaStart
.proc ngaStart, root
        lda #0
        sta ngaWanted
        lda ngaColdFrame
        sta ngaFrameUnit
        lda ngaColdFrame+1
        sta ngaFramePos
        lda ngaColdFrame+2
        sta ngaFramePos+1
        jmp ngaEnter
.endp
cell nga.cell
ngaCurrentPhase
cart.asm nga.cart
; The cartridge header: the six bytes at $BFFA the OS reads before it runs
; anything of a cartridge. $BFFC being zero is what says one is there, and the
; OS then tests that $BFFB cannot be written, which it cannot, being ROM. See
; docs/spec/car.md.

.export ngaCartStart
.section absolute at $BFFA, root, readonly
ngaCartStart
        .word 0                 ; the program's start, which the Container
                                ; patches once addresses exist: the OS jumps
                                ; through it after E: is open on IOCB 0, and
                                ; again on every reset
        .byte 0                 ; $BFFC: a cartridge is here
        .byte $04               ; $BFFD: started through $BFFA, no disk boot,
                                ; and not the diagnostic cartridge bit 7 would
                                ; make it
        .root
        .word ngaCartInit       ; $BFFE: called in the middle of the cold start
.ends

; The init vector points here and here does nothing on purpose: it is called
; before the OS has finished its own cold start and before E: exists, so
; anything run there would run in a machine half set up.
.proc ngaCartInit, 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 cartCopy
@not0
        brk
.endp

Memory map

  1. intro152 zero page, 15686 bytes
  2. level153 zero page, 16051 bytes
AddressBytesSectionModuleKindPhasesWaits
$0000–$007F128osZeroossection0..1
$0080–$00823cartPtrcartsection0..1
$0083–$00842cartDstcarttemporary0..1
$0085–$00862cartSizecarttemporary0..1
$0087–$00871cartRuncarttemporary0..1
$0088–$00892ngaPtrnga.transitiontemporary0..1
$008B–$008C2ngaEntrynga.transitiontemporary0..1
$008D–$008E2ngaDstnga.transitiontemporary0..1
$008F–$00902ngaValuenga.transitiontemporary0..1
$0091–$00922ngaOffsetnga.transitiontemporary0..1
$0093–$00942ngaFramePosnga.transitiontemporary0..1
$0095–$00951ngaWantednga.transitiontemporary0..1
$0096–$00961ngaCountnga.transitiontemporary0..1
$0097–$00971ngaFrameUnitnga.transitiontemporary0..1
$0098–$00981ngaUnitnga.transitiontemporary0..1
$0200–$06FF1280osRamossection0..1
$0700–$07045introTextintrosection0bank 0 +$0014 (copy, 7 B)
$0705–$07051ngaCurrentPhasenga.cellsection0..1bank 0 +$001B (copy, 3 B)
$4000–$403F64sparesharedsection0..1
$A000–$A01A27cartOpenStreamcartproc0..1
$A01B–$A03022cartReadBytecartproc0..1
$A031–$A04016cartNextUnitcartproc0..1
$A041–$A0B2114cartCopycartproc0..1
$A0B3–$A0CB25printLineprintproc0..1
$A0CC–$A0DD18introStartintroproc0
$A10E–$A15673ngaTransitionnga.transitionproc0..1
$A157–$A1E7145ngaEnternga.transitionproc0..1
$A1E8–$A1F110ngaFrameOpennga.transitionproc0..1
$A1F2–$A1FB10ngaFrameSkipnga.transitionproc0..1
$A1FC–$A1FE3ngaColdFramenga.transitionsection0..1
$A1FF–$A21422ngaStartnga.transitionproc0..1
$A215–$A2151ngaCartInitnga.cartproc0..1
$A216–$A2161ngaShowBasesnga.transformsproc0..1
$A217–$A21E8ngaTransformnga.transformsproc0..1
$BFFA–$BFFF6ngaCartStartnga.cartsection0..1
$C000–$CFFF4096osRomLowossection0..1
$D800–$FFFF10240osRomHighossection0..1
$0088–$00892drawMap.atleveltemporary1
$008A–$008A1drawMap.leftleveltemporary1
$8000–$8171370levelMaplevelsection1
$A0DE–$A10237drawMaplevelproc1
$A103–$A10D11levelStartlevelproc1

The car

131088 bytes, 2 segments, 0 unaccounted.

016container header
46114658erased
1150747822erased
Storage 5 images at 16
$000016frame
$00004frame
$00007payload, copy
$00003payload, copy
$8000370pane
$A000–$BFFF 8192 bytes at 122896
$A0002cart.cartOpenStreamcart.asm
$A0022cart.cartOpenStreamcart.asm
$A0041cart.cartOpenStreamcart.asm
$A0052cart.cartOpenStreamcart.asm
$A0072cart.cartOpenStreamcart.asm
$A0092cart.cartOpenStreamcart.asm
$A00B2cart.cartOpenStreamcart.asm
$A00D3cart.cartOpenStreamcart.asm
$A0101cart.cartOpenStreamcart.asm
$A0112cart.cartOpenStreamcart.asm
$A0132cart.cartOpenStreamcart.asm
$A0152cart.cartOpenStreamcart.asm
$A0173cart.cartOpenStreamcart.asm
$A01A1cart.cartOpenStreamcart.asm
$A01B2cart.cartReadBytecart.asm
$A01D2cart.cartReadBytecart.asm
$A01F2cart.cartReadBytecart.asm
$A0212cart.cartReadBytecart.asm
$A0232cart.cartReadBytecart.asm
$A0252cart.cartReadBytecart.asm
$A0272cart.cartReadBytecart.asm
$A0292cart.cartReadBytecart.asm
$A02B1cart.cartReadBytecart.asm
$A02C3cart.cartReadBytecart.asm
$A02F1cart.cartReadBytecart.asm
$A0301cart.cartReadBytecart.asm
$A0312cart.cartNextUnitcart.asm
$A0332cart.cartNextUnitcart.asm
$A0353cart.cartNextUnitcart.asm
$A0382cart.cartNextUnitcart.asm
$A03A2cart.cartNextUnitcart.asm
$A03C2cart.cartNextUnitcart.asm
$A03E2cart.cartNextUnitcart.asm
$A0401cart.cartNextUnitcart.asm
$A0412cart.cartCopycart.asm
$A0432cart.cartCopycart.asm
$A0453cart.cartCopycart.asm
$A0482cart.cartCopycart.asm
$A04A3cart.cartCopycart.asm
$A04D2cart.cartCopycart.asm
$A04F2cart.cartCopycart.asm
$A0512cart.cartCopycart.asm
$A0532cart.cartCopycart.asm
$A0552cart.cartCopycart.asm
$A0572cart.cartCopycart.asm
$A0591cart.cartCopycart.asm
$A05A2cart.cartCopycart.asm
$A05C2cart.cartCopycart.asm
$A05E2cart.cartCopycart.asm
$A0602cart.cartCopycart.asm
$A0622cart.cartCopycart.asm
$A0642cart.cartCopycart.asm
$A0662cart.cartCopycart.asm
$A0682cart.cartCopycart.asm
$A06A2cart.cartCopycart.asm
$A06C2cart.cartCopycart.asm
$A06E2cart.cartCopycart.asm
$A0701cart.cartCopycart.asm
$A0712cart.cartCopycart.asm
$A0732cart.cartCopycart.asm
$A0751cart.cartCopycart.asm
$A0762cart.cartCopycart.asm
$A0782cart.cartCopycart.asm
$A07A2cart.cartCopycart.asm
$A07C2cart.cartCopycart.asm
$A07E2cart.cartCopycart.asm
$A0802cart.cartCopycart.asm
$A0823cart.cartCopycart.asm
$A0851cart.cartCopycart.asm
$A0861cart.cartCopycart.asm
$A0872cart.cartCopycart.asm
$A0892cart.cartCopycart.asm
$A08B2cart.cartCopycart.asm
$A08D2cart.cartCopycart.asm
$A08F1cart.cartCopycart.asm
$A0901cart.cartCopycart.asm
$A0912cart.cartCopycart.asm
$A0932cart.cartCopycart.asm
$A0952cart.cartCopycart.asm
$A0972cart.cartCopycart.asm
$A0991cart.cartCopycart.asm
$A09A2cart.cartCopycart.asm
$A09C2cart.cartCopycart.asm
$A09E2cart.cartCopycart.asm
$A0A02cart.cartCopycart.asm
$A0A22cart.cartCopycart.asm
$A0A42cart.cartCopycart.asm
$A0A62cart.cartCopycart.asm
$A0A82cart.cartCopycart.asm
$A0AA2cart.cartCopycart.asm
$A0AC3cart.cartCopycart.asm
$A0AF3cart.cartCopycart.asm
$A0B21cart.cartCopycart.asm
$A0B33print.printLineprint.asm
$A0B63print.printLineprint.asm
$A0B93print.printLineprint.asm
$A0BC2print.printLineprint.asm
$A0BE3print.printLineprint.asm
$A0C12print.printLineprint.asm
$A0C33print.printLineprint.asm
$A0C62print.printLineprint.asm
$A0C83print.printLineprint.asm
$A0CB1print.printLineprint.asm
$A0CC2intro.introStartintro.asm
$A0CE2intro.introStartintro.asm
$A0D02intro.introStartintro.asm
$A0D23intro.introStartintro.asm
$A0D59intro.introStartintro.asm
$A0DE48storage image
$A10E1nga.transition.ngaTransitiontransition.asm
$A10F2nga.transition.ngaTransitiontransition.asm
$A1111nga.transition.ngaTransitiontransition.asm
$A1122nga.transition.ngaTransitiontransition.asm
$A1142nga.transition.ngaTransitiontransition.asm
$A1162nga.transition.ngaTransitiontransition.asm
$A1182nga.transition.ngaTransitiontransition.asm
$A11A1nga.transition.ngaTransitiontransition.asm
$A11B2nga.transition.ngaTransitiontransition.asm
$A11D2nga.transition.ngaTransitiontransition.asm
$A11F2nga.transition.ngaTransitiontransition.asm
$A1211nga.transition.ngaTransitiontransition.asm
$A1222nga.transition.ngaTransitiontransition.asm
$A1242nga.transition.ngaTransitiontransition.asm
$A1262nga.transition.ngaTransitiontransition.asm
$A1282nga.transition.ngaTransitiontransition.asm
$A12A2nga.transition.ngaTransitiontransition.asm
$A12C2nga.transition.ngaTransitiontransition.asm
$A12E1nga.transition.ngaTransitiontransition.asm
$A12F2nga.transition.ngaTransitiontransition.asm
$A1312nga.transition.ngaTransitiontransition.asm
$A1333nga.transition.ngaTransitiontransition.asm
$A1362nga.transition.ngaTransitiontransition.asm
$A1382nga.transition.ngaTransitiontransition.asm
$A13A1nga.transition.ngaTransitiontransition.asm
$A13B2nga.transition.ngaTransitiontransition.asm
$A13D2nga.transition.ngaTransitiontransition.asm
$A13F2nga.transition.ngaTransitiontransition.asm
$A1412nga.transition.ngaTransitiontransition.asm
$A1432nga.transition.ngaTransitiontransition.asm
$A1453nga.transition.ngaTransitiontransition.asm
$A1481nga.transition.ngaTransitiontransition.asm
$A1492nga.transition.ngaTransitiontransition.asm
$A14B2nga.transition.ngaTransitiontransition.asm
$A14D1nga.transition.ngaTransitiontransition.asm
$A14E2nga.transition.ngaTransitiontransition.asm
$A1502nga.transition.ngaTransitiontransition.asm
$A1521nga.transition.ngaTransitiontransition.asm
$A1532nga.transition.ngaTransitiontransition.asm
$A1552nga.transition.ngaTransitiontransition.asm
$A1573nga.transition.ngaEntertransition.asm
$A15A3nga.transition.ngaEntertransition.asm
$A15D2nga.transition.ngaEntertransition.asm
$A15F3nga.transition.ngaEntertransition.asm
$A1622nga.transition.ngaEntertransition.asm
$A1643nga.transition.ngaEntertransition.asm
$A1672nga.transition.ngaEntertransition.asm
$A1692nga.transition.ngaEntertransition.asm
$A16B3nga.transition.ngaEntertransition.asm
$A16E2nga.transition.ngaEntertransition.asm
$A1702nga.transition.ngaEntertransition.asm
$A1723nga.transition.ngaEntertransition.asm
$A1753nga.transition.ngaEntertransition.asm
$A1782nga.transition.ngaEntertransition.asm
$A17A3nga.transition.ngaEntertransition.asm
$A17D2nga.transition.ngaEntertransition.asm
$A17F3nga.transition.ngaEntertransition.asm
$A1822nga.transition.ngaEntertransition.asm
$A1843nga.transition.ngaEntertransition.asm
$A1872nga.transition.ngaEntertransition.asm
$A1893nga.transition.ngaEntertransition.asm
$A18C2nga.transition.ngaEntertransition.asm
$A18E3nga.transition.ngaEntertransition.asm
$A1911nga.transition.ngaEntertransition.asm
$A1922nga.transition.ngaEntertransition.asm
$A1943nga.transition.ngaEntertransition.asm
$A1972nga.transition.ngaEntertransition.asm
$A1992nga.transition.ngaEntertransition.asm
$A19B2nga.transition.ngaEntertransition.asm
$A19D3nga.transition.ngaEntertransition.asm
$A1A01nga.transition.ngaEntertransition.asm
$A1A12nga.transition.ngaEntertransition.asm
$A1A32nga.transition.ngaEntertransition.asm
$A1A53nga.transition.ngaEntertransition.asm
$A1A82nga.transition.ngaEntertransition.asm
$A1AA3nga.transition.ngaEntertransition.asm
$A1AD3nga.transition.ngaEntertransition.asm
$A1B03nga.transition.ngaEntertransition.asm
$A1B32nga.transition.ngaEntertransition.asm
$A1B52nga.transition.ngaEntertransition.asm
$A1B72nga.transition.ngaEntertransition.asm
$A1B93nga.transition.ngaEntertransition.asm
$A1BC2nga.transition.ngaEntertransition.asm
$A1BE3nga.transition.ngaEntertransition.asm
$A1C12nga.transition.ngaEntertransition.asm
$A1C33nga.transition.ngaEntertransition.asm
$A1C62nga.transition.ngaEntertransition.asm
$A1C83nga.transition.ngaEntertransition.asm
$A1CB2nga.transition.ngaEntertransition.asm
$A1CD2nga.transition.ngaEntertransition.asm
$A1CF2nga.transition.ngaEntertransition.asm
$A1D12nga.transition.ngaEntertransition.asm
$A1D31nga.transition.ngaEntertransition.asm
$A1D42nga.transition.ngaEntertransition.asm
$A1D62nga.transition.ngaEntertransition.asm
$A1D82nga.transition.ngaEntertransition.asm
$A1DA3nga.transition.ngaEntertransition.asm
$A1DD3nga.transition.ngaEntertransition.asm
$A1E02nga.transition.ngaEntertransition.asm
$A1E23nga.transition.ngaEntertransition.asm
$A1E53nga.transition.ngaEntertransition.asm
$A1E82nga.transition.ngaFrameOpentransition.asm
$A1EA2nga.transition.ngaFrameOpentransition.asm
$A1EC2nga.transition.ngaFrameOpentransition.asm
$A1EE3nga.transition.ngaFrameOpentransition.asm
$A1F11nga.transition.ngaFrameOpentransition.asm
$A1F21nga.transition.ngaFrameSkiptransition.asm
$A1F32nga.transition.ngaFrameSkiptransition.asm
$A1F52nga.transition.ngaFrameSkiptransition.asm
$A1F72nga.transition.ngaFrameSkiptransition.asm
$A1F92nga.transition.ngaFrameSkiptransition.asm
$A1FB1nga.transition.ngaFrameSkiptransition.asm
$A1FC1nga.transition.ngaColdFrametransition.asm
$A1FD2nga.transition.ngaColdFrametransition.asm
$A1FF2nga.transition.ngaStarttransition.asm
$A2012nga.transition.ngaStarttransition.asm
$A2033nga.transition.ngaStarttransition.asm
$A2062nga.transition.ngaStarttransition.asm
$A2083nga.transition.ngaStarttransition.asm
$A20B2nga.transition.ngaStarttransition.asm
$A20D3nga.transition.ngaStarttransition.asm
$A2102nga.transition.ngaStarttransition.asm
$A2123nga.transition.ngaStarttransition.asm
$A2151nga.cart.ngaCartInitcart.asm
$A2161nga.transforms.ngaShowBasestransforms.asm
$A2172nga.transforms.ngaTransformtransforms.asm
$A2192nga.transforms.ngaTransformtransforms.asm
$A21B3nga.transforms.ngaTransformtransforms.asm
$A21E1nga.transforms.ngaTransformtransforms.asm
$A21F7643storage image
$BFFA2nga.cart.ngaCartStartcart.asm
$BFFC1nga.cart.ngaCartStartcart.asm
$BFFD1nga.cart.ngaCartStartcart.asm
$BFFE2nga.cart.ngaCartStartcart.asm