# Dig Dug II (U) [!] ## Status **Pass 1.** The project builds and verifies byte-identical to the original ROM. `prg.s` is a traced disassembly classifying bytes as reachable code or data, with every instruction carrying a trailing audit comment. Unreached bytes remain as explicit `.byte` blocks; no ROM byte is deleted or reformatted. The data blocks have since been named and interpreted in Pass 3 - see the inventory below. Every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name as well: the actor system is named for what it moves (`MoveActorRight`, `MoveActorDiagonally`, `UpdateActorBehaviour`, `DispatchEnemyBehaviour`, `SelectActorBehaviour`, `WriteActorOamEntry`, ...), the round and wave logic for what it schedules (`StartEnemyWave`, `AdvanceEnemySchedule`, `AdvanceStageSchedule`, `UpdateRoundPhase`, ...), the player for what it does (`HandlePlayerInput`, `CheckPlayerCanAct`, `HandlePlayerDeath`, `StartPlayerRespawn`, ...) and the sound engine for what it plays (`InitializeSoundEngine`, `UpdateSoundChannel`, `WriteApuStatusRegisters`). No entry point is left with an address-derived name. `make` assembles `header.s`, `prg.s`, `chr.s`, links them with `ld65` and compares the result against `rom.nes`: ``` Byte-identical ROM verified. ``` ## ROM | Property | Value | |----------|-------| | Format | iNES | | Mapper | 0 | | Board | NROM-256 | | Mirroring | Vertical | | Four-screen VRAM | no | | Battery | no | | PRG | 32 KB (2 x 16 KB), mapped at CPU $8000-$FFFF | | CHR | 8 KB (1 x 8 KB) | | Trainer | none | | Footer | none | | NMI vector | $A749 | | RESET vector | $A52D | | IRQ vector | $A52D | | ROM file | `Dig Dug II (U) [!].nes` | | SHA-256 | `1bb89959ba8366a6610c820f7d42a8f59374b0d719741f0f95ce0e92700e71d7` | ### Header notes - none ## 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 `$A52D` (`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 | |---------|---------| | `$A534` | `SubCA534` (falls through) | | `$A539` | `SubCA539` (falls through) | | `$E725` | `ClearOamBuffer` | | `$F7C9` | `InitializeSoundEngine` | | `$A54E` | `SubCA54E` (falls through) | | `$E736` | `SubCE736` (falls through) | | `$F7D9` | `SubCF7D9` (falls through) | | `$A55C` | `SubCA55C` (falls through) | **NMI** is at `$A749` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| **IRQ** is at `$A52D` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path. ## Pass 3 data-table inventory Every `; ---- data $XXXX-$YYYY` block in `prg.s` is listed below, split into the sub-structures it holds where the block mixes several. The PRG is a 32 KB NROM image mapped at `$8000-$FFFF`; the game has **72 rounds** (`$0259` counts 0-71 and wraps at `$48` at `$C8EA`), so most of the large tables are indexed by the round number. `Read by` gives the consuming instruction(s) with their addresses. Where a block is listed with `-` in the `Name` column, no instruction in this dump addresses it and it has deliberately been left unnamed in `prg.s`. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$8000-$808F` | 144 | `RoundStageMapPointerTable` | 72 x 16-bit LE pointers into the round stage-map area, indexed by the round counter `$0259` | `lda $8000,y` at `$AE59`, `lda $8001,y` at `$AE5E` | | `$8090-$9E5F` | 7632 | `RoundStageMapData` | 72 fixed 106-byte round slots; each slot holds 91 background tile indices (`$81` = empty cell) followed by 2-bit-per-pixel soil-edge data | read through the pointer above: `lda (Zp19),y` at `$AE7D` and `$AE9F` | | `$9E60-$9EFF` | 144 | `RoundEnemyConfigPointerTable` | 72 x 16-bit LE pointers, indexed by the round counter `$0259` | `lda $9E60,y` at `$D82F`, `lda $9E61,y` at `$D834` | | `$9EF0-$A4D0` | 1505 | `RoundEnemyConfigData` | 72 variable-length per-round enemy-roster records: a count byte, a second count byte, then two arrays of tile/attribute bytes copied into `$40` and `$79` | read through the pointer above: `lda (Zp1B),y` at `$D83B`-`$D893` | | `$A4D1-$A4D7` | 7 | - | 7 bytes of non-text data between the last roster record and the copyright string | not referenced by any instruction | | `$A4D8-$A508` | 49 | `CopyrightText` | ASCII, `COPYRIGHT 1982 1986 NAMCO LTD.ALL RIGHTS RESERVED` | not referenced by any instruction in this dump - the CDL never touches these bytes, so they belong to the title/attract screen, which this build does not reach | | `$A509-$A528` | 32 | `InitialPaletteImage` | 32 NES palette indices (`$0F` = black), the default background palette image copied to `$0200-$021F` at reset | `lda $A509,x` at `$A594` | | `$A529-$A52C` | 4 | `ColdBootSignature` | the 4 ASCII bytes `K.N.`, compared with zero page `$00-$03` to tell a cold boot from a warm one | `lda $A529,x` at `$A54E` and `$A55E` | | `$A911-$A91A` | 10 | `RoundTimerThresholdTable` | 5 x 16-bit LE threshold values, compared against the 16-bit timer held in `$06`/`$07` | `cmp $A911,y` at `$A9AB`, `cmp $A916,y` at `$A9B6` | | `$AA30-$AA3F` | 16 | `BackgroundPaletteSet1` | 4 x 4 NES background palette colours (4 palettes of 4) | `lda $AA30,x` at `$ACDC` | | `$AA40-$AA4F` | 16 | `BackgroundPaletteSet2` | 4 x 4 NES background palette colours | `lda $AA40,x` at `$ACE1` | | `$AA50-$AA5F` | 16 | `BackgroundPaletteSet3` | 4 x 4 NES background palette colours | `lda $AA50,x` at `$ACE6` | | `$AA60-$AA67` | 8 | `StageUploadPointerTable` | 4 x 16-bit LE pointers to the compressed VRAM upload streams, selected by `$025B` | `lda $AA60,y` at `$ACFE`, `lda $AA61,y` at `$AD03` | | `$AA68-$ACAD` | 582 | `StageUploadData` | 4 run-length-compressed VRAM upload streams; each sub-block is a 16-bit PPU address (low byte then high byte, masked to 14 bits) followed by an RLE stream where `$FF` escapes a literal and `$FF $FF` ends the block | read through the pointer above: `lda (Zp1B),y` at `$AD10`-`$AD47` | | `$AE47-$AE4A` | 4 | `SubCellBitMaskTable` | 4 x 8-bit masks selecting the four 2-bit fields of a stage-map byte | `and $AE47,y` at `$AEA5` | | `$AF14-$AF1B` | 8 | `RoundPaletteOverrideA` | 8 NES palette colours indexed by (round >> 2); non-zero entries replace the default palette entry | `lda $AF14,y` at `$AF8E` | | `$AF1C-$AF23` | 8 | `RoundPaletteOverrideB` | 8 NES palette colours indexed by (round >> 2) | `lda $AF1C,y` at `$AF93` | | `$AF24-$AF2B` | 8 | `RoundBackgroundColourOverride` | 8 NES palette colours indexed by (round >> 2), used where the default entry is `$0F` | `lda $AF24,y` at `$AF9C` | | `$AF2C-$AF3B` | 16 | `RoundSpriteColourTable1` | 16 x 8-bit NES palette colours indexed by (round >> 2), written to `$3F1D` | `lda $AF2C,y` at `$AFB5` | | `$AF3C-$AF4B` | 16 | `RoundSpriteColourTable2` | 16 x 8-bit NES palette colours indexed by (round >> 2), written to `$3F1E` | `lda $AF3C,y` at `$AFBB` | | `$AF4C-$AF5B` | 16 | `RoundSpriteColourTable3` | 16 x 8-bit NES palette colours indexed by (round >> 2), written to `$3F1F` | `lda $AF4C,y` at `$AFC1` | | `$B060-$B07F` | 32 | `LowTileGraphicPointerTable` | 16 x 16-bit LE pointers to the 16-byte tile bitmaps, indexed by the low nibble of the background tile index | `lda $B060,y` at `$B8CE`, `lda $B061,y` at `$B8D3` | | `$B080-$B15F` | 224 | - | 112 x 16-bit LE pointers into `$B360-$B5BF` (38 distinct tile bitmaps); the same shape as the two tables that flank it | not referenced by any instruction in this dump | | `$B160-$B25F` | 256 | `HighTileGraphicPointerTable` | 128 x 16-bit LE pointers to the 16-byte tile bitmaps, indexed by (tile index - `$80`) | `lda $B160,y` at `$B8B1`, `lda $B161,y` at `$B8B6` | | `$B260-$B35F` | 256 | `LowTileGraphics` | 16 x 16-byte NES 8x8 tile bitmaps (2 bits per pixel) | read through `LowTileGraphicPointerTable`: `lda (Zp19),y` at `$B8E0` | | `$B360-$B5BF` | 608 | `MidTileGraphics` | 38 x 16-byte NES 8x8 tile bitmaps | reached only through the unreferenced table at `$B080` | | `$B5C0-$B89F` | 736 | `HighTileGraphics` | 46 x 16-byte NES 8x8 tile bitmaps | read through `HighTileGraphicPointerTable`: `lda (Zp19),y` at `$B8E0` | | `$B966-$B969` | 4 | `ActorTypeOffsetTable` | 4 x 8-bit X offsets, one per actor type | `adc $B966,x` at `$B988` | | `$BA10-$BA4F` | 64 | `CellTileIndexGroupTable` | 16 groups of 4 background tile indices (values `$30`-`$3D`), one group copied per playfield cell | `lda $BA10,y` at `$BAA1` | | `$BA50-$BA8F` | 64 | `CellTileIndexGroupTable2` | 16 further groups of 4 background tile indices (values `$41`-`$4F`), the second half of the same table | `lda $BA10,y` at `$BAA1` (the same read reaches this half) | | `$BADD-$BB1C` | 64 | `TilePatchParameterTable` | 4 x 16-byte records of (offset, mask) pairs that patch the playfield tile buffer `$0400` and the per-pixel buffer `$0480`; indexed by actor type x 16 | `and $BADD,y` at `$BB41`, `adc $BADD,y` at `$BB49`/`$BB66`/`$BB95`, `ora $BADD,y` at `$BB53`/`$BB70`/`$BB84`/`$BBA1` | | `$BBC7-$BBCE` | 8 | `ActorPositionOffsetTable` | 8 x 8-bit position offsets, one per actor type | `adc $BBC7,y` at `$BC40`, `adc $BBCB,y` at `$BC52` | | `$BF0B-$BF28` | 30 | `EnemyDispatchJumpTable` | 15 x 16-bit LE code addresses (`$BF2B`-`$BFC0`, plus `$C081`); the base is odd, so the two byte reads address the table at byte granularity | `lda $BF0B,x` at `$BEFE`, `lda $BF0C,x` at `$BF03` - the target of `jmp (Zp1C)` at `$BF08` | | `$BF29-$BF60` | 56 | - | the enemy dispatch handlers the table above points at; the CDL marks 24 of these bytes as executed code | not read as data; reached by `jmp (Zp1C)` at `$BF08` | | `$C8CB-$C8CE` | 4 | `StatusTextAddressHighTable` | 4 x 8-bit high bytes of the nametable addresses of four status cells (the low byte `$23` is set in code) | `lda $C8CB,x` at `$C970` | | `$CA15-$CA1A` | 6 | `EnemyRosterPointerTable` | 3 x 16-bit LE pointers to the per-round enemy rosters | `lda $CA15,y` at `$CA8A`, `lda $CA16,y` at `$CA8F` | | `$CA1B-$CA40` | 38 | `EnemyRosterData` | 3 variable-length roster records: a count byte, a second count byte, then two arrays of tile/attribute bytes copied into `$40` and `$79` | read through the pointer above: `lda (Zp1D),y` at `$CA96`-`$CAB0` | | `$CA41-$CA4A` | 10 | `StatusWriteAddressLowTable` | 10 x 8-bit low bytes of the VRAM addresses of ten screen writes | `lda $CA41,x` at `$CAC1` | | `$CA4B-$CA54` | 10 | `StatusWriteAddressHighTable` | 10 x 8-bit high bytes of the same ten VRAM addresses | `lda $CA4B,x` at `$CAC7` | | `$CA55-$CA5E` | 10 | `StatusWriteTileTable` | 10 x 8-bit tile indices written to those addresses | `lda $CA55,x` at `$CACD` | | `$CB2B-$CB40` | 22 | `ActorTypeParameterTable` | 22 x 8-bit values in `$00-$03`; the byte read becomes the actor's type byte in `$86` | `lda $CB2B,y` at `$CB8E` | | `$CB41-$CB4E` | 14 | `ActorSpawnScheduleTable` | 14 x 8-bit tick counts compared against the frame counter `$02CC` | `lda $CB41,y` at `$CB58` | | `$CC52-$CCD5` | 132 | `ActorStateParameterTable` | 132 x 8-bit values in `$00-$03` (per-actor-type state data); the sign bit of the entry read selects the `$CCB5` half of the table | `lda $CC52,y` at `$CD5E`, `lda $CCB5,y` at `$CD63` | | `$CCD6-$CCF1` | 28 | `ActorStateScheduleTable` | 28 x 8-bit tick counts compared against the frame counter `$02CC` | `lda $CCD6,y` at `$CD00` | | `$CDA6-$CDB6` | 17 | `ActorScheduleTable` | 17 x 8-bit tick counts compared against the frame counter `$02CC` | `lda $CDA6,y` at `$CDC0` | | `$CEF9-$CF06` | 14 | `PlayfieldOffsetAndMaskTable` | 8 x 8-bit playfield X offsets followed by 4 x 8-bit bit masks | `adc $CEF9,y` at `$CF6C`, `and $CF03,y` at `$CF7B` and `$CFD4` | | `$D195-$D1A4` | 16 | `EnemyStepTable` | 16 x 8-bit movement/step offsets and masks, one per actor type | `adc $D195,y` at `$D1DF`, `and $D19D,y` at `$D1CB`/`$D25F`, `and $D1A1,y` at `$D242`/`$D2B0`, `lda $D199,y` at `$D255` | | `$D49B-$D49E` | 4 | `DigOffsetTable` | 4 x 8-bit X offsets used while digging a tunnel | `adc $D49B,y` at `$D594` and `$D66C` | | `$D49F-$D4B2` | 20 | `DigTileIndexTable` | 5 x 4 matrix of background tile indices, read at a 4-byte stride | `lda $D49F,y` at `$D5EA`, `$D601` and `$D61F` | | `$D702-$D711` | 16 | `SoilMapBitTable` | 2 rows of 8 bytes, 2 bits per cell (32 cells) | `lda $D702,y` at `$D760`, `ora $D70A,y` at `$D765` | | `$D9B7-$D9BA` | 4 | `ActorFlagMaskTable` | 4 x 8-bit flag masks, one per actor type | `and $D9B7,y` at `$DAF4` | | `$E177-$E17E` | 8 | `ActorSpeedTable` | 8 x 8-bit movement offsets, one per actor type | `adc $E177,y` at `$E1B6`, `adc $E17B,y` at `$E1D2` | | `$E2E3-$E2EC` | 10 | `ActorOffsetTable` | 10 x 8-bit offsets; the routine reads the entry at `$E2E7` | `lda $E2E7,y` at `$E37E` and `$E3A0` | | `$E777-$E796` | 32 | `ActorPatternPointerTable` | 16 x 16-bit LE pointers into `$E797` | `lda $E781,y` at `$E83F`, `lda $E782,y` at `$E844` | | `$E797-$E7FE` | 104 | `ActorPatternData` | the paired byte rows the pointer table addresses; each row is copied into the parallel arrays `$41` and `$7A` | read through the pointer above: `lda (Zp1B),y` at `$E84D`, `lda (Zp1D),y` at `$E852` | | `$E85C-$E85F` | 4 | `OverwriteTextAddressHighTable` | 4 x 8-bit high bytes of the VRAM addresses of four overwrite-text cells | `lda $E85C,x` at `$E88E` | | `$E860-$E863` | 4 | `OverwriteTextTileTable` | 4 x 8-bit tile indices written to those cells | `lda $E860,x` at `$E894` | | `$EB8F-$EB9E` | 16 | `RoundMonsterTypeTable` | 16 x 8-bit monster types, indexed by (round & `$1F`) | `lda $EB8F,x` at `$ED16` | | `$EB9F-$EBA1` | 3 | `RoundMonsterParamTable` | 3 x 8-bit parameters, used only for the first three rounds | `lda $EB9F,x` at `$ED1F` | | `$ED49-$ED58` | 16 | `RoundEnemyXOffsetTable` | 16 x 8-bit X offsets, indexed by (round & `$1F`) | `lda $ED49,x` at `$EE02` | | `$ED59-$ED68` | 16 | `RoundEnemyYOffsetTable` | 16 x 8-bit Y offsets, indexed by (round & `$1F`) | `lda $ED59,x` at `$EDD1` | | `$F01E-$F026` | 9 | `SpawnParamTableA` | tail of a 10-entry table whose first byte is the `rts` at `$F01D` | `lda $F01D,x` at `$F065` | | `$F027-$F030` | 10 | `SpawnCountTable` | 10 x 8-bit counts | `lda $F027,x` at `$F06A` | | `$F031-$F03B` | 11 | `SpawnTileTable` | 11 x 8-bit tile indices | `lda $F031,x` at `$F05F` | | `$F27B-$F282` | 8 | `ActorMoveOffsetTable` | 8 x 8-bit movement offsets, one per actor type | `adc $F27B,y` at `$F2B9`, `adc $F27F,y` at `$F2CF` | | `$F494-$F4AF` | 28 | `EnemyStateFlagTable` | 28 x 8-bit bit masks OR-ed into the state byte | `ora $F494,x` at `$F4F2` | | `$F4B0-$F4CB` | 28 | `EnemyStateScheduleTable` | 28 x 8-bit tick counts compared against the frame counter `$02CC` | `lda $F4B0,x` at `$F4DE` | | `$F4F8-$F507` | 16 | `NametablePatchRow` | 16 background tile indices written straight to `PPUDATA` | `lda $F4F8,x` at `$F5D2` | | `$F508-$F50F` | 8 | `NametablePatchRow2` | 8 background tile indices written straight to `PPUDATA` | `lda $F508,x` at `$F630` | | `$F69C-$F7C8` | 301 | - | decodes as 133 consecutive instructions; the CDL never touches a byte of it | not referenced by any instruction - code the tracer could not reach, not data | | `$F924-$F980` | 93 | - | the CDL marks 57 of these bytes as executed code | not referenced by any instruction - code the tracer could not reach (CDL-confirmed) | | `$F99A-$F9B8` | 31 | - | the CDL marks 17 of these bytes as executed code | not referenced by any instruction - code the tracer could not reach (CDL-confirmed) | | `$F9E6-$F9FD` | 24 | `SpritePlacementTable` | 12 x 16-bit LE (X position, attribute) pairs | `lda $F9E6,y` at `$F9C6`, `lda $F9E7,y` at `$F9CB` | | `$FA48-$FA4B` | 4 | `SpriteAttributeTable` | 4 x 8-bit sprite attribute bits (`$01`/`$02`/`$04`/`$08`) | `bit $FA48` at `$FA1A`, `lda $FA48,y` at `$FA42` | | `$FA55-$FAA4` | 80 | `ActorBehaviourPointerTable` | 40 x 16-bit LE pointers to the per-actor behaviour records, indexed by actor type x 2 | `lda $FA55,y` at `$FA33`, `lda $FA56,y` at `$FA38` | | `$FAA5-$FFF9` | 1365 | - | the per-actor behaviour records the pointer table addresses, plus sprite-animation tables and Namco WSG sound scripts | only `lda $FAA5,x` at `$F856` reads it directly; the sound/animation interpreter that consumes the rest is not reached by any instruction in this dump, so the bytes are left unnamed | | `$FFFA-$FFFB` | 2 | `NmiVector` | NMI vector, `$A749` | read by the CPU hardware; `jmp ($FFFC)` at `$E748` reads the reset vector next to it | | `$FFFC-$FFFD` | 2 | `ResetVector` | RESET vector, `$A52D` | read by the CPU hardware, and by `jmp ($FFFC)` at `$E748` | | `$FFFE-$FFFF` | 2 | `IrqVector` | IRQ vector, `$A52D` | read by the CPU hardware | ### Notes on the unread blocks - `$F69C-$F7C8`, `$F924-$F980` and `$F99A-$F9B8` are the only three whole blocks that no instruction in this dump references. All three decode cleanly as instruction streams, and the Mesen CDL capture (`digdug2_gameplay.cdl`) marks bytes of the last two as executed code, so they are code the static tracer could not reach rather than data. - Four sub-regions inside otherwise-named blocks are also unreferenced and are left unnamed: `$A4D1-$A4D7`, `$B080-$B15F`, `$BF29-$BF60` and `$FAA5-$FFF9`. Their bytes are recorded above with what they are. ## Open work - **Pass 1** - run `nesdis.py` over `prg.bin` to produce the code/data split. The tracer reads the vectors from the last six bytes of the PRG image, so the split seeds correctly. - **Pass 1** must emit the canonical audit comment `; CPU:$C123 PRG:0123 AD 21 03` at column 35 for every instruction. - **Pass 2** - replace the address-derived `Label_XXXX` names and the raw `$XXXX` call operands. - **Pass 3** - the data tables are named and interpreted; see the **Pass 3 data-table inventory** above. Still open: the semantic zero-page aliases (to be applied only at the sites where their role is proven). - **Promotion** to `NROM//` once Pass 3 is done, as `git mv`. ## Files | File | Purpose | |------|---------| | `prg.s` | Pass 0 PRG include (replaced by the Pass 1 disassembly) | | `header.s` | iNES header include | | `chr.s` | CHR include | | `prg.bin` | 32768 bytes (32 KB) PRG image | | `chr.bin` | 8192 bytes (8 KB) CHR image | | `rom.nes` | byte-identical copy of the original ROM | | `linker.cfg` | HEADER / PRG / CHR memory areas | | `symbols.inc` | NES hardware register names | | `locals.inc` | zero-page aliases (empty at Pass 0) | | `Makefile` | build and byte-identity check |