# Defender 2 (U) — Game Flow from Power-On This document describes the execution flow of the disassembled Defender 2 (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 `defender2.nes`. --- ## 1. ROM and Memory Layout | Property | Value | |----------|-------| | Format | iNES / NES 2.0 | | PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) | | CHR | 1 × 8 KB | | Mirroring | Vertical | | PRG bank start | CPU $C000 | | Source file | `copilot/prg.s` (11,905 lines) | | Output | `defender2_rebuilt.nes` (24,592 bytes) | ### Interrupt vectors (CPU $FFFA–$FFFF) | Vector | Address | Handler | |--------|---------|---------| | NMI | $CAA1 | `NmiHandler` | | RESET | $C983 | `ResetHandler` | | IRQ / BRK | $CA9E | `IrqHandler` (self-loop `jmp $CA9E` — unused) | --- ## 2. Build Status `make` in `copilot/` assembles `header.s`, `prg.s`, and `chr.s` with `ca65`, links with `ld65`, and verifies against `defender2.nes`. **Verification result: BYTE-IDENTICAL** — `defender2_rebuilt.nes` matches `defender2.nes` exactly (headers, CHR, and all PRG bytes). ### Build fix applied The original source referenced `LookupTableEntry_FAF5` at a `bpl` instruction (CPU $FAB4), but the symbol was undefined. The branch target CPU $FAF5 falls in the middle of what the disassembler classified as `lda #$16` (A9 at $FAF4, 16 at $FAF5). The fix: - Replaced the symbolic `bpl LookupTableEntry_FAF5` with `bne *-$1B` at CPU $E811, which produces the exact original bytes `d0 e3` (the disassembler had mislabeled `bne` as `bpl`). - The build now produces a byte-identical ROM. --- ## 3. Reset — CPU $FFFC → $C983 **`ResetHandler`** ($C983) On power-on or reset: 1. `sei` — disable maskable interrupts. 2. `cld` — clear decimal mode. 3. `lda #$00 / sta PPUCTRL` — disable PPU rendering and NMI. 4. `dex / txs` — initialize stack pointer to $FF. 5. `UpdateSideScrollingGameplay_C98C` ($C98C): wait for VBlank (poll `PPUSTATUS` bit 7), then clear a block of PPU memory via `ConfigureHudAndPlayfieldPpu_C99D` ($C99D) using zero-page pointers $00/$01 and counter $01. 6. Set PPUCTRL = $00, `PpuControlShadow` ($10) = $00, PPUMASK = $02, `PpuMaskShadow` ($11) = $02. 7. Set scroll registers: `PPUSCROLL` = $00 twice, `HorizontalScroll` ($12) = $00, `VerticalScroll` ($13) = $00. 8. Configure HUD and playfield PPU regions via repeated calls to `ConfigureHudAndPlayfieldPpu_C092` ($C092) with various source/destination parameters — uploads background tiles, HUD elements, and playfield data. 9. Initialize game RAM variables: `Ram_03FB` = $80, `Ram_06AB` = $90, `Ram_06CB` = $94, `Ram_06EB` = $98, `Ram_05C0` = $03, `Ram_0580` = $04, `Ram_060B` = $68, `Ram_064B` = $70. 10. `Zp_1D` ($1D) = $55; `SND_CHN` ($4015) = $0F (enable all APU channels); `Ram_0100` = $0F; `Zp_36` ($36) = $01. 11. Enable NMI: `PpuControlShadow` ($10) \| $80, store to shadow and PPUCTRL. 12. Falls into `UpdateSideScrollingGameplay_CA6A` ($CA6A) — the permanent main loop. --- ## 4. Main Loop — `UpdateSideScrollingGameplay_CA6A` ($CA6A) Once NMI is enabled, `ResetHandler` never returns. It enters the main loop: ``` UpdateSideScrollingGameplay_CA6A ($CA6A): jsr UpdateSideScrollingGameplay_C40D ; $CA6A: 20 0D C4 lda Zp_A2 ; $CA6D: A5 A2 beq UpdateSideScrollingGameplay_CA6A ; $CA6F: F0 F9 (spin while Zp_A2 == 0) dec Zp_A2 ; $CA71: C6 A2 jsr UpdateSideScrollingGameplay_E47A ; $CA73: 20 7A E4 jsr UpdateSideScrollingGameplay_DC7B ; $CA76: 20 7B DC jsr UpdateSideScrollingGameplay_E6E1 ; $CA79: 20 E1 E6 jsr UpdateSideScrollingGameplay_D91A ; $CA7C: 20 1A D9 jsr UpdateSideScrollingGameplay_E6D5 ; $CA7F: 20 D5 E6 jsr UpdateSideScrollingGameplay_E7AA ; $CA82: 20 AA E7 jsr UpdateSideScrollingGameplay_E029 ; $CA85: 20 29 E0 jsr UpdateSideScrollingGameplay_DE78 ; $CA88: 20 78 DE jmp UpdateSideScrollingGameplay_CA6A ; $CA8B: 4C 6A CA (loop forever) ``` The main loop spins waiting for `Zp_A2` ($A2) to become nonzero (set by the NMI), then decrements it and calls a chain of per-frame update routines covering flying entities, object updates, collisions, scrolling, and rendering. It then loops back to itself. --- ## 5. NMI / Frame Heartbeat — `NmiHandler` ($CAA1) The NMI fires once per frame and is the central real-time driver: 1. **Save registers** (A, X, Y on stack). 2. `ldx #$00` — X = 0 for buffer copy loop. 3. `ConfigureHudAndPlayfieldPpu_CAA8` ($CAA8): copy 16 bytes from zero-page pointer $00,X into PPU registers (PPU command buffer upload), toggling `PpuControlShadow` bit 7. 4. `jsr ConfigureHudAndPlayfieldPpu_D2CF` ($CAB9) — additional PPU configuration. 5. `jsr TransferShipTargetOamBuffer` ($CABC) — OAM DMA: write $00 to OAMADDR ($2003), then $02 to OAMDMA ($4014). 6. `jsr UpdateSideScrollingGameplay_C073` ($CABF) — configure HUD/playfield PPU and reset scroll. 7. Restore PPU mask from `PpuMaskShadow` ($11). 8. `jsr PollShipControllers` ($CAC7) — read controller ports $4016/$4017 into `PreviousControllerButtons` ($19) and `CurrentControllerButtons` ($1B). 9. `jsr TransformFlightState_CEE8` ($CACA) — process ship flight state. 10. If `Zp_3B` ($3B) != 0 → `UpdateSideScrollingGameplay_CAD4`; else → `UpdateSideScrollingGameplay_C3CC` ($CAD1). 11. `UpdateSideScrollingGameplay_CAD4` ($CAD4): `jsr ReadShipControls_F880`, `jsr UpdateSideScrollingGameplay_CBD4`, then `jsr UpdateSideScrollingGameplay_D6D8` ($CADA). 12. `ConfigureHudAndPlayfieldPpu_CADF` ($CADF): pop saved registers (Y, X, A) from the stack — restores the context saved at the start of NMI. 13. Re-enable NMI: `PpuControlShadow` \| $80, store to shadow and PPUCTRL. 14. `pla / rti` — restore A and return from interrupt. The NMI is the frame heartbeat: it handles PPU updates, OAM DMA, controller polling, and gameplay state updates, all during VBlank. --- ## 6. PPU / Rendering ### `ConfigureHudAndPlayfieldPpu_C092` ($C092) - Reads PPUSTATUS to reset address latch. - Loads `PpuControlShadow` ($10), ANDs with $FB (disable sprite rendering bit), stores to PPUCTRL and shadow. - Sets up PPU address and writes tile runs to $2007 — used for HUD and playfield background updates. ### `TransferShipTargetOamBuffer` ($C087) - Write $00 to OAMADDR ($2003), then $02 to OAMDMA ($4014) — DMA sprite data from $0200–$02FF. ### `PollShipControllers` ($C434) - Reads both controller ports, tracking previous and current button states. ### `UpdateSideScrollingGameplay_C073` ($C073) - Configures HUD/playfield PPU upload and resets scroll. Used during NMI and gameplay state transitions. --- ## 7. Zero-Page / RAM Symbols | Address | Symbol | Use | |---------|--------|-----| | $00–$03 | ScratchValue0–3 | General scratch values | | $04–$05 | DataPointerLo/Hi | Data pointer | | $06–$07 | TemporaryPointerLo/Hi | Temporary pointer | | $08–$0D | Zp_08–Zp_0D | Zero-page scratch | | $0F | Zp_0F | Zero-page scratch | | $10 | PpuControlShadow | $2000 mirror | | $11 | PpuMaskShadow | $2001 mirror | | $12 | HorizontalScroll | Horizontal scroll value | | $13 | VerticalScroll | Vertical scroll value | | $14–$18 | Zp_14–Zp_18 | Zero-page scratch | | $19 | PreviousControllerButtons | Previous frame controller state | | $1A | Zp_1A | Zero-page scratch | | $1B | CurrentControllerButtons | Current frame controller state | | $1C–$1F | Zp_1C–Zp_1F | Zero-page scratch | | $20–$3F | Zp_20–Zp_3F | Zero-page scratch (extensive use) | | $36 | Zp_36 | Game flag | | $3B | Zp_3B | Gameplay branch flag | | $3F | Zp_3F | Zero-page scratch | | $40–$47 | Zp_40–Zp_47 | Zero-page scratch | | $4C | Zp_4C | Zero-page scratch | | $54–$5F | Zp_54–Zp_5F | Zero-page scratch | | $60–$61 | Zp_60–Zp_61 | Zero-page scratch | | $A0–$A7 | Zp_A0–Zp_A7 | Zero-page scratch ($A2 used as NMI frame sync) | | $A9–$AB | Zp_A9–Zp_AB | Zero-page scratch | | $AC–$AF | Zp_AC–Zp_AF | Zero-page scratch | | $B0–$B9 | Zp_B0–Zp_B9 | Zero-page scratch | | $BA–$BF | Zp_BA–Zp_BF | Zero-page scratch | | $C0–$C4 | Zp_C0–Zp_C4 | Zero-page scratch | | $D0–$D5 | Zp_D0–Zp_D5 | Zero-page scratch | | $D7–$DF | Zp_D7–Zp_DF | Zero-page scratch | | $E0–$EF | Zp_E0–Zp_EF | Zero-page scratch | | $F0–$F7 | Zp_F0–Zp_F7 | Zero-page scratch | | $FA–$FB | Zp_FA–Zp_FB | Zero-page scratch | | $FD–$FF | Zp_FD–Zp_FF | Zero-page scratch | | $0100 | Ram_0100 | APU/channel state | | $0200 | OamBuffer | Sprite DMA source | | $0201–$0203 | Ram_0201–Ram_0203 | RAM scratch | | $0214, $0244, $0299, $029C–$029D, $029F–$02FE | Ram_* | Game state RAM | | $0330–$0331 | Ram_0330–Ram_0331 | Gameplay state | | $0380–$03FF | Ram_0380–Ram_07FF | Extensive game state RAM (objects, entities, scores, etc.) | | $0540–$05FF | Ram_0540–Ram_05FF | Gameplay variables | | $0600–$06FF | Ram_0600–Ram_06FF | Entity/object arrays | | $0782–$07F2 | Ram_0782–Ram_07F2 | Late-game state | --- ## 8. Routine Naming Patterns The disassembly uses consistent name prefixes that indicate routine purpose: | Prefix | Example | Meaning | |--------|---------|---------| | `UpdateSideScrollingGameplay_*` | `UpdateSideScrollingGameplay_C073` | Side-scrolling gameplay logic | | `UpdateGameplayStateField_*` | `UpdateGameplayStateField_C1F7` | Gameplay state field updates | | `TransformFlightState_*` | `TransformFlightState_C109` | Ship flight/transform state | | `ConfigureHudAndPlayfieldPpu_*` | `ConfigureHudAndPlayfieldPpu_C092` | PPU HUD/playfield configuration | | `CalculateMovementAndClamp_*` | `CalculateMovementAndClamp_C183` | Movement calculation with clamping | | `UpdateFlyingEntity_*` | `UpdateFlyingEntity_CF11` | Flying entity (ship/enemy) updates | | `ReadShipControls_*` | `ReadShipControls_F880` | Ship controller input | | `ReadObjectRecord_*` | `ReadObjectRecord_DADA` | Object record reading | | `ProcessObjectStateRoutine_*` | `ProcessObjectStateRoutine_CBE3` | Object state machine | | `UpdateObjectBuffer_*` | `UpdateObjectBuffer_DB52` | OAM buffer updates | | `UpdateObjectRecord_*` | `UpdateObjectRecord_D27D` | Object record maintenance | The data side of the disassembly is named separately: every data block in the PRG has been interpreted and recorded in the **Pass 3 data-table inventory** below, with the instruction in this dump that reads it. The blocks that decode as 6502 code are recorded there as *code the tracer could not reach*. --- --- ## Pass 3 data-table inventory Every `.byte`/`.word` region in `prg.s` is listed here with what its bytes are and the instruction in this dump that reads it. A region no instruction operand lands in is recorded as **unreferenced data** with its ROM provenance; a region whose bytes decode as a run of 6502 instructions is recorded as **code the tracer could not reach**, because that is what it is - the Pass-1 trace never entered it, so the tracer emitted it as data. The Pass-1 tracer's `; ---- data $XXXX-$YYYY ----` markers are gone from this source, so the blocks below were recovered from the emitted `.byte` rows and their CPU addresses; the consumers were found by resolving every operand - literal **and** label - to an address. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000-$C009` | 10 B | `ScreenStateHandlerPointerTable` | five little-endian 16-bit pointers into code: `$C261, $C25D, $C355, $C177, $C092`. `$C092` is `ConfigureHudAndPlayfieldPpu` and the other four are the game's remaining state entry points. No instruction in this dump reads the table, so the dispatch that uses it is not in the traced code. | - | | `$C00A-$C072` | 105 B | `PlayfieldColumnTerrainTable` | the terrain generator's table of 4-byte records. The routine at `$C183` computes a record index of `((value >> 2) + 1) & $0F`, rejects an index of 9 or more, and then reads **four consecutive bytes** of the record: byte 0 and byte 1 are written to the column-height array at `$0331`, byte 2 is masked with `$07` and used as the column type, and byte 3 is kept as the column's third parameter. The records actually used are the eight at `$C00E-$C02D`; the bytes at `$C00A-$C00D` and `$C02E-$C072` are not reached by that index. | `lda PlayfieldColumnTerrainTable,y` at `$C193`, `$C19D`, `$C1A7` and `$C1C2` (Y = record index x 4) | | `$C161-$C168` | 8 B | (unreached code) | eight bytes that decode as 6502 code (`A9 85 85 00 ...`): a routine the Pass-1 trace never reached. | - | | `$C25D-$C260` | 4 B | (unreached code) | four bytes that decode as 6502 code (`A2 FF D0 02` = `LDX #$FF` / `BNE`). | - | | `$C647-$C68E` | 72 B | `DefugeCreditString` | 72 bytes of ASCII: `" DEFUGED BY TOSHIO SENGOKU ( Intelligent Systems ) 1987/07/31 15:00 "` followed by `$0A` and `$EA`. It is the developer's build stamp that the game prints in its "defuge" (debug) mode. | - | | `$C68F` | 1 B | `SpriteDefinitionBaseAddress` | one byte `$C7`, the high half of the code address `$C7EA` that the sprite-definition routine reads two bytes at a time. | `lda SpriteDefinitionBaseAddress` at `$E123` -> `sta ScratchValue2` at `$E126` | | `$C690-$C69F` | 16 B | `SpriteDefinitionPointerTable` | 16 bytes, the first of which (`$EA`) is the low half of the same `$C7EA` pointer; the bytes after it are 16-bit little-endian addresses of the game's sprite definitions (`$EE02`, `$EE57`, `$EE66`, `$EF14`, ...). | `lda SpriteDefinitionPointerTable` at `$E11E` -> `sta ScratchIndex` at `$E121`; the 16-bit entries are then read with `lda ($02),y` at `$E128` and `$E12D` | | `$C6A0-$C6BE` | 31 B | `SpriteDefinitionEntryTable` | 31 bytes of sprite-definition data in the same 16-bit-address shape (`$AC,$B4` = `$B4AC`, `$B5,$AD` = `$ADB5`, ...). | - | | `$C6BF` | 1 B | `LanderFlightSpeedTable` | one byte `$89`, the first of the lander-flight parameters. | `lda LanderFlightSpeedTable,y` at `$E910` -> `sta Ram_029D` at `$E913` | | `$C6C0-$C6CF` | 16 B | `LanderFlightPositionTable` | 16 bytes, the second of the lander-flight parameter tables (`$AE,$8D` = `$8DAE`, `$FE,$8E` = `$8EFE`, ...). | `lda LanderFlightPositionTable,y` at `$E916` -> `sta Ram_02A1` at `$E919` | | `$C6D0-$C6D3` | 4 B | (tail of `LanderFlightPositionTable`) | four more bytes, `$24,$24,$24,$24`. | - | | `$C6D4-$C6DB` | 8 B | `BitIndexToMaskTable` | eight bytes `$01,$02,$04,$08,$10,$20,$40,$80` - the mask of bit *n*. | `lda BitIndexToMaskTable,x` at `$CE7A` -> `sta ScratchValue0,y` at `$CE7D` | | `$C6DC-$C6EB` | 16 B | `TerrainColumnParameterTable` | 16 bytes of per-column terrain parameters (`$01,$02,$03,$04,$10,$15,$00,$01,$10,$C0,$03,$00,$6C,$00,$24,$00`). | `lda TerrainColumnParameterTable,y` at `$CEAB` | | `$C6EC-$C7D7` | 236 B | (terrain data) | 236 bytes of column data in the same 4-byte shape as the terrain records, including runs of `$00,$FF,$98` and `$00,$FF,$A8`. | - | | `$C7D8-$C7E7` | 16 B | `TerrainColumnHeightTable` | 16 bytes, the height the terrain generator writes into `$0560` for each column. | `lda TerrainColumnHeightTable,y` at `$D029` -> `sta Ram_0560,x` at `$D02C` | | `$C7E8-$C89E` | 183 B | (sprite definition data) | 183 bytes of sprite definitions, including the 16-bit address list that the routine at `$E11E` walks with `lda ($02),y`. | `lda ($02),y` at `$E128` and `$E12D` (the pointer is `$C7EA`) | | `$C89F-$C8AE` | 16 B | `SpriteTargetYCoordinateTable` | 16 bytes, the low byte of a 16-bit coordinate: the code copies the byte at `$C89F+X` to offset `$13` of the object record and the next byte to offset `$14`, so the pair is one 16-bit little-endian value. | `lda SpriteTargetYCoordinateTable,x` at `$E283` and `$E28B` | | `$C8AF-$C8B4` | 6 B | (tail of `SpriteTargetYCoordinateTable`) | six more bytes, the high halves of the same 16-bit values. | - | | `$C8B5-$C8C4` | 16 B | `SpriteLanderXCoordinateTable` | 16 bytes, the low byte of a 16-bit coordinate written to object-record offsets `$11`/`$12`. | `lda SpriteLanderXCoordinateTable,x` at `$E2D5` and `$E2DD` | | `$C8C5-$C8CA` | 6 B | (tail of `SpriteLanderXCoordinateTable`) | six more bytes, the high halves of the same values. | - | | `$C8CB-$C8DA` | 16 B | `SpriteTargetYCoordinateTable2` | 16 bytes, the low byte of a second 16-bit coordinate, written to object-record offsets `$13`/`$14`. | `lda SpriteTargetYCoordinateTable2,x` at `$E1C4` and `$E1CB` | | `$C8DB-$C8EA` | 16 B | `SpriteLanderXCoordinateTable2` | 16 bytes, the low byte of a second 16-bit coordinate, written to object-record offsets `$11`/`$12`. | `lda SpriteLanderXCoordinateTable2,x` at `$E183` and `$E18A` | | `$C8EB-$C8EE` | 4 B | `TerrainColumnThresholdTable` | four bytes, compared against the object field `$1D` to decide whether the column has been passed. | `lda TerrainColumnThresholdTable,y` at `$DD65` -> `cmp Zp_1D,x` at `$DD68` | | `$C8EF-$C8F2` | 4 B | `TerrainColumnTypeTable` | four bytes, the column type; a zero entry ends the column's list. | `lda TerrainColumnTypeTable,y` at `$DCDE` -> `beq` at `$DCE1` | | `$C8F3-$C8F6` | 4 B | `TerrainColumnSpeedTable` | four bytes, the column's scroll speed. | `lda TerrainColumnSpeedTable,y` at `$D0B3` -> `sta Zp_C0` at `$D0B6` | | `$C8F7-$C8FA` | 4 B | `TerrainColumnLimitTable` | four bytes, the column limit that the current scroll position is compared against. | `cmp TerrainColumnLimitTable,y` at `$D4EF` | | `$C8FB-$C8FE` | 4 B | `TerrainColumnFlagTable` | four bytes, a second column limit/flag compared against the scroll position. | `cmp TerrainColumnFlagTable,y` at `$D518` | | `$C8FF-$C902` | 4 B | `TerrainColumnLimitTable2` | four bytes, a third column limit. | `cmp TerrainColumnLimitTable2,y` at `$D502` | | `$C903-$C912` | 16 B | `ControllerButtonMaskTable` | 16 bytes, one per screen state: the default value of the controller-button latch, masked with `$CF` and then OR-ed with the buttons the player is holding. | `lda ControllerButtonMaskTable,y` at `$CEF5` -> `and #$CF` at `$CEF8` | | `$C913-$C982` | 112 B | (button-mask data) | 112 more bytes of the same 4-byte group shape as the button-mask table. | - | | `$CE47-$CE50` | 10 B | (unreached code) | ten bytes that decode as 6502 code (`08 48 84 C2 86 C3 98 4C 84 ...`): a routine the Pass-1 trace never reached. | - | | `$D6A9-$D6B9` | 17 B | (unreached code) | 17 bytes that decode as eight consecutive 6502 instructions. | - | | `$DA30-$DA3F` | 16 B | `SpriteTileIndexTable` | 16 bytes, the CHR tile index of each sprite: `$01,$01,$01,$05,$03,$03,$03,$05,$07,$0B,$09,$09,$09,$0B,$07,$07`. | `lda SpriteTileIndexTable,y` at `$DA09` -> `tay` at `$DA0C` | | `$EA0A-$F7FF` | 3574 B | (large data region) | the largest data block in the PRG. It holds at least three different tables: a 4-byte sprite-layout table from `$EACA` (`$3C,$23,$48,$01` = X, Y, tile, flag), an ASCII character table at `$EAAA` (`$41,$20,$55,$84,$43,$44,$44,$45` = `A U C D D E`), a column-position table at `$EB0A` (`$30,$40,$50,$60,$70,$80,$90,$A0` - a doubling sequence of X positions) and a 3-byte record stream from `$EAEA` (`$14,$0F,$30`, `$01,$21,$0F`, ...). No instruction in this dump addresses the block with an absolute operand, so the index that reaches these sub-tables is computed at run time; the record layout of the region as a whole is not established. | - | | `$F800` | 1 B | `ApuPulse1PeriodHighTable` | one byte, the high half of the first pulse channel's period. | `lda ApuPulse1PeriodHighTable,y` at `$F8C8` -> `ora #$08` at `$F8CB` -> `sta APUPULSE1TIMERHI,x` at `$F8CD` | | `$F801-$F810` | 16 B | `ApuPulse1PeriodLowTable` | 16 bytes, the low halves of the first pulse channel's periods (`$F0,$00,$69,$00,$5E,$00,$3E,$00,$00,$D4,$00,$BD,$00,$B2,$00,$A8`). | `lda ApuPulse1PeriodLowTable,y` at `$F8C0` -> `sta APUPULSE1TIMERLO,x` at `$F8C5` | | `$F811-$F84B` | 59 B | (audio data) | 59 more bytes of the pulse-channel period data. | - | | `$F84C-$F85B` | 16 B | `AudioVolumeTable` | 16 bytes, the volume the engine writes for a given note. | `lda AudioVolumeTable,y` at `$F906` -> `rts` at `$F909` | | `$F85C-$F87F` | 36 B | (audio data) | 36 more bytes of the volume and envelope data. | - | | `$F8A5-$F8A6` | 2 B | (unreferenced) | Unreferenced data (ROM provenance PRG `$38A5-$38A6`): `$A0,$7F`. | - | | `$F8D1-$F8D3` | 3 B | (unreached code) | three bytes that decode as a 6502 instruction (`20 A7 F8` = `JSR $F8A7`). | - | | `$F90A-$F918` | 15 B | (audio data) | 15 bytes of the volume ramp: `$18,$1A,$1B,$1A,$19,$19,$18,$18,$17,$17,$16,$16,$15,$15,$14`. | - | | `$F919-$F924` | 12 B | `AudioPitchTable` | 12 bytes, the pitch bytes of the first sound set: `$16,$84,$8E,$8E,$9E,$9F,$9F,$AF,$AF,$AF,$AF,$AF`. | `lda AudioPitchTable,y` at `$F996` | | `$F925-$F931` | 13 B | `AudioEnvelopeTable` | 13 bytes, the envelope bytes of the first sound set (`$84,$8E,$8E,$9E,$9F,$9F,$9F,$AF,$9F,$AF,$9F,$AF,$0A`). | `lda AudioEnvelopeTable,y` at `$F99C` | | `$F932` | 1 B | `ApuPulse1PeriodHighTable2` | one byte, the high half of the first pulse channel's period for the second sound set. | `lda ApuPulse1PeriodHighTable2,y` at `$F975` -> `sta APUPULSE1TIMERHI` at `$F978` | | `$F933-$F941` | 15 B | `ApuPulse1PeriodLowTable2` | 15 bytes, the low halves of the same periods. | `lda ApuPulse1PeriodLowTable2,y` at `$F96F` -> `sta APUPULSE1TIMERLO` at `$F972` | | `$F942` | 1 B | `ApuPulse2PeriodHighTable` | one byte, the high half of the second pulse channel's period. | `lda ApuPulse2PeriodHighTable,y` at `$F986` | | `$F943-$F951` | 15 B | `ApuPulse2PeriodLowTable` | 15 bytes, the low halves of the second pulse channel's periods. | `lda ApuPulse2PeriodLowTable,y` at `$F980` -> `sta APUPULSE2TIMERLO` at `$F983` | | `$FB8E-$FB92` | 5 B | `AudioSequenceEntryTable` | 5 bytes, the sound-effect sequence descriptors: `$0F,$05,$05,$05,$00`. The zero byte ends the list. | `lda AudioSequenceEntryTable,y` at `$FAE8` -> `beq` at `$FAEB` | | `$FD32-$FD36` | 5 B | `ApuNoisePeriodIndexTable` | five bytes `$09,$0A,$0B,$0C,$0D` - the APU noise-period **indices** the sound effects use. The table really starts at `$FD32`; the read at `$FCE7` is `lda $FD31,y`, one byte below it, so entry 0 of the read is the high byte of the preceding `JMP $FC89` and the five real entries are reached with Y = 1 to 5. | `lda ApuNoisePeriodIndexTable,y` at `$FCE7` -> `sta APUNOISEPERIOD` at `$FCEA` | | `$FE59` | 1 B | `AudioNoteTable` | one byte, the first of the note table. | `lda AudioNoteTable,y` at `$FD53` and `$FD57` -> `sta Ram_078D` at `$FD5A` | | `$FE5A` | 1 B | `AudioPeriodBaseTable` | one byte, the period base of the note. | `lda AudioPeriodBaseTable,y` at `$FD5D` -> `sta Zp_F6` at `$FD60` | | `$FE5B` | 1 B | `AudioSequencePointerLoTable` | one byte, the low half of the note's sequence pointer. | `lda AudioSequencePointerLoTable,y` at `$FD62` -> `sta Zp_F7` at `$FD65` | | `$FE5C` | 1 B | `AudioSequencePointerHiTable` | one byte, the high half of the note's sequence pointer. | `lda AudioSequencePointerHiTable,y` at `$FD67` | | `$FE5D-$FE6C` | 16 B | `AudioWaveformTable` | 16 bytes, the waveform/duty bytes of the note table. | `lda AudioWaveformTable,y` at `$FD6C` | | `$FE6D-$FFFF` | 403 B | (audio data + vectors) | the remaining sound-effect and note data, followed by the three vectors at `$FFFA-$FFFF`. | the CPU's own vector fetch | ### Notes on the inventory * The `DefugeCreditString` at `$C647` is the reason the game carries a printable build stamp: Defender 2 has a "defuge" (debug) mode that puts ` DEFUGED BY TOSHIO SENGOKU ( Intelligent Systems ) 1987/07/31 15:00 ` on screen. It is ASCII, not tile indices. * The `PlayfieldColumnTerrainTable` at `$C00A` is read in **four-byte records**: the terrain generator at `$C183` computes an index of `((value >> 2) + 1) & $0F` and reads four consecutive bytes, so the record size is four, not one. * `$C68F`/`$C690` are two bytes that together form the code address `$C7EA`; the routine at `$E11E` loads them into a zero-page pointer and then reads the sprite definitions two bytes at a time through it. * The 3,574-byte block at `$EA0A` is the biggest data region in the PRG and holds several different tables; it is listed as one block here because no instruction in this dump names any of its sub-tables. --- ## 9. Summary of Execution Flow ``` Power On / Reset │ ▼ ResetHandler ($C983) ───────────────────────────────┐ │ sei, cld, disable PPU, stack=$FF │ │ wait VBlank, clear PPU memory │ │ set PPUCTRL=$00, PPUMASK=$02, scroll=$00 │ │ upload HUD/playfield tiles via C092 │ │ init game RAM, APU channels ($4015=$0F) │ │ enable NMI (PpuControlShadow \| $80) │ ▼ │ jmp UpdateSideScrollingGameplay_CA6A ($CA6A) │ │ │ │ Main loop — waits for NMI frame sync: │ │ wait until Zp_A2 ($A2) != 0 │ │ dec Zp_A2 │ │ jsr E47A, DC7B, E6E1, D91A, E6D5, E7AA, │ │ E029, DE78 │ │ jmp back to CA6A │ │ │ └────────────────────────────────────────────────┘ │ NMI ($CAA1) [once per frame] │ │ │ ├─ save A, X, Y │ ├─ PPU command buffer upload ($CAA8) │ ├─ ConfigureHudAndPlayfieldPpu_D2CF ($CAB9) │ ├─ OAM DMA ($4014 from $0200) │ ├─ ConfigureHudAndPlayfieldPpu_C073 ($C073) │ ├─ restore PPU mask from shadow │ ├─ PollShipControllers ($CAC7) │ ├─ TransformFlightState_CEE8 ($CEE8) │ ├─ branch on Zp_3B: CAD4 or C3CC │ ├─ CAD4: ReadShipControls_F880, CBD4, D6D8 │ ├─ restore registers, re-enable NMI │ └─ rti │ IRQ ($CA9E) — self-loop (unused) ``` --- ## 10. Notes - **Build verification:** `make` in `copilot/` produces `defender2_rebuilt.nes` that is **byte-identical** to `defender2.nes` and to the PRG bank of `Defender 2 (U).nes`. - Vectors confirm NMI at $CAA1, RESET at $C983, and IRQ at $CA9E (self-loop, unused). - The main loop (`CA6A`) is a tight NMI-synchronized loop; all substantive per-frame work happens either in the NMI handler or in the routines called from the main loop after the NMI sets `Zp_A2`. - Many routines carry address-based names (`UpdateSideScrollingGameplay_C073`, `TransformFlightState_C109`, etc.) reflecting the automated first-pass disassembly; semantic naming is provisional. - The disassembly includes inline `.byte` data blocks interleaved with code; boundaries are marked as classified by Pass 1. - `locals.inc` provides zero-page names; `symbols.inc` provides the NES hardware register set. - One build fix was required: a `bpl` branch to an undefined symbol `LookupTableEntry_FAF5` was replaced with `bne *-$1B` to produce the original `d0 e3` bytes (the disassembler had mislabeled `bne` as `bpl`). ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry.