# Lode Runner (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Lode Runner (U) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `copilot/prg.s` in the repository. The rebuild is verified **byte-identical** to the original `prg.bin` at the PRG level. --- ## 1. Reset Vector — CPU $FFFC → $C000 **`Reset`** ($C000–$C029) On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization: | Step | Code | Purpose | |------|------|---------| | 1–2 | `sei` / `cld` | Disable interrupts, clear decimal mode | | 3–4 | `ldx #$FF` / `bne Label_C00A` | X=$FF, branch always taken (delay slot?) | | 5–6 | `txs` / `jmp $C012` | Initialize stack pointer to $01FF | | 7–8 | `ldy #$00` / `sty Zp00` / `sty Zp01` | Zp00/Zp01 = $0000 (RAM clear pointer) | | 9 | `tya` / `ldx #$08` | A=0, X=8 (8 pages to clear) | | 10–14 | `Label_C01B`: `sta (Zp00),y` / `iny` / `bne` / `inc Zp01` / `dex` / `bne` | Clear $0000–$07FF (8 pages of 256 bytes) | | 15 | `lda #$23` / `sta ZpE5` | ZpE5 ($E5) = $23 | | 16 | `jmp $C3A6` | Jump to main initialization | **No warm-start signature check** — full 2 KB clear every reset. --- ## 2. Interrupt Vectors — CPU $FFFA–$FFFF Located at PRG offset $3FFA (end of 16 KB bank): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C02C | `NmiHandler` | | RESET | $C000 | `Reset` | | IRQ | $FFF9 | `IrqHandler` (single `rti` — unused) | --- ## 3. NMI / Frame Heartbeat — `NmiHandler` ($C02C) The NMI fires once per frame and implements a two-path structure controlled by **Zp10** ($10): ``` NmiHandler ($C02C): pha / txa / pha / tya / pha ; preserve A,X,Y jsr $C27C ; common NMI setup lda Zp10 ($10) beq Label_C050 ; Zp10=0: Path A lda #$00 / sta Zp10 ; clear Zp10 lda Zp11 ($11) / tax beq Label_C043 ; Zp11=0: Path B bne Label_C050 ; Zp11≠0: Path A Label_C043: jsr $C18E ; Path B: PPU/game update lda #$20 / ldx #$00 / jsr $C178 ; PPU write ($2000 + X=0) jmp $C0A4 ; → main NMI exit Label_C050: ; Path A: Palette/sprite update lda #$3F / ldx #$08 / jsr $C178 ; PPUADDR=$3F08 (palette) inc Zp9E ($9E) ; frame sub-counter lda Zp9E / and #$1F / bne Label_C07B ; every 32 frames inc Zp9F ($9F) ; secondary counter lda Zp9F / and #$03 / tax ; 0-3 selector lda #$0F / jsr $C006 ; PPUDATA write lda $C39E,x / jsr $C006 ; palette data from table lda #$00 / jsr $C006 ; clear lda #$30 / jsr $C006 ; $30 Label_C07B: lda Zp9E / and #$07 / bne Label_C0A4 ; every 8 frames lda #$3F / ldx #$0C / jsr $C178 ; PPUADDR=$3F0C inc Zp72 ($72) ; tertiary counter lda Zp72 / and #$03 / tax ; 0-3 selector lda #$0F / jsr $C006 lda #$38 / jsr $C006 lda #$28 / jsr $C006 lda $C3A2,x / jsr $C006 ; sprite palette data Label_C0A4: ; Common NMI exit lda Zp03 ($03) / sta $2001 (PPUMASK) ; apply PPUMASK jsr $C1E1 ; sprite/OAM update lda ZpDB ($DB) beq Label_C0D1 ; if 0: skip gameplay lda Zp06 ($06) / and #$10 / beq Label_C0D1 ; bit 4 check jsr $C2C6 / jsr $DE9F / jsr $C1D7 ; gameplay logic jmp Label_C127 ; rti Label_C0BF: ; Alternate entry (ZpDB=0?) jsr $C1E1 lda Zp06 / and #$10 / beq Label_C0BF ; wait for bit 4 jsr $DE9F / jsr $C1D7 / jsr $C2B3 jmp Label_C127 Label_C0D1: lda ZpB7 ($B7) beq Label_C127 ; if 0: rti jsr $E271 ; level/game init inc ZpB9 / lda ZpB9 / cmp ZpB8 / bcc Label_C127 lda #$00 / sta ZpB9 ; reset counter ... (level transition logic) Label_C127: pla / tay / pla / tax / pla / rti ; restore, return ``` **Two NMI Modes:** - **Path A (Zp10≠0):** Palette/sprite updates, periodic palette cycling (every 8/32 frames), sprite DMA - **Path B (Zp10=0, Zp11=0):** `jsr $C18E` → PPU register update → main exit **Key ZP Flags:** - `Zp10` ($10): NMI path selector (0=Path B, non-zero=Path A) - `Zp11` ($11): Sub-path selector for Path B - `Zp9E` ($9E): Frame sub-counter (palette cycling) - `Zp9F` ($9F): Secondary frame counter - `Zp72` ($72): Tertiary counter (sprite palette) - `ZpDB` ($DB): Gameplay active flag - `Zp06` ($06): Bit 4 = gameplay trigger - `ZpB7` ($B7): Level transition flag - `Zp03` ($03): PPUMASK shadow --- ## 4. Main Initialization — `$C3A6` (from Reset) After reset jumps to `$C3A6`, the game initializes title screen and enters main loop. The main loop is driven by NMI synchronization — the main thread waits for NMI to signal frame completion via ZP flags. Key initialization routines (reached from `$C3A6`): - `$C178`: PPU address/data write helper (A=addr high, X=addr low → $2006/$2006/$2007) - `$C18E`: PPU control update (reads Zp03→PPUMASK, Zp10→PPUCTRL) - `$C1E1`: Sprite/OAM update (builds OAM at $0200) - `$C27C`: Common NMI setup (called at NMI entry) --- ## 5. Key Subsystems ### 5.1 PPU Helper — `$C178` / `$C006` - `$C178`: Set PPUADDR from A (high) / X (low), write via `$2006` twice - `$C006`: Write A to PPUDATA ($2007), RTS - `$C18E`: Read PPUSTATUS, write Zp03→PPUMASK, Zp10→PPUCTRL ### 5.2 Sprite/OAM System — `$C1E1` / `$C1D7` - Builds OAM at $0200 from object state arrays - `$C1D7`: Called from NMI paths for per-frame sprite update ### 5.3 Gameplay Logic — `$C2C6`, `$DE9F`, `$C1D7`, `$C2B3` - `$C2C6`: Player/input handling - `$DE9F`: Enemy/guard AI - `$C2B3`: Level progression, scoring ### 5.4 Level Data & Transitions — `$E271`, `$C0E4`–`$C123` - `$E271`: Level load/init - `$C0E4`: State machine on `ZpBE`/`ZpBA` (pointer to level data) - Level data tables at `$C2F0`, `$C378`, `$C39E`, `$C3A2` ### 5.5 Audio — `$C12D` / `$C143` / `$C16E` - APU register writes ($4000–$4003) for pulse channels - Frequency data from `$C2F0`, `$C378` - Called from level transition / gameplay --- ## 6. Memory Map (Key Zero Page / RAM) | Address | Name | Use | |---------|------|-----| | $00–$01 | `Zp00`/`Zp01` | 16-bit pointer (RAM clear, PPU) | | $03 | `Zp03` | PPUMASK shadow | | $06 | `Zp06` | Gameplay flags (bit 4 = trigger) | | $10 | `Zp10` | NMI path selector (0=title, ≠0=game) | | $11 | `Zp11` | NMI sub-path selector | | $12–$13 | `Zp12`/`Zp13` | Object/sprite pointers | | $20–$21 | `Zp20`/`Zp21` | Player position (X/Y) | | $26 | `Zp26` | Player state/animation | | $28 | `Zp28` | Player velocity | | $32–$35 | `Zp32`–`Zp35` | Enemy/guard positions | | $39 | `Zp39` | Level timer | | $52–$5F | `Zp52`–`Zp5F` | Sprite attribute buffers | | $72 | `Zp72` | Sprite palette cycle counter | | $85–$86 | `Zp85`/`Zp86` | Level data pointers | | $9E–$9F | `Zp9E`/`Zp9F` | Frame counters (palette cycling) | | $B7 | `ZpB7` | Level transition trigger | | $B8–$B9 | `ZpB8`/`ZpB9` | Level transition counters | | $BA–$BF | `ZpBA`–`ZpBF` | Level data pointer + state | | $DB | `ZpDB` | Gameplay active flag | | $E5 | `ZpE5` | Init constant ($23) | | $0200 | OAM buffer | 64 sprites (DMA source) | | $0300+ | Game state arrays | Player, enemies, level data | --- ## 7. Data Tables The full, interpreted inventory of every data block in this PRG is in **Pass 3 data-table inventory** below; the short list here is only the tables the flow analysis refers to. | Table | Address | Description | |-------|---------|-------------| | Palette data (BG) | $C39E | 4 entries, indexed by Zp9F&3 | | Palette data (sprites) | $C3A2 | 4 entries, indexed by Zp72&3 | | Audio frequency low | $C2F0 | Note→period low byte | | Audio frequency high | $C378 | Note→period high byte (×2 + index) | | Level data pointer | `ZpBA`/`ZpBC` | Indirect pointer to level scripts | --- ## 8. Summary of Execution Flow ``` Power On / Reset │ ▼ Reset ($C000) ────► sei/cld, stack=$FF │ ├─ Clear $0000–$07FF (8 pages via Zp00/Zp01) │ ├─ ZpE5 = $23 │ ▼ jmp $C3A6 (Main Initialization) │ ├─ Title screen setup ├─ PPU/APU init └─ Enter main loop (VBlank synchronized) Main Loop (implicit, VBlank-driven) │ ▼ (NMI fires once per frame) NMI ($C02C) ├─ Preserve A,X,Y ├─ jsr $C27C (common setup) ├─ lda Zp10 │ ├─ =0 (Title/Menu): │ │ lda Zp11 │ │ ├─ =0: jsr $C18E (PPU update), $C178, jmp $C0A4 │ │ └─ ≠0: Path A (palette cycling) │ │ │ └─ ≠0 (Gameplay): │ lda #$00 / sta Zp10 │ lda Zp11 / tax │ ├─ =0: jsr $C18E, $C178, jmp $C0A4 │ └─ ≠0: Path A (palette cycling) │ ├─ Path A (Label_C050): │ Palette writes to $3F08/$3F0C every 8/32 frames │ inc Zp9E, Zp9F, Zp72 │ ├─ Common Exit ($C0A4): │ lda Zp03 → PPUMASK │ jsr $C1E1 (sprite update) │ lda ZpDB │ ├─ =0: Label_C0BF (wait for Zp06 bit 4) │ └─ ≠0: Check Zp06 bit 4 │ ├─ Clear: Label_C0D1 (level transition check) │ └─ Set: jsr $C2C6, $DE9F, $C1D7 (gameplay) │ └─ Label_C127: Restore A,X,Y, rti Gameplay (when ZpDB≠0, Zp06&$10) ├─ $C2C6: Player control ├─ $DE9F: Enemy AI ├─ $C1D7: Sprite update ├─ $C2B3: Level/scoring └─ Level transition: $E271, $C0E4 state machine ``` --- ## Pass 3 data-table inventory The tracer leaves **18** data blocks in `prg.s`; each one is listed below. Every row says what the bytes *are* and which instruction of this dump reads them, or records that nothing reads them. The game is the NES/Famicom conversion of Doug Smith's *Lode Runner* (1983, Broderbund), published in Japan and the US by Hudson Soft in 1984. The playfield is made of platforms, ladders, gold pieces and a single exit; the player digs holes, collects the gold and reaches the exit. ### The named tables | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C2F0-$C377` | 136 | `ApuPulse1TimerPeriodTable` | 68 x 16-bit LE APU pulse-1 timer periods (the low byte goes to `$4002`, the high byte OR `$A0` to `$4003`); five octaves of twelve notes plus the eight notes of the top octave | `lda $C2F0,y` at `$C15D` and `$C164` (the two halves of one period) | | `$C378-$C37D` | 6 | `NoteOctaveBaseOffsets` | 6 x 8-bit base offsets into `ApuPulse1TimerPeriodTable`, 24 bytes (12 notes x 2 bytes) apart, one per octave. The routine at `$C143` adds the note's index within its octave to this byte | `adc $C378,x` at `$C157` | | `$C37E-$C39D` | 32 | `TitleMusicNoteStream` | 32 x 8-bit note values, the phrase the title-screen tune plays (`$0F` separates the notes) | `lda $C37E,x` at `$C2E5` (the loop writes 32 notes to the sound engine) | | `$C39E-$C3A1` | 4 | `TickMelodyNotes` | 4 x 8-bit notes of the short melody that plays on the game's tick, indexed by `Zp9F & 3` | `lda $C39E,x` at `$C06B` | | `$C3A2-$C3A5` | 4 | `StageStartMelodyNotes` | 4 x 8-bit notes of the melody that plays at the start of a stage, indexed by `Zp72 & 3` | `lda $C3A2,x` at `$C09E` | | `$C66C-$C710` | 165 | `ScreenLayoutTables` | a group of small tables the screen code uses: `$C66C-$C674` nine ascending positions ($88-$EA, step $0E), `$C675-$C679` five more ($B8-$F0, step $0E), `$C67A-$C680` seven bytes of `$03`, `$C681-$C685` five bytes of `$05`, `$C686` a `$00` separator, `$C687-$C693` thirteen ascending offsets ($1C-$6C, step $1C), `$C694-$C69B` page numbers ($02/$03), `$C69C-$C6A3` eight bytes of `$02`, `$C6A4-$C6AB` eight bytes of `$04`, `$C6AC-$C6B1` six bytes of `$04`/`$05`, `$C6B2-$C6BB` a table of ten multiples of 3, `$C6BC-$C6D1` twenty-one increasing values, and `$C6D2-$C710` sixty-three bytes of three-byte masks (`$03`, `$07`, `$0F`, `$1F`, `$3F`, `$7F` families). The block is a mixture of several tables and the split above is read off the byte patterns; the individual meanings of the first two groups are not established | `ldx $C6B2,y` at `$C452`, `lda $C6D2,x` / `lda $C6D3,x` / `lda $C6D4,x` at `$C455`/`$C45A`/`$C45F` (the three-byte masks), `lda $C6BD,y` at `$C466`, `lda $C688,y` at `$C81D`, `lda $C696,y` at `$C824`, `lda $C6A4,y` at `$C829`, `lda $C689,y` at `$CBDD` and `$CE43`, `lda $C697,y` at `$CBE2` and `$CE48`, `lda $C687,y` at `$CDE2`, `lda $C695,y` at `$CDE7`, `lda $C688,y` at `$D239`, `$D2EF`, `$D2FA`, `lda $C6A4,y` at `$D2F4`, `lda $C696,y` at `$D2FF`, `lda $C689,y` at `$D511` | | `$C887-$C88E` | 8 | `BitMaskTable` | 8 x 8-bit single-bit masks, `$80` down to `$01` | `and $C887,x` at `$C868` (the routine tests one bit of the tile byte it just read) | | `$CB09-$CB21` | 25 | `ObjectTypeCountTable` | 25 x 8-bit small values (0-3), one per object type; the value is stored as the object's count at `Zp0F`. The table is indexed by the object-type number the drawing routine is given, so entry *n* belongs to object type *n* | `lda $CB09,x` at `$CA7E` | | `$D03D-$D054` | 24 | `LevelTileIndexTable` | 24 x 8-bit ascending values ($0A-$13); the routine at `$D02B` writes entry `X+12` and then entry `X` into the level buffer, so the table supplies a pair of values per column of the playfield | `lda $D049,x` at `$D02D` (the `X+12` half) and `lda $D03D,x` at `$D035` | | `$D177-$D17E` | 8 | `LevelStepDistanceTable` | 8 x 8-bit per-level distances (1, 2, 4, 8, 12, 16, 20, 25) | `lda $D177,x` at `$D16A` | | `$D17F-$D186` | 8 | `LevelMovementSpeedTable` | 8 x 8-bit per-level movement speeds (10, 12, 14 ... 24), added to a 16-bit fixed-point position at `$D0AA` | `adc $D17F,x` at `$D0AA` and `$DC3B` | | `$D187-$D18E` | 8 | `LevelSpeedDeltaTable` | 8 x 8-bit signed per-level deltas (-7, -5, -3, -1, 1, 3, 5, 7) | `adc $D187,x` at `$D10B` and `$DC73` | | `$D602-$D60B` | 10 | `LevelDataPointerTable` | 5 x 16-bit LE pointers ($DBB7, $D6D9, $D74D, $D616, $D678) into the level code | `lda $D603,y` at `$D5F9` and `lda $D602,y` at `$D5FD` (the routine returns the low and the high byte of the selected pointer) | | `$DD86-$DE9D` | 280 | `TextStringTable` | a list of records `[PPU address high][PPU address low][NUL-terminated ASCII text]`: `$DD86` `$21CB` ":1 PLAYER ", `$DD93` `$220B` " 2 PLAYERS", `$DDA0` `$224B` " EDIT MODE", `$DDAD` `$2303` "COPYRIGHT @1983 DOUG SMITH", `$DDCA` `$2323` "COPYRIGHT @1984 HUDSON SOFT", `$DDE8` `$234A` "LICENSED BY", `$DDF6` `$2364` "NINTENDO OF AMERICA INC", `$DE10` `$2123` "BY BRODERBUND SOFTWARE INC", `$DE2D` `$0020` " ", `$DE33` `$210C` "PLAYER", `$DE3C` `$21CB` "GAME OVER", `$DE49` `$21C8` "STAGE ", `$DE52` `$21D1` "LEFT", `$DE59` `$2288` "SCORE", `$DE61` `$22C8` "HISCORE", `$DE6B` `$20F1` "0 POINTS", `$DE76` `$2171` "0 POINTS", `$DE81` `$2268` "TOTAL", `$DE89` `$2271` "0 POINTS", `$DE94` `$21F1` "0 POINTS"; the table is followed by a single `$00` byte at `$DE9E` | `lda $DD86,x` at `$E1E2` and `lda $DD87,x` at `$E1E8` (the two bytes of the record's PPU address, written to `$2006`) | | `$E2A0-$E2A1` | 2 | `TitleCursorCharacterTable` | 2 x 8-bit characters ($53 = 'S', $69 = 'i') chosen by the mode counter `ZpCA` | `lda $E2A0,x` at `$E289` | | `$E650-$E65B` | 12 | `HudTileSequenceTable` | 3 x 4-byte sequences of CHR tile indices: `$E650` `0F 29 16 30`, `$E654` `0F 39 28 27`, `$E658` `0F 29 26 30`; the routine writes four of them to `$2007` | `lda $E650,y` at `$DF5D` | | `$E65C-$E663` | 8 | `HudTileSequenceOffsets` | 8 x 8-bit offsets into `HudTileSequenceTable` (0, 4, 0, 0, 8, 8, 0, 0), one per HUD slot | `lda $E65C,x` at `$DF50` | | `$E854-$E868` | 21 | `MenuItemTextTable` | four NUL-terminated ASCII strings: "MENU", "SAVE", "LOAD", "ERROR" | not referenced by any instruction - the menu code reaches it through a pointer built at run time | | `$FFFA-$FFFF` | 6 | `InterruptVectorTable` | 3 x 16-bit LE vectors: NMI = `$C02C`, RESET = `$C000`, IRQ = `$FFF9` | the CPU reads them from the vector page; the IRQ target holds a single `rti` | ### The blocks that are not tables | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$D5EA-$D5EF` | 6 | - | unreferenced; six bytes of alternating `$02`/`$04` values that follow the routine at `$D5E0` | not referenced by any instruction | | `$D60C-$D66E` | 99 | - | unreferenced; decodes as a run of instructions (`ldy $38`, `cpy #$03`, `jsr $D2FA`, ...) - it is code the tracer could not reach, not data. It sits inside the same tracer block as `LevelDataPointerTable`, whose five pointers do point into it | reached indirectly through `LevelDataPointerTable` at `$D602` | | `$D679-$D69B` | 35 | - | unreferenced; decodes as 17 consecutive instructions - code the tracer could not reach, not data | not referenced by any instruction | | `$D6DA-$D700` | 39 | - | unreferenced; decodes as 19 consecutive instructions - code the tracer could not reach, not data | not referenced by any instruction | | `$DD43-$DD4E` | 12 | - | unreferenced; decodes as a run of instructions (`lda #$01`, `jsr $CA1C`, `inc $85`, ...) - code the tracer could not reach, not data | not referenced by any instruction | | `$DD6B-$DD6D` | 3 | - | three bytes that sit between the routine at `$DD5F` and the `lda ($12),y` / `sta $2006` sequence at `$DD6E`; they are not the start of any instruction in this dump | not referenced by any instruction | | `$E2B5-$E64F` | 923 | - | unreferenced; a 923-byte region whose layout was not established from this dump. ROM provenance: it lies between the code at `$E2A2` and the HUD tile table at `$E650`, and it is the block that holds the game's remaining static data | not referenced by any instruction | | `$EA1C-$FFF8` | 5597 | - | unreferenced; the largest block in the PRG. Its bytes are a mixture of small values and values with the high bit set, which is what a packed level/tile description looks like, but no instruction of this dump names an address inside it, so the record structure was not established. ROM provenance: it runs from the end of the code at `$EA1B` to the last byte before the IRQ vector | not referenced by any instruction | ### Notes - The two blocks at `$D679-$D69B` and `$D6DA-$D700`, and the 99 bytes at `$D60C-$D66E`, all decode as 6502 code. They are still emitted as `.byte` rows because the tracer never reached them, but their contents are instructions; they are left unnamed on purpose. - The level data of the game is in the two large unreferenced blocks (`$E2B5-$E64F` and `$EA1C-$FFF8`). The five pointers of `LevelDataPointerTable` point at the routines at `$D616`, `$D678`, `$D6D9`, `$D74D` and `$DBB7`, so the level *scripts* are code, while the level *layouts* are in the large static blocks. Establishing the layout of those blocks is the one piece of data work still open in this project. ## 9. Naming and Residue **Pass 1 complete** — 11,057 lines, 627 `Label_` definitions, canonical audit comments, 52.6% code. **Pass 2 complete (2026-10-20)** — All 627 label definitions renamed. 196 semantic routine entry points named via `_tools/pass2.py` derive/apply using a hand-curated routinemap; remaining 431 internal branch targets named as `_Block[_n]`. Zero `Label_Cxxxx` definitions remain. **Pass 3 partial (2026-10-20)** — `_tools/apply_symbols.py` substituted **85 operands** (83 hardware registers + 2 single-name zero-page operands). Zero multi-alias residue. **2 semantic zero-page aliases applied**; remaining 150 zero-page slots retain neutral `ZpXX` names in `locals.inc`. Data tables/structures named in Memory Map section. **Pass 3 data tables (2026-10-21)** — every one of the 18 data blocks in the PRG is now interpreted: 19 tables carry ROM-specific names (`ApuPulse1TimerPeriodTable`, `NoteOctaveBaseOffsets`, `TitleMusicNoteStream`, `TickMelodyNotes`, `StageStartMelodyNotes`, `ScreenLayoutTables`, `BitMaskTable`, `ObjectTypeCountTable`, `LevelTileIndexTable`, `LevelStepDistanceTable`, `LevelMovementSpeedTable`, `LevelSpeedDeltaTable`, `LevelDataPointerTable`, `TextStringTable`, `TitleCursorCharacterTable`, `HudTileSequenceTable`, `HudTileSequenceOffsets`, `MenuItemTextTable`, `InterruptVectorTable`), and the remaining blocks are recorded as unreferenced data or as unreached code, with their ROM provenance. See **Pass 3 data-table inventory** above. **Promotion outstanding** — Add `header.s`/`chr.s` + HEADER/PRG/CHR `linker.cfg`, switch `make` to whole-ROM `cmp`, move `_tools` paths from `../../../_tools/` to `../../_tools/`. Build verification: `make` in the project folder produces `reconstructed.nes`, compared against `rom.nes` (whole ROM byte-identical). ### Entry-point naming (2026-10-21) Every `jsr`/`jmp` target in this project that carried an address-derived `SubCxxxx` name was reviewed against the routine it points at, the code that calls it and the RAM fields it touches. | | count | |---|---| | Address-derived entry points before | 197 | | Entry points now carrying a semantic name | **168** | | Entry points still address-derived | **29** | The names now in `prg.s` cover the parts of the game the dump does reveal: * **PPU** — `WritePpuData`, `ReadPpuData`, `SetPpuAddress`, `SetPaletteAddress`, `WritePpuMask`, `UploadSprites`, `ShowSpritesOnly`, `EnablePpuNmi`, `DisablePpuNmi`, `EnableBackgroundLayer`, `WritePpuDataBlock`, `WritePpuDataFromX`, `WritePpuAddress`, `WriteLevelTilePair`.* **Controller and expansion** — `ReadControllers`, `AccumulateControllerBit`, `ReadExpansionJoypad`, `ReadExpansionJoypadButton`. * **Sound** — `EnableApu`, `SilenceApu`, `InitSoundChannels`, `PlayPulse1Tone`, `PlayTwoToneBlip`, `StopPulse2AndPlayBlip`, `SilencePulse1`. * **Game set-up** — `InitGame`, `InitGameState`, `InitTitleScreen`, `StartLevel`, `InitLevelObjects`, `InitPlayerAndLevelState`, `InitLevelSprites`, `ClearNmiModeFlags`, `StartRamClear`, `PointToSpriteBuffer`. * **Screen layout and object drawing** — `LoadScreenLayoutPointers`, `SetScreenLayoutPointer`, `SetScreenTileBufferPointer`, `SetNextScreenLayoutPointer`, `ClearLayoutAccumulator`, `AccumulateLayoutBit`, `TestLayoutBit`, `DrawObjectTile`, `DrawObject`, `ComputeObjectPosition`, `ComputeObjectAddress`, `WriteLevelTile`, `WriteLevelTilePair`. * **Guards (the enemies)** — `UpdateEnemyPosition`, `ComputeEnemyFixedPointPosition`, `StepEnemyDirection0/1/2`, `AdvanceEnemy`, `SaveEnemyState`, `LoadEnemyState`, `CheckEnemyAlignment`, `FindEnemyAbove`, `FindEnemyBelow`, `FindEnemyAhead`, `ComputeEnemyDistance`. * **Player and collision** — `UpdatePlayerPosition`, `CheckPlayerCollision`, `ResolvePlayerCollision`, `CheckPlayerTile`, `CheckTileUnderPlayer`. * **HUD, stage flow and level scripts** — `DrawHudPanel`, `UpdateTitleCursor`, `StartLevelDataE7E3` and the five other per-board start routines, `DrawStageCompleteScreen`. **Residue — 29 entry points remain address-derived.** They are the small fragments of the guard and player state machines: one- or two-instruction helpers that load a constant, branch on a flag or pick a row index, and whose role the dump does not distinguish from their neighbours. They are `SubCC287`, `SubCC419`, `SubCC4F2`, `SubCC52D`, `SubCC563`, `SubCCA22`, `SubCCA26`, `SubCCA2F`, `SubCCB9F`, `SubCCFB1`, `SubCD1AD`, `SubCD415`, `SubCD46E`, `SubCD672`, `SubCDC04`, `SubCDED3`, `SubCDFBD`, `SubCDFEE`, `SubCE1D2`, `SubCE258`, `SubCE690`, `SubCE75D`, `SubCE8FB`, `SubCE90F`, `SubCE97D`, `SubCE98B`, `SubCE998`, `SubCE99D`, `SubCE9FE`. Giving them names would mean inventing a role the ROM does not show, so they keep their address-derived names. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. **RESET** is at `$C000` (`Reset`). The CPU comes here out of reset with the PPU and APU in their power-on state and the zero page and RAM uninitialised. The reset path calls: | Address | Routine | |---------|---------| | `$C006` | `Sub_C006_Continue_C006` (falls through) | **NMI** is at `$C02C` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$C27C` | `Sub_C27C_CallNext_C27C` | **IRQ** is at `$FFF9` (`IrqHandler`) - a different address from RESET.