# 1942 (JU) ## Status **Pass 3 complete; promoted.** `prg.s` is a traced disassembly with all address-derived labels replaced, all `jsr`/`jmp` operands symbolic, hardware registers named via `symbols.inc`, and `apply_symbols.py` reporting 0 residue. Code coverage: 17,065 / 32,768 bytes (52.1%). `make` assembles and verifies byte-identical to `rom.nes`. The Pass 3 data-table work is finished: every one of the 26 data blocks in the PRG is listed below with its structure and the instruction that reads it. Seventeen of the blocks carry a label - eighteen labels in all, because the `$8494` block has a second one at `$9EDB` and the `$B200` block's label sits at `$B33B`, where the data that is actually read begins. The other nine are recorded with their ROM provenance; the eight that no instruction in this dump reads are deliberately left unnamed in the source (a block no instruction reads must not be named after what it might be), and `$FC9C-$FFFF` is named for the four tables it holds rather than for the unreachable code around them. Two defects were repaired while doing this: 13 `.byte` rows carried a duplicated audit comment left behind by `repair_audit_comments.py` (the first copy had a negative PRG offset, `PRG:-1F74`, which is meaningless for a PRG mapped at `$8000`), and 7 rows that document an undocumented opcode carried the note *after* the audit comment instead of on its own line. Both were comment-only changes; the build is still byte-identical. ## Entry-point naming (2026-10-21) All **375** address-derived entry points (`Sub_xxxx` / `SubCxxxx`) in this project now carry a semantic name; **0 remain address-derived**. The names come from what the ROM shows each routine does, read out of the routine's own header, its callers and the RAM fields it touches: | Area | Names | |------|-------| | reset / NMI | `Reset`, `NmiHandler`, `RunDeferredCallQueue`, `QueueDeferredCall`, `QueueDeferredCallWithPosition`, `EnableNmi`, `ReadControllers`, `ReadControllerPort`, `RunVramCommandQueue` | | VRAM / PPU | `WriteVramBlockToPpu`, `ApplyVramUpdateQueue` (the `$078F` command queue), `BuildVramColumnQueue`, `QueueColumnTile`, `SetVramWritePointerToColumn`, `SetVramWritePointerToRow`, `ApplyColumnAttribute`, `ComputeColumnAttribute`, `SetColumnOccupancyBit`, `MarkColumnOccupied` | | scroll | `UpdateBackgroundScroll`, `UpdateVerticalScrollOffset`, `ScrollBackgroundLeft`, `ScrollBackgroundRight`, `ScrollBackgroundUp`, `ResetNametableBuffer`, `ResetScrollPointers`, `SetScrollPosition`, `ApplyScrollWaveLeft`…`ApplyScrollWaveDown` | | player | `InitPlayerState`, `InitPlayerHitbox`, `TestPlayerHitbox`, `CheckPlayerHitEnemy`, `CheckPlayerShotCollision`, `ResetPlayerShotsAndObjects` | | objects / enemies | `InitObjectTables`, `InitObjectFromRecord`, `SpawnObject`, `DespawnObject`, `UpdateObject`, `UpdateObjectState`, `AdvanceObjectState`, `SetObjectTileIndex`, `SetObjectAttribute`, `MoveObjectToPlayer` | | enemy spawn table | `DispatchEnemySpawnCommand`, `SpawnEnemyObject`, `DespawnEnemyObject`, `SetEnemyObjectFlag`, `SetEnemyObjectX`, `SetEnemyObjectY`, `ReadEnemyScriptValue`, `BuildEnemyObjectRecord` | | enemy behaviour table | `SpawnEnemyFromTypeTable`, `InitEnemyType0`…`InitEnemyType9`, `InitDefaultEnemyType`, `RunEnemyTypeBehaviour`, `LoadEnemyPatternData`…`LoadEnemyPatternDataAlt3`, `CheckEnemyCollisions`, `TriggerEnemyDrop` | | text / HUD | `SetVramPointerToText`, `ClearTextBuffer`, `AtEndOfTextRecord`, `AdvanceTextColumn`, `AdvanceTextRow`, `AdvanceVramWritePointer`, `WriteScoreTextToVram` | | stage | `InitStageState`, `StartNewStage`, `StartNextStage`, `AdvanceStageSequence`, `ResetLevelState`, `ResetObjectArray` | The `SubCxxxx` labels that survive (`ColumnClassTable`, `EnemyPatternDataPart1`…, `EnemyMovementDataPart1`…) sit on **data** blocks, not on code: the tracer split them because some of their bytes decode as instructions. They are named for the block they head, not for a routine. --- ## 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 `$BF90` (`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 | |---------|---------| | `$BF9A` | `Reset_OnNonnegative` (falls through) | | `$BFAD` | `Reset_Loop` (falls through) | | `$BFB7` | `Reset_OnNonzero` (falls through) | | `$BFCA` | `Reset_Loop2` (falls through) | **NMI** is at `$C030` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| **IRQ** is at `$BF90` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path. ## ROM | Property | Value | |----------|-------| | Format | iNES | | Mapper | 0 (NROM) | | PRG | 32 KB | | CHR | 8 KB | | Mirroring | Horizontal | | NMI vector | $C030 | | RESET vector | $BF90 | | IRQ vector | $BF90 | ## Power-on flow RESET enters at `$BF90`: sei ; CPU:$BF90 PRG:3F90 78 cld ; CPU:$BF91 PRG:3F91 D8 ldx #$F3 ; CPU:$BF92 PRG:3F92 A2 F3 txs ; CPU:$BF94 PRG:3F94 9A lda #$10 ; CPU:$BF95 PRG:3F95 A9 10 sta PPUCTRL ; CPU:$BF97 PRG:3F97 8D 00 20 lda PPUSTATUS ; CPU:$BF9A PRG:3F9A AD 02 20 bpl Reset_OnNonnegative ; CPU:$BF9D PRG:3F9D 10 FB lda $07BD ; CPU:$BF9F PRG:3F9F AD BD 07 cmp #$55 ; CPU:$BFA2 PRG:3FA2 C9 55 bne Reset_Loop ; CPU:$BFA4 PRG:3FA4 D0 07 lda $07BE ; CPU:$BFA6 PRG:3FA6 AD BE 07 NMI handler at `$C030`: sta Zp63 ; CPU:$C030 PRG:4030 85 63 stx Zp64 ; CPU:$C032 PRG:4032 86 64 sty Zp65 ; CPU:$C034 PRG:4034 84 65 lda PpuCtrlShadow ; CPU:$C036 PRG:4036 A5 8C and #$7F ; CPU:$C038 PRG:4038 29 7F sta PPUCTRL ; CPU:$C03A PRG:403A 8D 00 20 sta PpuCtrlShadow ; CPU:$C03D PRG:403D 85 8C lda #$00 ; CPU:$C03F PRG:403F A9 00 ## Data-table inventory (Pass 3) Every data block in the PRG, its structure, and the instruction in this dump that reads it. "Unreferenced" means no instruction in the disassembly addresses the block, so it is recorded here with its ROM provenance instead of being named in `prg.s`. The tracer left 26 blocks; the table below has 27 rows because the `$8494-$9FDA` block holds a second, separately-read table at its tail (`$9EDB`). | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$8494-$9FDA` | 6983 | `StageScriptData` (partly) | Mixed stage data: a stream of 8-byte records `$01 <7 bytes> $00` from `$8494`, then opcode-driven runs (`$DC`, `$DE`, `$DF`, `$AD` prefixes, `$FF` fill) around `$8738-$87D4`, then long runs of CHR tile indices in the `$80-$FF` range (nametable tile data) from about `$8800` on, then tile-index/text runs from `$9B04`. The last 133 bytes `$9EDB-$9FDA` are a separate byte table (see below). | `lda $9EDB,y` at `$82E7` — the only direct reference; the rest of the block is reached only through computed pointers the static tracer cannot follow, so its record format is **not established** | | `$9EDB-$9FDA` | 133 | `StageColumnAttributeTable` | 133 bytes, values 0-3 in runs (5×`00`, 8×`01`, 49×`03`, 11×`01`, 16×`00`, …). One entry per background column of the scrolling stage; the value selects which of four attribute-bit groups the column uses. | `lda $9EDB,y` at `$82E7`; `Y` is the class byte `$03AB`, itself read from the runtime per-column table at `$0724` by `lda ($CC),y` at `$8249`. The byte is shifted left 2-4 times (`asl $0734` at `$831B`/`$8321`/`$8327`) and ORed into the nametable byte read at `$82F0` | | `$A08C-$A093` | 8 | `ColumnOccupancyBitMaskTable` | 8 bytes, one bit each: `$01 $02 $04 $08 $10 $20 $40 $80` | `lda $A08C,y` at `$A082`; `Y` = `A & 7`. `SetColumnOccupancyBit` sets bit `A & 7` of the RAM byte at `$07C1 + (A >> 3)` (`ora (VramWritePtrLo),y` / `sta (VramWritePtrLo),y` at `$A087`/`$A089`), so the RAM at `$07C0` is an occupancy bitmap of 8-pixel columns. Called with `$0F`, `$10`, `$11` from `$B647` and `$AD31` | | `$A2F2-$A311` | 32 | `EnemySpawnCommandHandlerTable` | 16 × 16-bit little-endian code addresses: `$A312, $A332, $A31E, $A31E, $A35A, $A31E, $A34A, $A31E`, then `$A31E` × 9 | `lda $A2F2,x` at `$A2E5` and `lda $A2F3,x` at `$A2EA`, then `jmp ($83)` at `$A2EF`. The index is `A & $0F` (`and #$0F` / `asl a` / `tax` at `$A2E1`-`$A2E4`) and `A` is the script byte, which is ≥ `$F0` (`cmp #$F0` / `bcc` at `$A25E`), so the table is indexed by *command − `$F0`*. `$A312` spawns the object (builds its tiles via `BuildEnemyObjectRecord`), `$A332`/`$A34A`/`$A35A` set its flag / X / Y bytes, and `$A31E` despawns it (`sta $0746,y` with `$FF` at `$A32C`) | | `$A3E2-$A73D` | 860 | `EnemyTypeScriptData` | Per-enemy-type data. `$A3E2-$A3F8` = 23 bytes of small values 1-4 (one per type). `$A3F9-$A410` = 12 × 16-bit little-endian fixed-point X offsets. `$A411-$A412` = the 16-bit base `$0735` of the object-record array. `$A413-$A440` = 23 × 16-bit little-endian pointers to the per-type behaviour scripts (`$A441`, `$A448`, `$A44F`, `$A456`, `$A46D`, `$A486`, `$A49F`, `$A4C6`, `$A4FB`, `$A502`, `$A529`, `$A532`, `$A553`, `$A578`, `$A5A5`, `$A662`, `$A66F`, `$A67E`, `$A68D`, `$A69B`, `$A6A2`, `$A6AF`, `$A6BA`). `$A441`-`$A73D` = those behaviour scripts: a bytecode stream in which `$0F $01 $FF` terminates a group and the other opcodes carry a tile index and a movement/position operand | `lda $A411` at `$9FEB` and `lda $A412` at `$9FF0` load the object-record base; `lda $A413,y` at `$A171` and `$A177` read the per-type script pointer (index = type × 2) into the object's record at `$0735`; `lda $A3F9,x` at `$A288` and `lda $A3FA,x` at `$A28D` read the 16-bit X offset, `X` = 2 × (script byte >> 4), the low nibble being the number of right shifts applied to it (`lsr Zp83` / `ror Zp84` loop at `$A298`) | | `$A770-$A815` | 166 | *unreferenced* | `$A770-$A7AF` = 64 bytes of 3-byte groups (19 complete records plus 1 byte); the values are CHR tile indices (`$00-$23` are the digits and letters, see below, `$24`+ are graphics). `$A7B0-$A815` = **code the tracer could not reach**: six routines that manipulate the 16-bit pointer at `$8A/$8B` (increment, double, add/subtract the PPU address, and a decimal add of `$0A` that carries into `$038B`) | none in this dump; the copy of the same 64-byte table at `$B783` is also unreferenced | | `$ABD0-$AC13` | 68 | *unreferenced* | 68 bytes, all values in `$02-$20` (68 one-byte values, no repeating structure that the dump explains) | none | | `$AD99-$ADC8` | 48 | *unreferenced* | 12 × 4-byte NES OAM records (Y, tile, attribute, X): Y = `$20` (off-screen), tile = `$FF`, attribute = `$03`, X = `$20, $28, $30, $38, $40, $48, $B0, $B8, $C0, $C8, $D0, $D8` | none; the same shape as the OAM templates at `$C366` and `$E19E`, which *are* read | | `$B200-$B37A` | 379 | `BackgroundScrollWaveTable` (from `$B33B`) | `$B200-$B2FA` = 16-byte rows each carrying a 5-byte group `04 ` at offset 11 with values 1-4; `$B2FB-$B33A` = 64 bytes of 16-bit values; `$B33B-$B37A` = 32 × 16-bit little-endian signed values, of which the first 26 form 13 `(sin θ, −cos θ)` pairs for θ = 0°, 22.5°, …, 270° with amplitude 256 (`0`, `-256`, `$61`=97, `$FF14`=-236, `$B5`=181, `$FF4B`=-181, `$EC`=236, `$FF9F`=-97, `$0100`=256, …) | `lda $B33B,y` at `$AF55` and `$AF60` load the pair into the VRAM write pointer (`$8A`/`$8B`) and set the flag `$038B` = `$FF` when the high byte's sign bit is set (`bpl` at `$AF65`); `$B200-$B33A` is unreferenced | | `$B4DC-$B4DF` | 4 | `ShotHitRowThresholdTable` | 4 bytes `$22 $27 $2C $31` (34, 39, 44, 49), increasing by 5 | `lda $B4DC,y` at `$B4A7`; `Y` = `(-$03A1) >> 6`, i.e. the player's row. The value is compared with the target's Y (`cmp PlayerY` at `$B4AC`) and the shot is rejected when the target is above the threshold (`bcc` at `$B4AE`) | | `$B783-$B7C2` | 64 | *unreferenced* | Byte-identical copy of the 64-byte table at `$A770-$A7AF` | none | | `$B9C5-$B9E4` | 32 | *unreferenced* | 32 bytes, every one `$FF` | none; `$FF` is the "no entry" fill value used by the game's own tables (e.g. `$ABD0`) | | `$BB51-$BC85` | 309 | `TitleScreenTileData` | `$BB51-$BB70` = 8 groups of 4 bytes `0F `; `$BB71-$BBB0` = 64 bytes of tile data (16 × `$00`, 16 × `$55`, 32 × `$00`); `$BBB1-$BC85` = 5-byte groups separated by `$FF` runs, then `$BC5F-$BC85` = `$FF`-separated groups of 6-8 tile indices | `lda $BB71,y` at `$BA1F` copies 64 bytes to the PPU command queue at `$0300`, and `lda $BB71,y` at `$BA2A` copies the same 64 bytes to `$0340`; the pointer is set to `$BB71` at `$BA37`. `$BB51-$BB70` is unreferenced | | `$BF0F-$BF57` | 73 | *unreferenced* | 73 bytes in `$FF`-separated groups of 6-9 values, all in `$01-$26` | none | | `$C366-$C3C9` | 100 | `OamTemplateAndPatternUpdateTable` | `$C366-$C371` = 12 bytes of OAM (3 sprites × Y, tile, attribute, X); `$C372-$C37D` = the same 12 bytes for the alternative plane set; `$C37E-$C389` = 12 bytes of Y positions for OAM sprites 9-11; `$C38A-$C3A9` = 16 × 16-bit little-endian PPU write addresses (`$000B`, `$001B`, `$002B`, `$003C`, `$004D`, `$005E`, `$006F`, `$0080`, `$0091`, `$00A2`, `$00B3`, `$00C4`, `$00D1`, `$00DE`, `$00EB`, `$0000`); `$C3AA-$C3C9` = 16 × 16-bit values written at those addresses | `lda $C372,y` at `$C156` and `lda $C366,y` at `$C166` copy the 12-byte OAM templates to `$0200` (Y 0-11); `lda $C37E,y` at `$C173` copies the sprite Y bytes to `$0224`; `lda $C38A,x` at `$C20B` gives the PPU address (`sbc #$02` / `sta PpuAddrLow` at `$C20F`/`$C211`); `lda $C3AA,x` at `$C21E` and `lda $C3AB,x` at `$C224` supply the two bytes written there | | `$C555-$D069` | 2837 | *unreferenced* | The bytes decode as long runs of instructions (e.g. 95 consecutive instructions at `$C696-$C6F4`), so this is **code the tracer could not reach**, not a table | none; the end of the block (`$D069`) is the target of the pointer at `$C3B0` | | `$DF58-$DF7F` | 40 | `PlaneTypeScreenSetupTable` | 5 × 8-byte records, one per plane type 5-9: `[PPU address hi, PPU address lo, buffer hi, buffer lo, OAM byte 0-3]`; e.g. `$DF58` = `00 00 05 00 FF FF FF 05` | `lda $DF58,y` at `$DF26`, `$DF2C`, `$DF32`, `$DF38`, `$DF3E`, `$DF44`, `$DF4A`, `$DF50` — eight consecutive reads that fill `PpuAddrHigh`, `PpuAddrLow`, `VramWritePtrHi`, `VramWritePtrLo`, `$E0`, `$DF`, `$DE`, `$DD`. The index is `($0721 - 5) * 8` (`asl a` × 3 at `$DF22`-`$DF24`) | | `$DFBE-$DFE5` | 40 | `PlaneTypeOamSetupTable` | 5 × 8-byte records with the same field layout, e.g. `$DFBE` = `00 01 00 00 FF FF 01 00` | `lda $DFBE,y` at `$DF8C`, `$DF92`, `$DF98`, `$DF9E`, `$DFA4`, `$DFAA`, `$DFB0`, `$DFB6`; the index is `($0721 - 5) * 8` (`asl a` × 3 at `$DF88`-`$DF8A`) | | `$E19E-$E1DF` | 66 | `PlaneOamTemplateTable` | `$E19E-$E1AB` = 14 bytes of tile indices; `$E1AC-$E1B7` = 3 × 4-byte OAM records (`48 FF 01 68`, `48 FF 01 70`, `48 FF 01 78`); `$E1B8-$E1CF` = 6 × 4-byte OAM records; `$E1D0-$E1DF` = 4 × 4-byte OAM records | `lda $E1AC,y` at `$DEE4` copies 12 bytes to OAM `$0230` (sprites 0-2); `lda $E1B8,y` at `$E027` copies 24 bytes to `$0230`+`$0C` (sprites 3-8); `lda $E1D0,y` at `$E077` copies 16 bytes to `$0230`+`$24` (sprites 9-12) | | `$E24B-$E392` | 328 | `EnemyOamTemplateAndScriptData` | `$E24B-$E25E` = 5 × 4-byte OAM records (`68 19 03 68`, `68 0A 03 70`, `68 1E 03 78`, `68 1C 03 80`, `68 0E 03 88`); `$E25F-$E266` = 2 × 4-byte OAM records; `$E267-$E26E` = 2 × 4-byte OAM records; `$E26F-$E2AA` = 30 × 16-bit little-endian **code pointers** into `$E2E5-$EAA7` (the per-enemy-type update routines); `$E2AB-$E392` = 3-byte movement records ` ` (opcodes descend 7, 6, 5, 4, 3, 2 within a group, each group terminated by `00 00 00`) | `lda $E24B,y` at `$DCCD` (the 5-record group); `lda $E25F,y` at `$D8BA` (2-record group); `lda $E267,y` at `$D8DD` (2-record group); `lda $E26F,y` at `$D978`, `$D97E`, `$DC3C`, `$DC4C` (the handler-pointer table) | | `$E3A1-$EC0B` | 2155 | `EnemyBehaviourScriptData` | Per-enemy-type behaviour data. `$E3A1-$EB18` = per-type handler code and attribute data. `$EB19-$EBDC` = a run of 16-bit opcode/operand pairs (`32 02 32 03 32 01 14 03 1E 01 28 06 …`), four bytes (two pairs) per score bucket. `$EBDD-$EBF4` = 16-bit values on a 4-byte stride (`$1E` ×7, `$14`, `$1E` ×3, `$3C`). `$EBF5-$EBF9` = 5 score thresholds (`$10`, `$11`, `$12`, `$17`, `$1C`). `$EBFA` = `$21`; `$EBFB-$EC0B` = `$FF` fill | `lda $EBF5,x` at `$DA05` compares the five thresholds against the score (`cmp PlayerScore` at `$DA08`, loop `cpx #$05` at `$DA0D`); `lda $EB19,y` at `$DA21`, `$DA27`, `$DA2D`, `$DA33` copies the four bytes of the record for the current score bucket (`Y` = score × 4) into `$06CD`-`$0700`; `lda $EBDD,y` at `$DA64` and `$DA6B` reads the 16-bit value for the bucket into `$070A`/`$070B`; `lda $EBDD,y` at `$DB65`/`$DB6C` reads it again for the second object | | `$EE2A-$EE35` | 12 | `EnemySpawnValueTable` | 6 × 16-bit little-endian values: `$C400`, `$1C01`, `$E400`, `$0C01`, `$D400`, `$2C01` | `lda $EE2A,y` at `$EDA2` and `$EDA8` load the two halves of one entry (index = `$0682,x` × 2) into `PlayerScore` (`$B0`) and `$B1`. The two bytes are used separately by the score code that follows, so the table's exact semantics per entry are **not established** | | `$EFF2-$F053` | 98 | `PlaneStateDispatchTable` | 49 × 16-bit little-endian **code addresses** into `$EE8C-$FF09`: `$EE8C`, `$EEB4`, `$EE8C`, `$EED7`, `$EEEA`, `$EF00`, `$EF00`, `$EF13`, `$EF2B`, `$EF49` ×2, `$EF6B` ×7, `$EF6E` ×8, `$EF8E`, `$EFA0`, `$EFAD`, `$EFB5`, `$EFBD`, `$EFC5`, `$EFCD`, `$EFBD`, `$EFDA` ×8 | `lda $EFF2,y` at `$EE7E` and `$EE84` load the two halves of the address into `VramWritePtrLo`/`VramWritePtrHi`, which is then executed with `jmp ($8A)` at `$EE89`. The index is the plane's state byte `$05D3,x` × 2 (`asl a` at `$EE7C`) | | `$F13D-$F159` | 29 | `EnemyAnimationFrameTable` | `$F13D-$F149` = 13 bytes of 16-bit values (`$0140`, `$0030`, `$0040`, `$00C0`, `$0140`, `$0070`, `$40`); `$F14A-$F159` = 16 bytes of small values (`$08 $07 $06 $05 $0B $03 $02 $01 $01 $0F $0E $0D $05 $0B $0A $09`) | `lda $F14A,y` at `$F122`, `Y` = `$050B,x & $0F`; the value is passed to `CheckEnemyFrameCounter`. `$F13D-$F149` is unreferenced | | `$F3EC-$F42B` | 64 | `EnemyFormationSlotTable` | 16 × 4-byte records; the bytes are drawn from `{00, 01, 50, 70, 80, 90, B0}` and give the four per-enemy initial values (X offset, Y offset, and two state bytes) for formation slot `($071F & $0F)` | `lda $F3EC,y` at `$F181`, `$F188`, `$F18F`, `$F196` copy the four bytes into `$056F,x`, `$0588,x`, `$0524,x`, `$053D,x`; the same table is read again at `$F357`, `$F35E`, `$F365`, `$F36C`. The index is `($071F & $0F) × 4` (`asl a` × 2 at `$F17B`/`$F17C`) | | `$F55D-$F56F` | 19 | *unreferenced* | The bytes decode as 19 consecutive instructions, so this is **code the tracer could not reach**, not a table | none | | `$FC9C-$FFFF` | 868 | *unreferenced* (partly) | `$FC9C-$FCD2` = 55 consecutive instructions of code the tracer could not reach; `$FCD3-$FCE2` = 8 × 16-bit values (`$027A`, `$017B`, `$037C`, `$007E`, `$017D`, `$017F`, `$0000`, `$0005`); `$FCDF-$FD2E` = 4-byte records `[PPU address hi, PPU address lo, buffer hi, buffer lo]`; `$FD2F-$FD49` = 13 × 16-bit code pointers (`$FD9A`, `$FE2F`, `$FE50`, `$FE61`, `$FE72`, `$FE83`, `$FE94`, `$FEA5`, `$FEB6`, `$FED7`, `$FEF8`, `$FF09`, `$FF2A`); `$FD4A-$FD9F` = 22 × 16-bit code pointers (`$FE50` … `$FF4B`); `$FDA0-$FFF9` = 44 × 16-bit values; `$FFFA-$FFFF` = the NMI/RESET/IRQ vectors | `lda $FD4A,y` at `$F6D9` and `$F6DF` (index = (`$05D3,x` − `$0A`) × 2); `lda $FCD3,y` at `$F8FC` and `$F903`; `lda $FCDF,y` at `$FA0C`, `$FA12`, `$FA18`, `$FA1E`; `lda $FD2F,y` at `$FB3F`, `$FB45`, `$FC40`, `$FC46` | ### Notes on the CHR as an oracle `chr.bin` holds the game's font and sprites. Tiles `$00-$09` are the digits `0-9` and tiles `$0A-$23` are the letters `A-Z` (tile `$19` = `P`, `$1B` = `R`, `$23` = `Z`); tile `$1A` is an anomalous two-dot glyph. Tiles `$24` and above are graphics, not characters. This is what shows that the runs of small values in `$BB51-$BC85` and the tile-index runs in `$8494-$9FDA` are nametable tile data rather than text. ### Open work * The record format of the bulk stage data in `$8494-$9FDA` is not established: the tracer reaches only its last 133 bytes directly, and the rest is read through computed pointers that a static trace cannot follow. * The exact meaning of each byte of the 8-byte stage records at `$8494` is still unknown. * The bytes at `$ABD0-$AC13`, `$BF0F-$BF57` and the `$B200-$B33A` part of the wave table are unreferenced and their layout is unexplained. * The 30-entry handler-pointer table at `$E26F-$E2AA` points at code that is inside the data block, so the individual enemy-type routines are not yet split out of `$E24B-$E392`. ## Build ``` Byte-identical ROM verified. ```