# Raid on Bungeling Bay (U) [a2] — Game Flow from Power-On This document describes the execution flow of the disassembled **Raid on Bungeling Bay (U) [a2]** PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `prg.s` in the repository. The rebuild is verified **byte-identical to the original ROM** — `make` compares the whole `reconstructed.nes` against `rom.nes`. **Revision note:** this is a **different build** of the same game as `Raid on Bungeling Bay (U)` (the sibling dump in `NROM/Raid on Bungeling Bay (U)/`). The two share 16,234 of their 16,384 PRG bytes; the differences and what they mean are in §7. Both dumps were disassembled independently and cross-checked against each other. --- ## 1. Reset Vector — CPU $FFFC → $C000 → $C6E3 | Vector | Address | Handler | |--------|---------|---------| | NMI | $C0FA | `NmiHandler` | | RESET | $C000 | `Reset` — a single `jmp ResetEntry` | | IRQ | $C2C0 | `IrqHandler` (target $C2C0) | **`Reset`** ($C000) is a one-instruction trampoline: `jmp $C6E3`. **`ResetEntry`** ($C6E3) is the real cold-start routine: | Step | Code | Purpose | |------|------|---------| | 1 | `lda #$10` | PPUCTRL value (NMI on, BG at $1000, 8×8 sprites) | | 2 | `sta PPUCTRL` | PPUCTRL = $10 | | 3 | `sta PpuCtrlShadow` | $C2 = $10 — the PPUCTRL shadow is primed to match | | 4 | `inc PrngPeriodCounter` | $EA — the PRNG phase counter is advanced during the vblank wait | | 5 | `lda PPUSTATUS` / `bpl` | spin until the first vblank | The vblank wait loop at $C6EA is the one place outside the PRNG routine that touches `$EA`; it bumps the counter while it waits. That is why the slot is a *phase* counter rather than a frame timer. --- ## 2. NMI / Frame Heartbeat — `NmiHandler` ($C0FA) The NMI runs the whole frame state machine. Its first act is to choose a path: ``` NmiHandler ($C0FA): pha / txa / pha / tya / pha ; preserve A, X, Y lda NmiPathSelector ; $C4 bne FullPath ; != 0 -> full path lda #$00 / sta OAMADDR ; minimal path: OAM address 0 lda #$07 / sta OAMDMA ; OAM DMA from $0700 ... FullPath ($C05F): lda $0200 / ldx #$00 / stx $0200 ; read + clear the $0200 state bitmap lsr a / bcs ... ; bit 0 -> $C0C1 lsr a / bcs ... ; bit 1 -> $C0A8 lsr a / bcs ... ; bit 2 -> $C0A6 lsr a / bcs ... ; bit 3 -> $C0A6 lda NmiPathSelector / bne $C0A3 ; still the full path? lda FrameSubCounter / beq $C09A ; frame counter expired? dec FrameSubCounter ; no -> count it down ... ``` `NmiPathSelector` ($C4) is written `1` at $C3E9 (full path requested) and `0` at $C962 (back to the minimal path). `FrameSubCounter` ($55) is loaded with $0A at $C520, with 1 at $D226, and is decremented per NMI at $C07B. --- ## 3. Key Subroutines | Address | Name | Role | |---------|------|------| | $C006 | `WriteByteToPpu` | PPU helper (writes the pattern table) | | $C08E | `Sub_8764_OnNonzero_C08E` | sprite/OAM update | | $C17A | (no label - mid-block) | game base / main logic entry (the NMI's `jmp` target) | | $C3E2 | `StartFullFrameUpdate` | clear $0200 and request the full NMI path | | $C3F4 | `ResetScrollAndRestoreControl` | scroll setup (writes $C1/$C3 to PPUSCROLL) | | $C412 | `UpdatePseudoRandomNumber` | **pseudo-random generator update** | | $C4E4 | `ResetPaletteAddress` | palette update (16 bytes to $3F00) | | $C969 | `SetPpuControlAndClearRam` | write PPUCTRL and refresh the shadow at $C2 | | $E740 | `InitRadarSweepWindow` | 16-bit compare against $1C13 / $1610 | | $EE8F | — | attract-mode logic (in the base dump) | ### The PRNG at $C412 `UpdatePseudoRandomNumber` takes a value in A and returns a scrambled value in A. It runs the classic 6502 scrambler — `eor` / `asl` / `php` / `lsr` / `plp` / `rol`, twice — over the seed byte `$EB` and the state byte `$EC`, folds the phase counter `$EA` in twice, and stores the result back to `$EC`. Its callers ask for random values: `$CDF4`/`$CDF9` call it twice in a row, keep the first result in `$2D` and mask the second with `#$07` into `$2E`. Thirteen call sites use it. --- ## 4. Indirect Dispatch Four computed jumps are **not** followed statically — the target address is built at runtime: | Site | Instruction | Pointer | |------|-------------|---------| | $E2EC | `jmp (IndDispatch01)` | `sta IndDispatch01` / `sta IndDispatch02` at $E2E3/$E2E8 load a 16-bit target from the table at $E2EF | | $E79D | `jmp (IndDispatch17)` | target loaded elsewhere | | $E7A0 | `jmp (IndDispatch19)` | target loaded elsewhere | | $E7A3 | `jmp (IndDispatch1B)` | `sta IndDispatch1B` / `sta IndDispatch1C` at $E809, $EB13, $EB33 load a 16-bit target from tables at $E821, $EBCC, $EBCA | --- ## 5. Memory Map (Key Zero Page / RAM) | Address | Name | Use | |---------|------|-----| | $00 | `PtrLo` | 16-bit pair with $01 — the value $1C13 at $E742 and $1610 at $E756, compared at $E785/$E791 | | $01 | `PtrHi` / `IndDispatch01` | 16-bit high byte of the above; also the `jmp (ptr)` target of the computed dispatch at $E2EC | | $02 | `IndDispatch02` | high byte of the `jmp ($0001)` target at $E2EC | | $17 | `IndDispatch17` | `jmp ($0017)` at $E79D; the same address is also absolute RAM `$0017` in the copy loop at $E895 (`a:Zp17`) | | $18 | `Zp18` | absolute RAM `$0018` — the second byte of that copy loop | | $19 | `IndDispatch19` | `jmp ($0019)` at $E7A0 | | $1B | `IndDispatch1B` | `jmp ($001B)` at $E7A3 | | $1C | `IndDispatch1C` | high byte of the `jmp ($001B)` target | | $55 | `FrameSubCounter` | frame countdown (set to $0A at $C520, to 1 at $D226) | | $C2 | `PpuCtrlShadow` | PPUCTRL shadow — `ora #$04` / `and #$FB` / `and #$FC` / `eor #$01`, always followed by `sta PPUCTRL` | | $C4 | `NmiPathSelector` | NMI path selector (0 = minimal, ≠0 = full) | | $D6 | `GameState` | game state (`cmp #$02` at $C09A/$C4A5; used as an index at $C4C7) | | $EA | `PrngPeriodCounter` | PRNG phase counter — incremented and wrapped at $77 by $C412 | | $EB | `PrngSeedB` | PRNG seed byte — decremented and reloaded with $75 by $C412 | | $EC | `PrngState` | PRNG state byte — xored and stored back at $C43D | | $0200 | state bitmap | read in NMI path B; bits 0–3 dispatch | | $0268–$0273 | warm-start signature | 23 bytes, OR'd together and compared with $23 | | $0700 | OAM mirror | OAM DMA source for the minimal NMI path | --- ## 6. Differences from the Base Dump (`Raid on Bungeling Bay (U)`) | Property | Base (U) | [a2] variant | |----------|----------|--------------| | iNES header byte 6 | $01 — Vertical | **$00 — Horizontal** | | PRG SHA-256 | `46e80cd2d105178efc2adbdd49463421eb455b7729867c352ec8e5fb45d032d8` | `d30a3ea8a3ed0c4a90ab01f784780cf8ac0f0c769aa7f02d8202515b6e80cb53` | | PRG bytes differing | — | **150 of 16,384, in 7 runs** | | CHR | — | differs (byte 4097 onward) | | ROM MD5 | `7728550357cc8f6bde097627885000ac` | `659d340a1e9d420eee28df13319b0094` | The two PRGs differ in exactly two regions, and in both the difference is **code versus copyright text**: | Region | [a2] | Base (U) | |--------|------|----------| | $C67D–$C6D8 | Namco copyright string (`game ... copyright 1984 ... inc`) | executable code | | $C73B–$C779 | executable code | `©`/copyright/company strings | Everything else is byte-identical, so the two dumps disassemble to the same structure with the same label names. A side-by-side comparison of the two `prg.s` files confirms it: the zero-page reference sets are identical except for `$4E` (six references here, nine in the base dump — the base dump's `$C6A1`/`$C6A5`/`$C750`/`$C759` reads of `$4E` are code, and in this build those bytes are copyright text). **Naming divergence (2026-10-20).** This dump was finished after the base dump was promoted, and it found a naming error there. The base dump had named `$EA` `VBlankWaitCounter`; the evidence says it is the PRNG phase counter described in §3. `$EA` was renamed `PrngPeriodCounter` in **both** dumps, `$EB`/`$EC` were named `PrngSeedB`/`PrngState` in both, and the base dump's two `sta a:IndDispatch17,y` operands — absolute stores to RAM `$0017`, not the dispatch pointer — were corrected to `a:Zp17,y`. See `NROM/Raid on Bungeling Bay (U)/README.md`. --- ## Pass 3 data-table inventory Every data block in the PRG is listed below. The block boundaries are the tracer's (`; ---- data $XXXX-$YYYY` markers in `prg.s`); where a block holds more than one structure the sub-labels in `prg.s` mark the split. Raid on Bungeling Bay (U) is Konami's 1985 helicopter raid: you cross a map destroying the five enemy factories, each guarded by five targets, while a fuel gauge and a radar screen keep you informed. Most of the data below is therefore CHR tile indices (the game draws its screens from tiles rather than from graphics), per-target parameter records and small text strings. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C00A-$C031` | 40 | `RadarScreenTileTable` | four small tile-index tables: `$C00A-$C021` is the 24-tile status row (`$30`,`$21`,`$30`,`$17`,`$0F`,`$37`,...); `$C022-$C025` holds one tile per game state (`$19`,`$10`,`$1F`,`$1C`); `$C026-$C02D` holds the eight counter digits (`$1C`,`$14`,`$19`,`$1B`,`$13`,`$17`,`$0F`,`$15`); `$C02E-$C031` holds four tiles (`$07`,`$17`,`$17`,`$17`) | `lda $C00A,x` at `$C4D6` writes the 24 status tiles to VRAM through ``WriteByteToPpu``; `lda $C022,x` at `$C4C9` picks the tile for the current `GameState`; `lda $C026,y` at `$D6E0` picks a counter digit; `lda $C02E,x` at `$C4BC` picks one of the four tiles | | `$C35C-$C3E1` | 134 | `SoundEffectPeriodTable` + `EnemySpriteDefinitionTable` | `$C35C-$C37B` is 16 little-endian 16-bit APU pulse-1 timer periods (`$06AE`,`$064E`,`$05F3`,`$059E`,`$054D`,`$0501`,`$04B9`,`$0475`, ... - the same descending ladder the other Konami titles use). `$C37C-$C3E1` holds the per-enemy sprite and sound parameters | periods: `lda $C35C,x` at `$C347` and `lda $C35D,x` at `$C342`. sprite data: `lda $C374,x` at `$C1D1`, `lda $C3AC,x` at `$C1FA` (tile), `lda $C3B4,x` at `$C1FF` (attribute), `lda $C3BB,x` at `$C25D` (position), `lda $C3CC,x` at `$C2A7` and `$C529` (the sound period that is loaded into `APUNOISEPERIOD` at `$C2AC`) | | `$C625-$C6E2` | 190 | `SpritePositionOffsetTable` + `CopyrightCreditText` | $C625-$C67C holds four parallel 20-byte tables of small offsets - `$C625` (OAM Y), `$C629` (tile), `$C62D` (attribute) and `$C63D` (OAM X); the rest of the block, `$C67D-$C6E2`, is the credits screen's text: `"game" $7F "a" $BA "game" $7F "b" $BA "q" $7F "play" $CF` then `$7F` x4, `"copyright" $7F`, `"`" $7F`, `"{|}~" $7F`, `"fmg"` then `$7F` x17, `"by" $7F "kiku" $00` | offsets: `adc $C625,y` at `$C61E` -> `$0703,x`, `adc $C629,y` at `$C600` -> `$0700,x`, `adc $C62D,y` at `$C614` -> `$0702,x`, `adc $C63D,y` at `$C60A` -> `$0701,x`; each offset is added to a base held in `Zp49`, `Zp4A`, `Zp4B` and `Zp4D`. The credits text is printed by the routine at `$C73B` | | `$C8F0-$C8FA` | 11 | `GameOverText` | ASCII `"game"`, the control pair `$7F $7F`, ASCII `"over"`, `$00` - the game's end-of-game message | **no instruction in this dump reads it.** Unreferenced data, ROM offset `$08F0` | | `$C96F-$C98D` | 31 | `RoundMessageText` | a run of NUL-terminated words with control bytes between them: `"round"` `$DB` `"factory"` `$D9` `"left"` `$FA` `"score"` `$BB` `"top"` `$7F` `$7F` `"p"` `$00` - the six words the radar/status screen needs | **no instruction in this dump reads it.** Unreferenced data, ROM offset `$096F` | | `$CAD4-$CAE5` | 18 | `BaseTargetParameterTable` | three parallel six-byte tables: `$CAD4` (`$83`,`$85`,`$07`,`$63`,`$C9`,`$4B`), `$CADA` (`$03`,`$42`,`$44`,`$46`,`$86`,`$41`) and `$CAE0` (`$04`,`$0C`,`$13`,`$16`,...) - the position and type of the five targets guarding one factory | `lda $CAD4,x` at `$CAB7` -> `$04DE,x`, `lda $CADA,x` at `$CAC2` -> `$0322,x`, `lda $CAE0,x` at `$CACD` -> `$054D,x` | | `$CB7F-$CCBB` | 317 | `EnemyTargetParameterTable` | a set of parallel arrays indexed by enemy number, six bytes apart: the type at `$CB7F`, the tile at `$CB85`, the attribute at `$CB8B`, the position at `$CB91`, the score at `$CB97`, and then the six per-target fields at `$CBDB` (target tile), `$CBE1` (target attribute), `$CBE7` (score), `$CBED` (fuel), `$CBF3` (flag), `$CBF9` (flag), `$CBFF` (count) | `lda $CB7F,x` at `$CAF7`, `lda $CB85,x` at `$CAF2`, `lda $CB8B,x` at `$CB0D`, `lda $CB91,x` at `$CB08`, `lda $CB97,x` at `$CB13`, `cmp $CBDB,x` at `$CA14`, `lda $CBDB,x` at `$CA2D`, `lda $CBE1,x` at `$CA33`, `lda $CBE7,x` at `$CA39`, `lda $CBED,x` at `$CA3F`, `lda $CBF3,x` at `$CA45`, `lda $CBF9,x` at `$CA4B`, `lda $CBFF,x` at `$CA6B` | | `$CCEA-$CD25` | 60 | `SpriteDefinitionTable` | six parallel ten-byte tables - one per sprite slot: `$CCEA` (sprite type), `$CCF4` (tile), `$CCFE` (attribute), `$CD08` (X), `$CD12` (Y), `$CD1C` (frame) | `lda $CCEA,x` at `$CCC2`, `lda $CCF4,x` at `$CCC8`, `lda $CCFE,x` at `$CCCE`, `lda $CD08,x` at `$CCD4`, `lda $CD12,x` at `$CCDA`, `lda $CD1C,x` at `$CCE0` - the values are copied into the per-sprite RAM slots `$028B`, `$0369`, `$02FA`, `$03D8`, `$0447` and `$04B6` | | `$CE08-$CE1D` | 22 | `SpriteOamShadowTable` | 22 bytes copied straight into the zero-page sprite shadow at `ZpA5` | `lda $CE08,x` at `$CD79` with X counting up to `$15` | | `$CE96-$CED2` | 61 | `SpriteOffsetTable` | `$CEC9-$CECD` and `$CECE-$CED2` are two five-byte offset tables; the bytes before them are not read | `lda $CEC9,x` at `$CE36` -> `Zp4E` and `lda $CECE,x` at `$CE3B` -> `Zp4F` | | `$CFF4-$CFFE` | 11 | `CompleteText` | the control pair `$7F $7F`, ASCII `"complete"`, `$00` - the message shown when a factory is destroyed | **no instruction in this dump reads it.** Unreferenced data, ROM offset `$0FF4` | | `$D115-$D124` | 16 | `FuelGaugeText` | the text `" U"` + `$00`, then the sprite data `$62`,`$B8`,`$64`,`$00`,`$6C`,`$D0`,`$44`,`$00`,`$62`,`$D0`,`$86`,`$00`,`$86` - the fuel-gauge lettering and the sprites that make up the gauge | `lda $D115,x` at `$D0AE` copies all sixteen bytes into `$07C8-$07D7`; `lda $D119,x` at `$D075` copies five of them into `$07F4-$07F8` | | `$D1F8-$D1FF` | 8 | `SproutTileTable` | 8 bytes (`$00`,`$00`,`$32`,`$1E`,`$0A`,`$28`,`$00`,`$14`) | `lda $D1F8,x` at `$D1C3` | | `$D245-$D24B` | 7 | `MaskBitTable` | 7 mask bytes (`$1F`,`$1F`,`$1F`,`$3F`,`$7F`,`$7F`,`$FF`) | `lda $D245,x` at `$D262` | | `$D3FE-$D421` | 36 | `MovementAngleTable` | two parallel 18-byte tables of small values: `$D3FE` (`$00`,`$85`,`$83`,`$00`,`$03`,`$05`,`$07`,`$08`,`$07`,`$05`,`$03`,`$00`,`$83`,`$85`,`$87`,`$88`,`$86`,`$84`) and `$D410` - the direction an object moves in, as a packed angle | `lda $D3FE,y` at `$D3C3` and `$D3D9`, `lda $D410,y` at `$D3CF` and `$D3EB`; the high bit is then forced into the accumulator (`ora #$80` at `$D3E0`/`$D3F2`) to select the direction | | `$D4AD-$D4BE` | 18 | `MovementDeltaTable` | 18 single-byte values (`$FF`,`$16`,`$14`,`$12`,`$14`,`$16`,`$12`,`$0E`,`$12`,`$16`,`$16`,`$14`,`$16`,`$16`,`$12`,`$0E`,...) | `lda $D4AD,x` at `$D496` -> `Zp0B`/`Zp0A`, the speed the object is moved by | | `$D53D-$D560` | 36 | `EnemySpeedTable` | two parallel 18-byte tables: `$D53D` (`$FF`,`$04`,`$03`,`$00`,`$83`,`$84`,`$86`,`$88`,`$86`,`$84`,`$83`,`$00`,`$03`,`$04`,`$06`,`$08`,...) and `$D54F` - the speed at which each enemy closes on its target | `lda $D53D,x` at `$D4CC` and `lda $D54F,x` at `$D4D7` | | `$D5BD-$D5C8` | 12 | `FuelThresholdTable` | three little-endian 16-bit thresholds - `$D5BD`/`$D5BE` = `$0032`, `$D5C0`/`$D5C1` = `$01A2`, `$D5C2`/`$D5C3` = `$0000`, `$D5C4`/`$D5C5` = `$000C`, `$D5C6`/`$D5C7` = `$8007` - the fuel levels at which the gauge changes | `adc $D5BD,x` at `$D57F`, `lda $D5C0,x` at `$D584`, `cmp $D5C3,x` at `$D59A`, `cmp $D5C6,x` at `$D5A4`, `sbc $D5C6,x` at `$D5AD` and `sbc $D5C3,x` at `$D5B6` | | `$D676-$D68D` | 24 | `RadarBlipOffsetTable` | two parallel 12-byte tables of small offsets: `$D676` (`$18`,`$18`,`$18`,`$28`,`$10`,`$10`,`$20`,`$30`,`$18`,`$28`,`$10`,`$20`) and `$D682` (`$0E`,`$0E`,`$0E`,`$0A`,...) | `lda $D676,x` at `$D65E` and `lda $D682,x` at `$D658` | | `$D8B7-$D8D9` | 35 | `EnemySpriteOffsetTable` | five parallel seven-byte tables: `$D8B7`, `$D8BE`, `$D8C5`, `$D8CC` and `$D8D3` - the five offsets that place one enemy's parts on the radar blip | `cmp $D8B7,y` at `$D891`, `lda $D8BE,y` at `$D89B`, `lda $D8C5,y` at `$D873`, `lda $D8CC,y` at `$D87D`, `lda $D8D3,y` at `$D878` | | `$D933-$D941` | 15 | `TargetTileTable` | 15 tile indices (`$D2`,`$D7`,`$D3`,`$D3`,`$C2`,`$C7`,`$D3`,`$D2`,`$D2`,`$D6`,`$C3`,`$C3`,`$C2`,`$C6`,`$C3`) - the tiles the five targets of a factory are drawn from | `lda $D933,x` at `$D911` | | `$DA75-$DBA7` | 307 | `LevelLayoutData` | the level's own data: the radar map rows and the factory layouts, in the same tile-index encoding as the rest of the PRG | `lda $DADD,x` at `$C72A`, `lda $DA8D,y` at `$DA2E`, `lda $DA99,y` at `$DA34`, `lda $DB49,y` at `$DBCC`, `lda $DB8B,y` at `$DBD0`, `lda $DB6E,y` at `$DBD5` | | `$DC5B-$DC80` | 38 | `EnemyCountTable` | three parallel tables: `$DC5B` (12 bytes, `$A0`,`$A1`,`$A1`,`$A2`,`$A3`,`$A3`,`$A4`,`$A5`,`$A5`,`$A6`,`$A7`,`$A7`), `$DC67` (13 bytes) and `$DC74` (12 bytes) - how many enemies of each kind the level contains and when they appear | `lda $DC5B,y` at `$DC45`, `lda $DC67,y` at `$DC50`, `lda $DC74,y` at `$DC55` | | `$DEFC-$E01F` | 292 | `LevelSpriteData` | the per-level sprite definitions: rows of tile indices and attribute bytes that the game copies into the sprite buffer | `lda $DEFC,y` at `$DCB6` and `$DC7D`, `lda $DF0D,y` at `$DCBF` and `$DEDA`, `lda $DFE4,y` at `$DEC3`, `lda $DFA8,y` at `$DEC8` | | `$E146-$E156` | 17 | `SproutTileTable2` | 17 tile indices (`$00`,`$00`,`$01`,`$02`,`$03`,`$06`,`$05`,`$04`,`$03`,`$00`,`$0B`,`$0A`,`$09`,`$06`,`$07`,`$08`,...) | `lda $E146,x` at `$E126` | | `$E1EC-$E205` | 26 | `EnemyTileIndexTable` | two parallel 13-byte tables: `$E1EC` (`$00`,`$04`,`$07`,`$08`,`$07`,`$04`,`$00`,`$84`,`$87`,`$88`,`$87`,`$84`,`$00`) and `$E1F9` (`$86`,`$85`,`$83`,...) - the tiles and attributes of the enemy sprites | `lda $E1EC,y` at `$E1DD` and `lda $E1F9,y` at `$E1E2` | | `$E238-$E250` | 25 | *(unreferenced - unreachable code)* | 6502 code: `LDA $A5`, `LDX $A5`, `ASL $6A`, `BCS`, `INC $FE`, `LDA $043C`, `LDA $043C`, `CMP #$84`, `BNE`, `JSR` | **no instruction operand lands in this block.** Unreached code, ROM offset `$2238` | | `$E2EF-$E300` | 18 | `EnemyRoutinePointerTable` | 9 little-endian 16-bit code addresses: `$E320`,`$E24B`,`$E38B`,`$E320`,`$E37B`,`$E31D`,`$E320`,`$E3CE`,`$E323` - the routine each enemy type is driven by | `lda $E2EF,x` at `$E2E0` and `lda $E2F0,y` at `$E2E5`; the README of the project records the four-entry walk at `$E2EF` | | `$E31D-$E31F` | 3 | *(unreferenced - unreachable code)* | the instruction `JSR $E220` | **no instruction operand lands in this block.** Unreached code, ROM offset `$231D` | | `$E35B-$E3B4` | 90 | *(unreferenced - unreachable code)* | 6502 code: `DEC $2C`, `BEQ`, `LDA $0444`, `STA $0349`, `LDX $2B`, `LDA #$08` | **no instruction operand lands in this block.** Unreached code, ROM offset `$235B` | | `$E3CE-$E411` | 68 | *(unreferenced - unreachable code)* | 6502 code: `LDA $A5`, `AND #$03`, `BEQ`, `LDA $0443`, `CMP #$83`, `BCS`, `INC $0443` | **no instruction operand lands in this block.** Unreached code, ROM offset `$23CE` | | `$E566-$E586` | 33 | `TargetTileIndexTable` | a run of tile indices in groups of five separated by `$00` (`$00`,`$04`,`$08`,`$04`,`$00`, `$84`,`$88`,`$84`,`$00`,`$86`,`$83`,`$00`,`$03`,`$06`,`$03`,`$00`, ...) - the tiles each target is drawn with | `lda $E578,y` at `$E546` | | `$E6CD-$E6E4` | 24 | `DirectionBitMaskTable` | the eight single-bit masks `$01`,`$02`,`$04`,`$08`,`$10`,`$20`,`$40`,`$80` twice over - one set for each of the two joystick directions | `and $E6CD,x` at `$D730`, `$D74C`, `$D755` and `$D7A5`; `lda $E6CD,y` at `$E08E`; `and $E6CD,y` at `$E589` | | `$E821-$E82C` | 12 | `TargetSpawnTable` | three four-byte records: `($03`,`$BB`,`$76`,`$25)`, `($F3`,`$F2`,`$F3`,`$F3`) and `($08`,`$04`,`$02`,`$01`)` | `lda $E821,x` at `$E806`, `lda $E825,x` at `$E80B`, `lda $E829,x` at `$E810` | | `$E90F-$E922` | 20 | *(unreferenced - unreachable code)* | 6502 code: `STA $30`, `INC $F8`, `ORA $0C`, `BEQ`, `STA $30`, `BNE`, `INC $E7`, `DEC $C0` | **no instruction operand lands in this block.** Unreached code, ROM offset `$290F` | | `$EBCC-$EC9D` | 210 | *(unreferenced - unreachable code)* | 6502 code: `INC $EB`, `INC $EF`, `INC $C5`, `LDA $0D`, `LDA $CD`,`ADC #$03`,`JMP $EC0E` | **no instruction operand lands in this block.** Unreached code, ROM offset `$3BCC` | | `$ED1E-$ED60` | 67 | *(unreferenced - unreachable code)* | 6502 code: `LDY #$07`, `LDA $0065,y`, `STA $0310,x`, `JMP $EC9E`, `CMP #$15`,`BCC`,`CMP #$0B` | **no instruction operand lands in this block.** Unreached code, ROM offset `$3D1E` | | `$EE5C-$EE65` | 10 | *(unreferenced - unreachable code)* | 6502 code: `ORA #$07`, `ORA #$0D`, `CMP #$80`, `BCS`, `LSR $30`, `INC $F8` | **no instruction operand lands in this block.** Unreached code, ROM offset `$3E5C` | | `$EEB4-$EEBB` | 8 | *(unreferenced - unreachable code)* | 6502 code: `LDA $32`, `LDY $A4`, `LDA #$01`, `ORA #$0C`,`CMP #$80`,`BCS` | **no instruction operand lands in this block.** Unreached code, ROM offset `$3EB4` | | `$EF27-$EF30` | 10 | `FuelGaugeTable` | 10 bytes (`$80`,`$80`,`$A0`,`$B0`,`$80`,`$30`,`$20`,`$04`,`$30`,`$30`) - the pattern the fuel bar is drawn with | `lda $EF27,y` at `$EF19` and `lda $EF2C,y` at `$EF1D` | | `$F1A0-$F1E6` | 71 | `PlayerMovementDeltaTable` | six parallel twelve-byte tables of small deltas - `$F1A0` (`$0B`,`$0A`,`$09`,`$08`,`$07`,`$06`,`$05`,`$04`,`$03`,`$02`,`$01`,`$00`), `$F1AC`, `$F1B0`, `$F1B4`, `$F1C0`, `$F1C8` - the per-frame movement of the helicopter and the other objects | `lda $F1B4,x` at `$F018`, `$F067` and `$F085`, `lda $F1C0,x` at `$F03C`, `lda $F1B0,x` at `$F095`, `lda $F1C8,x` at `$F0A7` | | `$F2BB-$FFFF` | 3,397 | *(unreferenced - unreachable code)* | a long 6502 routine (`LDX $5A`, `LDY $58`, `JSR $F1E5`, `STX $5A`, `STX $5C`, ...) that runs to the end of the PRG, with the three CPU vectors in its last six bytes (NMI `$C04C`, RESET `$C000`, IRQ `$C2C0`) | **no instruction operand lands in this block.** Unreached code, ROM offset `$32BB` | ## 6a. Pass 3 data-table inventory See the [Pass 3 data-table inventory](#pass-3-data-table-inventory) below: all 42 data blocks in this PRG are interpreted - 32 of them carry a name (67 labels in all) and the 10 that carry none are blocks whose bytes are 6502 code the tracer could not reach. This revision differs from the base dump only in `$C67D-$C6D8` (the credits screen text) and `$C73B-$C779` (the routine that prints it), so the two inventories are otherwise identical. ## 7. Naming and Residue **Pass 1 complete** — 14,326 lines, 5,079 instruction lines covering 10,536 code bytes (64.3%), 388 data lines covering 5,848 bytes, 42 explicit `; ---- data $XXXX-$YYYY` regions, canonical audit comments throughout. Every one of the 5,467 audit comments was checked against `prg.bin` at its stated PRG offset: 0 mismatches. **Pass 2 complete** — 866 label definitions, **zero `Label_` residue**: the 252 `SubC` entry names for the `jsr`/`jmp` targets have all been replaced with semantic names (see [Address-derived entry points (residue)](#address-derived-entry-points-residue)), leaving 610 `Sub__` internal branch-target names, plus `Reset`, `ResetEntry`, `NmiHandler` and `IrqHandler`. **No raw `$XXXX` operand** remains on any `jsr`, `jmp` or branch — the four indirect jumps carry their `IndDispatchXX` aliases. **Pass 3 partial** — `locals.inc` holds 181 equates: 166 neutral `ZpXX` slots plus 15 semantic aliases (`PtrLo`, `PtrHi`, `IndDispatch01`/`02`/`17`/`19`/`1B`/`1C`, `FrameSubCounter`, `PpuCtrlShadow`, `NmiPathSelector`, `GameState`, `PrngPeriodCounter`, `PrngSeedB`, `PrngState`). Every alias is applied at the sites where its role is proven; the addresses that carry two names keep the neutral name at the sites where the other role applies (see §5). `apply_symbols.py` reports 0 substitutions pending and 0 residue. **Residue (recorded, not guessed):** - 376 raw absolute operands against 90 distinct RAM addresses in $0000–$07FF ($02xx entity/page buffers, the $0268–$0273 warm-start signature, the $0700 OAM mirror). Their roles are only partly traced, so they stay numeric rather than guessable — the same residue class accepted in `Pinball (JU)` and `Ice Climber (U)`. - The 42 data regions are unnamed. The base dump has the same gap; a table-naming pass would be the natural next step for both. - `$4E` has six references here against nine in the base dump (see §6). --- ## 8. Promotion to `NROM/` (2026-10-20) Moved out of `_wip/NROM/` with `git mv`; the build now compares the **whole ROM**, not just the PRG: - `header.s` / `chr.s` added (one-line `.incbin`s of `header.bin` / `chr.bin`) - `linker.cfg` gained the `HEADER` ($0000, $0010) and `CHR` ($0000, $2000) areas ahead of `PRG` ($C000, $4000) - `rom.nes` — symlink to `Raid on Bungeling Bay (U) [a2].nes` (the original ROM name contains spaces and brackets) - `Makefile` builds `header.o` / `prg.o` / `chr.o` into `reconstructed.nes` and compares it against `rom.nes` `reconstructed.nes` is 24,592 bytes, MD5 `659d340a1e9d420eee28df13319b0094` — equal to the original ROM. The `header.bin` / `prg.bin` / `chr.bin` splits were checked against the original before promotion: they are the ROM's first 16 bytes, its next 16,384 bytes and its last 8,192 bytes, exactly. Build verification: `make` in `NROM/Raid on Bungeling Bay (U) [a2]/` produces `reconstructed.nes`, compared against `rom.nes` (**whole-ROM byte-identical**). ## Address-derived entry points (residue) `PROMPTS.md` asks every `jsr`/`jmp` entry point to carry a semantic name and accepts the address-derived `SubCXXXX` form **only where the dump does not reveal a better one**. This dump started with **220** address-derived entry points; **all 220 now carry a semantic name and none remain address-derived.** The names are the same ones the base dump `Raid on Bungeling Bay (U)` carries, because the two revisions share their code: the two PRGs differ in only 150 bytes, all of them inside `$C67D-$C6D8` and `$C73B-$C779`, where this revision puts the "game over" / "complete" text where the base dump has the radar-screen code. Every routine outside those two blocks is byte-identical in the two dumps, so the semantic name that fits the base dump's routine fits this one. The three names the base dump needs and this one does not (`ReadPpuData` at `$C67D`, `GetRadarScreenAddress` at `$C770` and `DrawRadarScreen` at `$C77D`) are simply absent here. Representative names: `UpdatePseudoRandomNumber` (`$C412`), `UploadPaletteToPpu` (`$C48E`), `DrawSpritePart` (`$C5A1`), `ConvertBcdToBinary` (`$C85A`), `WriteScoreDigit` (`$C921`), `InitTargetScan` (`$CA04`), `UpdateFuelGauge` (`$D561`), `CheckSpawnState` (`$D8A7`), `DispatchEnemyRoutine17` (`$E79D`) and `LoadTargetSpawnRecord` (`$E806`). ## 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 | |---------|---------| | `$C6E3` | `ResetEntry` | | `$C6EA` | `Sub_8764_OnNonnegative_C6EA` (falls through) | | `$C6F6` | `Sub_8764_OnNonzero_C6F6` (falls through) | | `$C98E` | `ClearGameRam` | | `$C70E` | `Sub_8764_OnNonnegative_C70E` (falls through) | **NMI** is at `$C04C` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| **IRQ** is at `$C2C0` (`IrqHandler`) - a different address from RESET.