# Hogan's Alley (JU) — Game Flow from Power-On This document describes the execution flow of the disassembled Hogan's Alley (JU) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `prg.s` in this folder. The rebuild is verified **byte-identical** to the original ROM, header and CHR included (`make` prints `Byte-identical ROM verified.`). Hogan's Alley is a light-gun game: the player shoots at cans, birds and trick-shot targets with the Zapper while the D-pad selects the trick-shot options. The engine is built around three ideas that show up everywhere below: * a **frame heartbeat** — the NMI sets a flag, the main loop spends it; * an **inline dispatch table** — the handler tables sit *inside* the code stream, right after the call that reads them; * a **three-accumulator sound engine** — three zero-page bytes collect per-voice flags, and one routine turns them into APU writes. --- ## 1. Reset Vector — CPU $FFFC → $C000 **`Reset`** ($C000–$C063) | Step | Code | Purpose | |------|------|---------| | 1 | `cld` / `sei` | Clear decimal mode, disable interrupts | | 2 | `lda PPUSTATUS` / `bpl *-3` | Wait for the first VBlank (PPU warm-up) | | 3 | `ldx #$00` / `stx PPUCTRL` / `stx PPUMASK` | Blank the PPU | | 4 | `dex` / `txs` | Stack pointer to $01FF | | 5 | `ldx $05EC` | Read the warm-start signature (the first byte of the four-byte counter array at `$05EC`) | | 6 | $C016–$C025 | Clear RAM $0000–$07FF through the `Zp00`/`Zp01` pointer | | 7 | $C027–$C02C | If the signature was zero (a cold boot) store the marker $5F at `$05EC`; a warm start writes the old marker back | | 8 | `lda #$B6` / `sta AttractSpriteY` | The attract-mode sprite's Y position | | 9 | `jsr InitPpuAddress` / `jsr InitPpuAndOam` | Point the PPU at the OAM shadow and clear it | | 10 | `sta PPUSCROLL` ×2 | Scroll to zero | | 11 | `sty GameMode` (=$01) | Enter the game (mode 1) | | 12 | `lda #$0F` / `sta APUSTATUS` | Enable all five APU channels | | 13 | `jsr AudioInitTrampoline` | Clear the sound engine | | 14 | `lda #$90` / `sta PPUCTRL` / `sta PpuCtrlShadow` | NMI on, background and sprites on | | 15 | `lda #$02` / `sta PpuMaskShadow` | The PPUMASK shadow starts at $02 | | 16 | $C058–$C061 | Copy the 12-byte palette from `$E36A` to `$05F0` | Step 5 is the warm-start check: `$05EC` is read *before* the RAM clear, so a cold boot (signature zero) writes the marker $5F and a warm start writes the old marker back. The other three bytes of the array at `$05EC` are the attract-mode counters. --- ## 2. The Main Loop and the Frame Dispatch **`MainLoop`** ($C07E–$C085) ``` MainLoop: lda GameFramePending bne GameFrameDispatch jsr AttractModeTick jmp MainLoop ``` The loop spins on `GameFramePending` ($20). The NMI sets it; the main loop clears it at the end of the frame (see below). While the flag is clear the game is in attract mode and `AttractModeTick` ($C6BD) advances the four-byte counter array at `$05EC`: it adds the frame counter to the byte at `$05EC`, uses the sum as an index into the table at `$DE52`, and adds that table value to the array. **`GameFrameDispatch`** ($C065–$C07D) is the per-frame work list: 1. `lda GameMode` ($24) — mode 0 is the attract/demo mode. 2. `lda ModeIndex` ($26) — mode 0 runs the gameplay path. 3. `jsr AudioUpdateTrampoline` — let the sound engine advance. 4. `jsr ReadPlayerInput` — read and debounce the controllers. 5. `jsr GameStateDispatch` — run the mode's state machine. 6. `inc Zp2F` — the frame counter. 7. `sta GameFramePending` / `sta NmiPhaseFlag` — release the NMI. **`GameStateDispatch`** ($C0B8–$C0E3) reads the debounced buttons (`Player1Buttons`, $16) and tests bit 4 — `PAD_START` — to start a round. The first press is latched in `$05FC` so the button has to be released and pressed again. The sub-state index (`SubStateIndex`, $25) then selects the round's entry point: sub-states 3, 4, 7 and $0A all enter `InitGameplayRound`, and anything else falls through to the running frame. **`InitGameplayRound`** ($C0E4–$C103) copies the six round parameters from `Zp2A`–`Zp2F` to `$0430`–`$0435`, calls `CopyStackToOam` and `InitRoundState`, sets the actor state to $25, spawns the first target with `SpawnTarget`, saves the sub-state in `SavedSubState` ($1C) and switches the sub-state to 7. **`RunGameplayFrame`** ($C10E–$C117) is the gameplay heartbeat: `UpdateTargets` → `SetSoundFlagBit01` → `AudioUpdateTrampoline` → `SetSoundFlag2Bit01`. --- ## 3. The NMI Handler **`NmiHandler`** ($C088–$C0B7) ``` NmiHandler: php pha / txa / pha / tya / pha lda #$00 / sta OAMADDR lda #$02 / sta OAMDMA lda NmiPhaseFlag bne NmiHandler_Loop jsr UpdateHud jsr SetOamSlot jsr SetScroll jsr ReadControllers jsr CheckScrollFlip ldy #$01 / sty NmiPhaseFlag / sty GameFramePending ``` The handler pushes every register, starts an OAM DMA from `$0200`, and then tests `NmiPhaseFlag` ($22). The flag is set at the end of the handler and cleared by the main loop, so the per-frame work (HUD, OAM shadow, scroll, controller read) happens once per frame and not once per NMI. This is the same guard the other games in this repo use to stop a `$2002` read from re-arming the NMI inside one scanline. --- ## 4. The Dispatch Architecture The engine does **not** use a jump table in the data segment. It uses an **inline** table: the handler table is the data that follows the call, and the call itself pops the address of that data off the stack. **`IndirectCall`** ($C34D–$C362) is the trampoline: ``` IndirectCall: asl a ; the index is a word index tay iny pla ; the low byte of the return address sta HandlerPtrLo ; = the address of the table that pla ; follows the `jsr` sta HandlerPtrHi lda (HandlerPtrLo),y tax iny lda (HandlerPtrLo),y sta HandlerPtrHi stx HandlerPtrLo jmp (HandlerPtrLo) ``` `jsr IndirectCall` pushes the address of the next instruction, which is the first word of the table. The trampoline reads the word at `table + 2*index` and jumps to it. Six call sites use it, and each carries its own table inline: | Call site | Index | Table | Entries | Name | |-----------|-------|-------|---------|------| | $C121 | `SubStateIndex` ($25) | $C124 | 19 | `SubStateHandlerTable` | | $C216 | `ModeIndex` ($26) | $C219 | 4 | `ModeHandlerTable` | | $C899 | `HandlerIndex` ($41) | $C89C | 14 | `HandlerDispatchTable` | | $CDCE | `HandlerIndex` ($41) | $CDD1 | 1 | (inline) | | $D231 | `HandlerIndex` ($41) | $D234 | 2 | `ActorCommandTable` | | $D3D3 | `HandlerIndex` ($41) | $D3D6 | 14 | `TargetBehaviourTable` | Because the tables live in the middle of the code stream, the tracer cannot follow them: every entry is an address the static trace never sees. They were recovered by reading the bytes after each `jsr` and checking that they point at instruction boundaries. The sound engine has two more indirect jumps, `jmp ($0610)` at $F927 and `jmp ($0612)` at $F941, which dispatch on the sound engine's own state. --- ## 5. The Controller and the Zapper **`ReadControllers`** ($C282–$C2B3) strobes `$4016` and shifts eight bits out of the port. The value is compared with the previous one at `$0436`; only after the value has been stable for five frames is the new state written to `Player1Buttons` ($16) — so the button state is debounced, and a second slot at $17 holds the other port. **`ReadZapper`** ($CF3E) reads `$4017`, which is the Zapper port in a light-gun game: bit 3 is the light sensor and bit 4 is the trigger. The trigger bit and the sensor bit are what the shot logic tests. --- ## 6. The Sound Engine The sound engine lives at the top of the PRG ($F776–$FC48) and works from three zero-page accumulators: | Accumulator | Set by | Read by | |-------------|--------|---------| | `SoundFlag0` ($F0) | `SetSoundFlagBit01` … `SetSoundFlagBit80` ($D291–$D2AD) | `AudioCommand` ($F9FF), `AudioCommand3` ($FBF2) | | `SoundFlag1` ($F1) | `SetSoundFlag2Bit01` … `SetSoundFlag2Bit80` ($D2B4–$D2D0) | `AudioState4` ($FC48) | | `SoundFlag2` ($F2) | `SetSoundFlag3Bit01` … `SetSoundFlag3Bit80` ($D2D7–$D2EE) | `AudioState3` ($FAFC) | Each of the twenty-four stubs is a two-instruction routine that loads one bit and ORs it into its accumulator; the names carry the exact bit so the flag layout is visible in the source. `ClearSoundFlag2` ($D2F9) clears $F1. The note records live in `NoteParameterTable` ($FCC8, 812 bytes). `GetNoteIndex` ($F79B) copies nine bytes out of it into `NoteParam0`–`NoteParam7` ($F8–$FF) and `NotePitch` ($E5); `GetVolumeLevel` ($F944) looks a volume up in the table at $F766; `ApuPulse1CtrlTable` ($F992) holds the four pulse-channel control values the engine writes to `$4000`. --- ## 7. Memory Map (Key Zero Page / RAM) | Address | Name | What it holds | |---------|------|---------------| | $00/$01 | *(neutral)* | The general 16-bit working pointer — reused as the RAM-clear pointer, the OAM pointer and the VRAM write address | | $02/$03 | *(neutral)* | The script pointer; also reused for the VRAM address | | $08 | `PpuCtrlShadow` | The `$2000` shadow, written back at $C2E9 and $C312 | | $09 | `PpuMaskShadow` | The `$2001` shadow, written back at $C75F and $C76A | | $14/$15 | `HandlerPtrLo`/`HandlerPtrHi` | The 16-bit pointer the inline dispatch table hands to `jmp ($0014)` | | $16 | `Player1Buttons` | The debounced controller buttons (bit 4 is `PAD_START`) | | $1C | `SavedSubState` | The sub-state saved before a mode switch and restored afterwards | | $20 | `GameFramePending` | Set by the NMI, cleared by the main loop | | $21 | `DrawPendingFlag` | The HUD is waiting to be drawn | | $22 | `NmiPhaseFlag` | The NMI has already run this frame | | $23 | `DisplayListIndex` | The entry to draw next from the list at $E657 | | $24 | `GameMode` | The game mode, tested by the state dispatcher | | $25 | `SubStateIndex` | Selects the word read from the $C124 table | | $26 | `ModeIndex` | Selects the word read from the $C219 table | | $29 | `AttractSpriteY` | The attract sprite's Y position, written to the OAM byte 0 | | $2A | `FrameCountdown` | Counts down from 10; also the first byte of the round-parameter block | | $30 | `AttractTimer` | The attract-mode timer | | $3F | `PlayerSlot` | The player slot being worked on | | $40 | *(neutral)* | The 32-byte OAM shadow at $40–$5F; also used as a counter elsewhere | | $41 | `HandlerIndex` | The index into the inline dispatch tables | | $42/$43 | `ActorX`/`ActorY` | The actor's position, written together by `SetActorPosition` | | $45 | `ActorState` | The current actor's state byte | | $5A | `ActorIndex` | The index of the actor being drawn | | $B0/$B1 | `ActorListCursor`/`ActorFrameCounter` | The cursor into the 16-entry actor list and the counter that advances it | | $F0/$F1/$F2 | `SoundFlag0`/`1`/`2` | The three sound flag accumulators | The full zero-page map, with the reason every neutral `ZpXX` slot was left neutral, is in `zp_aliases.inc`. --- ## 8. Pass 3 Data-Table Inventory Every data block the tracer emitted in this PRG is listed here — 34 blocks in all (`data_region_names.tsv` carries the same list in machine-readable form). A block that an instruction reads is named and its structure recorded; a block nothing reads is recorded as unreferenced data with its ROM provenance; a block whose bytes decode as instructions is recorded as code the tracer could not reach. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | $C124 | 3 B | `SubStateHandlerTable` | The first two and a half entries of a 19-entry table of 16-bit little-endian handler addresses, $C124–$C149. Entry 0 is $C76E, entry 1 $C791, entry 2 $C7EE (`ResetActorsForRound`), entry 4 $CD00 (`SelectActorEntry0`), entry 5 $D267 (`SyncSubState`), entry 11 $D35F (`ReadZapperAndStartRound`), entry 14 $D86E (`RedrawActorRow`). | dispatched by `jsr IndirectCall` at $C121 with A = `SubStateIndex` ($25); `IndirectCall` ($C34D) turns the index into $C124 + 2×index and jumps through the word it finds | | $C127 | 35 B | `SubStateHandlerTablePart2` | The remaining entries of the same 19-entry table ($C791 … $DC5C). | as above | | $C219 | 2 B | `ModeHandlerTable` | A four-entry table of 16-bit mode handler addresses, $C219–$C220: $C6D5, $C734, $D184 (`SetupRoundScreen`), $D1E1. This block holds entry 0 and the low byte of entry 1. | dispatched by `jsr IndirectCall` at $C216 with A = `ModeIndex` ($26) | | $C21B | 6 B | `ModeHandlerTablePart2` | Entries 1–3 of the same four-entry mode table. | as above | | $C89C | 28 B | `ActorBehaviourHandlerTable` | Fourteen 16-bit actor-behaviour handlers: $C8B8 (`UpdateActorState`), $C8C2, $C916 (`AdvanceActorFrame`), $C93C, $C94E, $C9E2, $C9F7, $CA4C (`UpdateActorSpriteState`), $CA88 (`UpdateActorLife`), $CAD4 (`ResetActorMovement`), $CB10 (`StartActorBehaviour`), $CB34, $CB50, $CB81. | dispatched by `jsr IndirectCall` at $C899 with A = `HandlerIndex` ($41) | | $C965 | 13 B | `UnreachedCode14` | The bytes decode as a short routine — `TAY / LDA $05ED / AND #$1F / ADC #$0A / STA $19 / TYA / INC $19 / STA $71 / LDY …` — so this is code, not a table. Nothing in this dump enters it. | unreferenced | | $C9FB | 5 B | `UnreachedCode01` | Five bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $CB34 | 28 B | `UnreachedCode02` | 28 bytes. The address is the target of entry 11 of `ActorBehaviourHandlerTable`, so these bytes are the body of that behaviour handler rather than a table. | reached through `ActorBehaviourHandlerTable` entry 11 (the indirect jump at $C360) | | $CDF5 | 14 B | `UnreachedCode03` | 14 bytes. The address is the target of entry 1 of the two-entry table at $CDD1, so these bytes are the body of that handler. | reached through the two-entry table at $CDD1 (the indirect jump at $C360) | | $D0DC | 22 B | `UnreachedCode04` | 22 bytes that decode as instructions (`LDA #$00 / STA $7C / LDA #$24 / STA $0500 / LDA #$FC / LDY #$08 / …`); code the tracer could not reach. | unreferenced | | $D234 | 4 B | `ScreenSetupHandlerTable` | Two 16-bit handler addresses, $D238 and $D25A. | dispatched by `jsr IndirectCall` at $D231 with A = `HandlerIndex` ($41) | | $D2F5 | 4 B | `UnreachedCode05` | Four bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $D3D6 | 28 B | `TargetBehaviourHandlerTable` | Fourteen 16-bit target-behaviour handlers: $D3F2, $D4B0 (`UpdateActorAttract`), $D4C7, $C93C, $D4F1 (`SpawnNextTarget`), $D4FD, $D538 (`UpdateTargetNegative`), $D55E, $D56B, $D57B (`UpdateTargetPositive`), $D5A1 (`UpdateTargetSideways`), $D5C4, $CB81. | dispatched by `jsr IndirectCall` at $D3D3 with A = `HandlerIndex` ($41) | | $D494 | 2 B | `UnreachedCode06` | Two bytes that decode as an instruction; code the tracer could not reach. | unreferenced | | $D6B7 | 3 B | `UnreachedCode07` | Three bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $D894 | 287 B | `UnreachedCode08` | 287 bytes that decode as a run of instructions (`JSR $D287 / JSR $D279 / …`); code the tracer could not reach. | unreferenced | | $DBEC | 36 B | `UnreachedCode09` | 36 bytes that decode as instructions (`INC $43 / BNE … / LDA #$00 / STA $41 / JMP $CE4B / …`); code the tracer could not reach. | unreferenced | | $DC5C | 10 B | `UnreachedCode10` | 10 bytes that decode as instructions (`JSR $CD77 / LDX #$0E / LDA #$0C / JMP $D270 / …`); code the tracer could not reach. | unreferenced | | $DC91 | 3424 B | `ActorStateData` | The actor and target state region: parallel byte tables and 16-bit little-endian tables that the object code reads one entry at a time. The addresses the dump actually names are $DDBA, $DE52, $E0F8, $E0FD, $E133, $E13C, $E145, $E159, $E1D3, $E1DD, $E1E5, $E205, $E209, $E210, $E31A, $E32E, $E333, $E337, $E35A, $E35E, $E361, $E364, $E367, $E376, $E37C, $E380, $E381 and $E33B; the record size and units of each group are not established, so the region is recorded as one block rather than a table of guesses. | about 50 instructions, e.g. `lda $E36A,x` at $C05A, `lda $E64F,x` at $C1DE, `ldx $E657,y` at $C257, `lda $E658,y` at $C25A, `lda $DE52,y` at $C6C5, `lda $E364,x` at $C885, `cmp $E333,x` at $C957, `lda $E0FD,y` at $C976, `lda $E13C,x` at $D240, `lda $C3F1,y`-style reads of `$E1D3,x` at $CC39 and `$DDBA,x` at $CED5 | | $E9F1 | 13 B | `ActorEventTable` | One byte per actor-event slot; the byte is compared with $01 and, when it is not, stored to the event's value and offset fields. | `lda $E9F1,x` at $D6ED, X = `ZpAE` | | $E9FE | 21 B | `ActorEventTablePart2` | The middle of the same 36-byte event table, $E9F1–$EA14. | as above | | $EA13 | 2 B | `ActorEventTablePart3` | The last two bytes of that table. | as above | | $EA15 | 3425 B | `MusicSequenceData` | The music and sound-effect stream region. It holds three 16-bit little-endian sequence-pointer tables — $EA59, $EAF0 and $ED99, each indexed by twice a sound-effect number — and the melody tables at $F700/$F701 and $F766. The bytes decode as instructions in places, which is expected of a note stream. | `lda $EA5C,y` at $C773, `lda $EAF0,x` at $D743 and $D749, `lda $ED99,x` at $D78F and $D795, `lda $EA59,x` at $D84D and $D853, `lda $F766,y` at $F94A, `lda $F701,y` at $F953, `lda $F700,y` at $F95B | | $F7F6 | 25 B | `ApuInstrumentTable` | Three parallel byte columns at $F800, $F801 and $F802 (instrument volume, timbre and envelope bytes), read with the same Y so that the three values together form one instrument record. | `lda $F800,y` at $F909, `lda $F801,y` at $F90F, `lda $F802,y` at $F915 | | $F8A5 | 4 B | `UnreachedCode11` | Four bytes that decode as instructions (`LDA #$FF / BNE … / CLC / ADC #$FE`); code the tracer could not reach. | unreferenced | | $F992 | 8 B | `ApuPulse1CtrlTable` | Two four-entry tables of APUPULSE1CTRL values: $F992–$F995 = `04 84 84 C4` and $F996–$F999 = `04 7F 72 20`. Each byte is a duty/volume/length/loop setting for one channel. | `lda $F992,y` at $F9B7, Y = `ZpC1` & 3 | | $FA44 | 4 B | `VolumeLevelTable` | Four bytes, `3F 7F 0F 08`. No instruction in this dump addresses them. | unreferenced data (ROM provenance: immediately before the routine that starts at $FA48) | | $FAB4 | 37 B | `MusicCommandTable` | Four parallel byte tables at $FAB8, $FAC5, $FACE and $FAD0, each read with the same Y — the command step counter — so the four bytes together are one four-field music command. | `ldx $FAB8,y` at $FA87, `ldx $FACE,y` at $FA8D, `ldx $FAC5,y` at $FA9E, `ldx $FAD0,y` at $FAA3 | | $FB3D | 12 B | `UnreachedCode13` | 12 bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $FB89 | 8 B | `SoundRateTable` | Eight bytes that decode as instructions (`CMP $7F / BNE …`); code the tracer could not reach, not a rate table. | unreferenced | | $FBEE | 4 B | `SoundRateTable2` | Four bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $FC27 | 8 B | `SoundRateTable3` | Eight bytes that decode as instructions; code the tracer could not reach. | unreferenced | | $FCC8 | 812 B | `NoteParameterTable` | The sound engine's note data. The byte at $FCC8+Y is the note's pitch and doubles as the index of the note's own record; the eight bytes that follow it in the table ($FCC9+Y … $FCD0+Y) are that note's parameters. The engine copies them into `NotePitch` and `NoteParam0`–`NoteParam7`. | `lda $FCC8,y` at $F7A1 and $F7A5, `lda $FCC9,y` at $F7AA, `lda $FCCA,y` at $F7AF, `lda $FCCB,y` at $F7B4, `lda $FCCC,y` at $F7B9, `lda $FCCD,y` at $F7BE, `lda $FCCE,y` at $F7C3, `lda $FCCF,y` at $F7C8, `lda $FCD0,y` at $F7CD | | $FFFA | 6 B | `Vectors` | NMI $C088, RESET $C000, IRQ $C000. | the CPU's hardware vectors | **How the structures were derived.** The four dispatch tables are the direct consequence of `IndirectCall` at $C34D: the routine pulls the return address of its own `jsr`, so the table it reads always begins one byte past the last byte of that `jsr` instruction. Working the six call sites out gives the table bases $C124, $C219, $C89C, $CDD1, $D234 and $D3D6, and the index register at each site is what the table is keyed on (`SubStateIndex`, `ModeIndex` and `HandlerIndex`). The record size of every one of them is therefore two bytes — a 16-bit handler address — and the address of each target was read off the table and matched against the routine names already in the source. The sound engine's tables follow the same layout as the sibling engine in `NROM/Mario Bros (JU)`: the note base byte is used twice, once as the note offset and once as the record index, which fixes the record size; the four parallel music-command tables are read with the same Y, which makes them four fields of one command. The `UnreachedCode*` blocks are code: every one of them decodes cleanly as a run of 6502 instructions, and the two of them that a dispatch table points at ($CB34 and $CDF5) are handler bodies that the tracer never entered. --- ## 9. Naming and Residue * **681 labels**, no `Label_` residue, no raw `$XXXX` operand on any `jsr`, `jmp` or branch. * **One operand is left numeric, and it has to be.** `sta a:$00B4,y` at `$C203` is an *absolute* store to `$00B4`, three bytes long (`99 B4 00`). `$00B4` is the address `ZpB4`/`ActorPalette` stands for, but either name would assemble as a one-byte zero-page store, and the ROM's three-byte form is the absolute one. `ca65` has no way to say "absolute" for a zero-page-sized name, so the operand keeps its literal and carries the `a:` prefix that forces the long form. The build prints **Byte-identical ROM verified.** * **3,950 instructions / 8,041 bytes = 49.1 %** of the PRG is code; the other 8,343 bytes are data. * **Every data block is interpreted, not merely named** — see section 8. The dispatch tables were renamed to what they dispatch (`SubStateHandlerTable`, `ModeHandlerTable`, `ActorBehaviourHandlerTable`, `TargetBehaviourHandlerTable`, `ScreenSetupHandlerTable`), the sound engine's tables to what they hold (`ApuInstrumentTable`, `ApuPulse1CtrlTable`, `MusicCommandTable`, `NoteParameterTable`), the actor region to what it is (`ActorStateData`), the 36-byte event table to `ActorEventTable` and the stream region to `MusicSequenceData`. The blocks that decode as instructions keep the `UnreachedCode*` name and are recorded as code, not as tables. * Every instruction carries the canonical audit comment `; CPU:$C123 PRG:0123 AD 21 03`, and all 4,490 of them match `prg.bin` byte for byte. * The routine names came from reading this dump's own code. Where a routine's role could not be established, the honest address-derived `SubC` form is used rather than a guess — but no routine needed it: every entry that the engine reaches is named after what it does. * The internal branch targets are derived from the branch that reaches them (`beq` → `OnZero`, `bne` → `OnNonzero`, a backwards branch → `Loop`) and are prefixed with the owning routine, e.g. `UpdateTargets_OnZero`. * The zero-page slots that keep their neutral `ZpXX` name are listed with the reason in `zp_aliases.inc`. ### The Pass 1 correction The static trace alone classified only 41.8 % of the PRG as code. A Mesen Code Data Logger capture was merged with the four captures already in the folder (`hogans_merged.cdl`, the OR of five per-byte flag arrays) and `_tools/cdl_reclass.py` rewrote the split to match what the CPU actually fetched: **1,216 bytes moved from data to code**, five moved the other way, and 59 labels were emitted. The four branch targets the tool could not prove were turned into labels by `_tools/resolve_raw_branches.py`. --- ## 10. Promotion to `NROM/` (2026-10-20) * `git mv` from `_wip/NROM/` to `NROM/`. * `header.s` and `chr.s` added; `linker.cfg` now links the HEADER, PRG and CHR areas, so the whole ROM is compared against `rom.nes`. * `rom.nes` is a symlink to `Hogan's Alley (JU).nes`. * `make` prints `Byte-identical ROM verified.` — the header and the 8 KB of CHR are proven, not just the PRG. There is deliberately **no `make regen` target** here. The Pass 1 split in this project is the static trace *corrected* by a Code Data Logger capture, and the base the correction was applied to is not what the current `_tools/nesdis.py` produces, so a regen pipeline could not reproduce `prg.s` byte for byte. `prg.s` is the maintained source of truth; the tools that produced it are named above. ## 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 | |---------|---------| | `$C002` | `Reset_OnNonnegative` (falls through) | | `$C01E` | `Reset_OnNonzero` (falls through) | | `$C02C` | `Reset_Loop` (falls through) | | `$C221` | `InitPpuAddress` | | `$C231` | `InitPpuAndOam` | | `$FFF7` | `AudioInitTrampoline` | | `$C05A` | `Reset_OnNonnegative2` (falls through) | | `$C228` | `SetPpuAddress` | | `$C237` | `ClearOamShadow` | | `$C3F8` | `UpdateAttractSprite` | | `$F984` | `AudioInit` | **NMI** is at `$C088` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$C2BD` | `UpdateHud` | | `$C24E` | `SetOamSlot` | | `$C3A8` | `SetScroll` | | `$C282` | `ReadControllers` | | `$C3D0` | `CheckScrollFlip` | **IRQ** is at `$C000` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path.