# Hydlide (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. `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 | Horizontal | | 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 | $8003 | | RESET vector | $8000 | | IRQ vector | $8003 | | ROM file | `Hydlide (U).nes` | | SHA-256 | `40535404b1a17c89d3fbfa24e8e8d63c1619f1e0540915b64c889ffd4f8c586e` | ### Header notes - none ## 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** - semantic zero-page aliases (applied only at the sites where their role is proven) and named data tables. The data tables are named and interpreted; see "Pass 3 data-table inventory" below. - **Promotion** to `NROM//` once Pass 3 is done, as `git mv`. ## 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 `$8000` (`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 | |---------|---------| | `$801A` | `SubC801A` | | `$8024` | `SubC8024` (falls through) | | `$8029` | `SubC8029` (falls through) | | `$8084` | `SubC8084` (falls through) | | `$820E` | `SubC820E` | **NMI** is at `$8003` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$E1C2` | `SubCE1C2` | **IRQ** is at `$8003` (`NmiHandler`) - a different address from RESET. ## Pass 3 data-table inventory The code in this project is still at Pass 1 - the routines carry address-derived `SubCXXXX` names. The *data* blocks are done: every `; ---- data $XXXX-$YYYY ----` region in `prg.s` is listed below, each with what its bytes are and the instruction that reads it. A tracer data block is a contiguous run of bytes the tracer never reached, so a block can hold a table, a table *and* unreached code, or code alone. Where a block holds both, the table and the code are listed separately. ### Evidence Two oracles were used besides the source itself: - **Mesen CDL** (`hydlide_gameplay.cdl`). Mesen's Code Data Logger records, per PRG byte, whether the CPU fetched it as an opcode, read it as data, executed it, or branched to it. Bytes the capture marks *Data* are definitely data; bytes it marks *Code* are definitely code and are therefore not data tables. - **The consuming instructions.** Every table below is named from the code that reads it - the operand, the addressing mode and what the routine then does with the value. ### How the text and tile tables were decoded Most of the "text" tables below are streams of **CHR tile indices**, not ASCII: Hydlide draws its text with 8x8 nametable tiles. A few regions are genuine ASCII though - their letter bytes sit at their ASCII code points (`$41` = `A`, `$42` = `B`, ...) and their `$40` acts as the space. That reading is confirmed by the ROM's own contents, not guessed: - `$83F8` reads `LIFESTREXPMAGICMONSTER` - the five HUD meters (LIFE, STR, EXP, MAGIC, MONSTER) written without separators because each label is drawn at its own screen position. - `$9861` reads a sequence of words: `SWORD`, `SHIELD`, `LAMP`, `CROSS`, `MEDICINE`, `POT`, `KEY`, `JEWEL`, `RING`, `RUBY`, `FAIRY`, `YOU`, `GET`, `A`, `SAVE`, `USE`, `THE`, `GRAVE`, `IS`, `BROKEN`, `ATTACK`, `DEFEND`, `PASSWORD:`, `MONSTER`, `DEMONSTRATION` - the treasure list and the message box of Hydlide. The sentences (`YOU GET A SWORD`, `YOU SAVE FAIRY 1`, `THE GRAVE IS BROKEN`) are runs of consecutive words in the pool. - `$D237` reads the monster roster: `LADY`, `GOLD`, `BLACK`, `ZOMBIE`, `ROPER`, `SKELTON`, `VAMPIRE`, `SLIME`, `KOBOLD`, `WORM`, `EEL`, `WIZARD`, `ROCK`, `TREE`, `GHOST`, `WASP`, `WISP`, `FIREBALDRAGON`, `GOBLIN`. - `$A87F` reads the pause menu: `ITEM`, `SAVE`, `LOAD`, `PASSWORD`, `SPEED`, `GAME`, `NOTHING`, `PUSH BUTTON`, `turn`, `fire`, `ice`, `wave`, `flash`, `TURN`, `FIRE`, `ICE`, `FLASH`, `WINDOW`, `OPEN`, `LOAD`, `=`, `YES`, `NO`, `NORMAL`, `HIGH`, `SPEED`. - `$B955` reads `0123456789BDGHJKLMNPQRTVWXYZ` - the 28 characters the password entry screen accepts (the vowels and the confusable letters are left out). Two blanks are worth spelling out. First, the ROM does put two words together with no separator - the pool at `$9861` holds `MEDICINE` and `POT` both separately and joined as `MEDICINEPOT` - so a run such as `ZOMBIEROPER` cannot be split from the bytes alone. Second, the pool at `$9861` uses `$20` for its spaces while `$83F8` and `$840E` use `$40`; both are the game's own font codes and the ROM does not say which one is "the" space. ### Inventory | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$83C7-$85DF` | 537 | see the nine rows below | the title-screen and credits text and graphics pool, interleaved ASCII and CHR tile indices | - | | `$83C7-$83F7` | 49 | `StatusMeterTileGraphic` | CHR tile indices: eight `$2D`, then tiles `$BD`-`$D0` in order, tile `$FC`, six `$40` blanks, then tiles `$87`-`$94` in order - the meter graphics that sit above the HUD labels | no instruction operand lands in it; the CDL capture marks `$83C7-$83F0` as read as data | | `$83F8-$840D` | 22 | `StatusMeterLabelText` | ASCII, no separator: `LIFESTREXPMAGICMONSTER` - the five HUD meter labels, each drawn at its own screen position | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$840E-$842A` | 29 | `MagicElementNameText` | ASCII, `$40` as the space: `turn -fire -ice -wave -flash` - the four spell elements (fire, ice, wave, flash) plus the word `turn` | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$8430-$843C` | 13 | `TitleLetterText` | ASCII, `$40` between the letters: `H Y D L I D E` - the title, one letter per cell | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$8442-$8447` | 6 | `AttackLabelText` | ASCII: `ATTACK` | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$8448-$84E3` | 156 | `TitleLogoTileGraphic` | CHR tile indices, mostly `$ED`-`$FA` with `$40` blanks - the title-screen logo picture | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$84E4-$855F` | 124 | `PublisherCopyrightText` | ASCII: `PUSH BUTTON <`, then the publisher line `(C)1986 T&E SOFT INC. (P)1989 FCI. (R)PONY CANYON INC. LICENSED BY NINTENDO OF AMERICA INC.`, then `tm` | no instruction operand lands in it; the CDL capture marks `$83F8-$8541` as read as data | | `$8560-$85BF` | 96 | `CreditsString` | ASCII: `HYDLIDE (C)1986 T&E SOFT Programmed by EIJI KATO '85.11.13 - 1.10 V1.0D '89. 1.12` - the title-screen credit line | `lda $8560,y` at `$80B4` (copies the 96 bytes to RAM `$0100`-`$015F`), `cmp $8560,y` at `$8087` (compares the RAM copy back against the ROM) | | `$85C0-$85DF` | 32 | - | 32 bytes of `$20` (spaces) - the padding after the credits line | not referenced by any instruction | | `$8B4A-$8DCC` | 643 | see the two rows below | the object-type dispatch table plus the code it dispatches to | - | | `$8B4A-$8B8F` | 70 | `ObjectTypeDispatchTable` | 35 x 16-bit LE code pointers into `$8B90-$8DCC`, one per object type - the same 35 types the tables at `$EC00` and `$D2DD` cover. 24 of the 35 entries point at the default handler `$8B90` | `lda $8B4A,x` at `$8B3C` and `lda $8B4A,x` at `$8B42` (low and high byte of the entry), jumped to through `jmp (Zp00)` at `$8B47` | | `$8B90-$8DCC` | 573 | - | unreached code: the handler bodies the dispatch table points at. Decodes as a long run of instructions, and the CDL marks `$8B90` as code | not referenced by any instruction | | `$8DDA-$8DDB` | 2 | - | unreached code: the single instruction `sta $0A`, orphaned between the `jmp $8E20` at `$8DD7` and the routine at `$8DDC` | not referenced by any instruction | | `$97C4-$9A75` | 690 | see the eight rows below | the sprite-position, sprite-graphics and message-text tables | - | | `$97C4-$97D5` | 18 | `SpriteSlotPositionTable` | 9 x 16-bit LE pairs; the low byte is the on-screen X position of a sprite slot and the high byte its Y position | `lda $97C4,x` at `$8EFA`, with x = 2 x the slot number; the value is added to `Zp3C` and returned as the X coordinate. The CDL capture marks `$97C4-$97C9` as read as data | | `$97D6-$9801` | 44 | `MonsterSpriteTileAttributeTable` | 22 x 16-bit LE (CHR tile, sprite attribute) pairs, one per monster type; most are `$0202` | `lda $97D6,x` at `$912C` and `lda $97D7,x` at `$9131` (low and high byte), also at `$A1F2`/`$A1F7` and `$A261`/`$A266`. The CDL capture marks `$97E4-$97E7` as read as data | | `$9802-$9815` | 20 | - | 20 bytes of small values (`$00`-`$13`); no instruction in this dump references them | not referenced by any instruction | | `$9816-$9818` | 3 | `SpriteAttributeTable` | 3 x 8-bit sprite attributes (`$01`, `$03`, `$02`); the attribute of the sprite the player is about to draw | `lda $9816,x` at `$9381`, stored in `ZpAA` and then written to the OAM shadow at `$0200` | | `$9819-$9830` | 24 | `ItemIconTileTable` | 12 x 16-bit LE (CHR tile, sprite attribute) pairs, tiles `$00`-`$0B` with attribute `$00` - the icons of the twelve treasure items | `lda $9819,y` at `$91E2`, `$91EA`, `$91FE`, `$9206`, `$9236`, `$923E`, `$9263`, `$926B` (written into the OAM shadow). The CDL capture marks `$9819-$9858` as read as data | | `$9831-$9858` | 40 | `SpriteIconTileTable` | 20 x 16-bit LE (CHR tile, sprite attribute) pairs; the attribute is `$40` (vertical flip) on eight of them and `$00` on the rest | no instruction operand lands in it; the CDL capture marks `$9819-$9858` as read as data | | `$9859-$9860` | 8 | `TextStringOffsetTable` | 4 x 16-bit LE values `$00EA`, `$00EB`, `$00EC`, `$00ED` - four indices into the text pool that follows | not referenced by any instruction in this dump | | `$9861-$9A75` | 533 | `MessageTextPool` | ASCII, `$40` as the space: a sequence of words that the game prints in runs to build a message. The words are `SWORD`, `SHIELD`, `LAMP`, `CROSS`, `MEDICINE`, `POT`, `KEY`, `JEWEL`, `RING`, `RUBY`, `FAIRY`, `1`, `2`, `3`, `YOU`, `GET`, `A`, `SAVE`, `USE`, `THE`, `GRAVE`, `IS`, `BROKEN`, `ATTACK`, `DEFEND`, `PASSWORD:`, `MONSTER`, `DEMONSTRATION` - so `YOU GET A SWORD`, `YOU SAVE FAIRY 1`, `YOU USE A MEDICINE` and `THE GRAVE IS BROKEN` are each a run of consecutive words in the pool | no instruction operand lands in it (the pool is reached through a computed pointer); the CDL capture marks `$9A15-$9A20`, `$9A38-$9A4E` and `$9A58-$9A5E` as read as data | | `$A87F-$A926` | 168 | `MenuTextPool` | ASCII, `$40` as the space: a sequence of words for the pause menu - `ITEM`, `SAVE`, `LOAD`, `PASSWORD`, `SPEED`, `GAME`, `NOTHING`, `PUSH BUTTON`, `turn`, `fire`, `ice`, `wave`, `flash`, `TURN`, `FIRE`, `ICE`, `FLASH`, `WINDOW`, `OPEN`, `LOAD`, `=`, `YES`, `N`, `ONORMAL`, `HIGH`, `SPEEDPASSWORD` - plus four `<` characters that mark the end of a menu line | no instruction operand lands in it; the CDL capture marks `$A8B0-$A8CF` as read as data | | `$B427-$B4DC` | 182 | - | unreached code: decodes as 86 consecutive instructions, so it is code the tracer could not reach, not data | not referenced by any instruction | | `$B955-$BAF1` | 413 | see the seven rows below | the password-entry alphabet and the title/password text pool | - | | `$B955-$B970` | 28 | `PasswordAlphabetText` | ASCII: `0123456789BDGHJKLMNPQRTVWXYZ` - the 28 characters the password screen accepts (no vowels, no confusable letters) | `lda $B955,x` at `$B688`, with x = the index into the alphabet | | `$B971-$B994` | 36 | `PasswordCharSlotTable` | 36 x 8-bit: the alphabet index of each of the 36 on-screen character slots, `$FF` for the eight slots that are not usable (A, C, E, F, I, O, S, U) | `lda $B971,x` at `$B6B0` | | `$B995-$B9A4` | 16 | `SpriteTileOffsetTableA` | 16 x 8-bit CHR tile indices (`$AD`-`$C7`); the code adds them to an accumulator to pick a tile | `adc $B995,y` at `$AED8` | | `$B9A5-$B9B4` | 16 | `SpriteTileOffsetTableB` | 16 x 8-bit CHR tile indices (`$AC`-`$CA`); added to an accumulator to pick a tile | `adc $B9A5,y` at `$AF03` | | `$B9B5-$B9C4` | 16 | `SpriteTileOffsetTableC` | 16 x 8-bit CHR tile indices (`$B4`-`$CE`); subtracted from an accumulator to pick a tile | `sbc $B9B5,y` at `$AF12` | | `$B9C5-$B9E4` | 32 | `EntityWalkOffsetTable` | 32 x 8-bit displacements `$00`-`$09`; only the first 16 are used, the index being a random number 0-15. The byte is added to (or subtracted from) an entity's coordinate and the result is wrapped at 28, so this is the random-walk displacement table | `adc $B9C5,x` at `$B652` and `sbc $B9C5,x` at `$B6DE` | | `$B9E5-$BAF1` | 269 | `TitleAndPasswordText` | ASCII interleaved with CHR tile indices: `HYDLIDE`, `GAME START PASSWORD`, the 36-entry password alphabet, `PASSWORD IN`, `{END:INPUT }`, `PASSWORD`, `ERROR <`, `CONGRATULATION<`, `GAME OVER <`, `NAME` | no instruction operand lands in it; the CDL capture marks `$B9E9-$BA0B` as read as data | | `$BB2D-$BFDE` | 1202 | see the two rows below | the per-entity update dispatch table plus the code it dispatches to | - | | `$BB2D-$BB58` | 44 | `EntityUpdateDispatchTable` | 22 x 16-bit LE code pointers into `$BB59-$BFDE`; the routine loops over the live entities and calls the handler of each entity's type | `lda $BB2D,x` at `$BB0A` and `lda $BB2E,x` at `$BB0F`, then `jsr a:Zp12` at `$BB14` | | `$BB59-$BFDE` | 1158 | - | unreached code: the entity-update handlers. The CDL marks runs of code from `$BB59` to `$BCD8` | not referenced by any instruction | | `$C0A9-$C79D` | 1781 | see the two rows below | the monster-action dispatch table plus the code it dispatches to | - | | `$C0A9-$C0D4` | 44 | `MonsterActionDispatchTable` | 22 x 16-bit LE code pointers into `$C0D5-$C79D` | `lda $C0A9,x` at `$C09A` and `lda $C0AA,x` at `$C09F`, jumped to through `jmp (Zp00)` at `$C0A6` | | `$C0D5-$C79D` | 1737 | - | unreached code: the monster-action handlers. The CDL marks runs of code from `$C0FF` to `$C320` | not referenced by any instruction | | `$C7BB-$C7C0` | 6 | - | unreached code: `jsr $CF77` / `jsr $D14C`, the tail of a routine the tracer could not reach. The CDL marks all six bytes as code | not referenced by any instruction | | `$C96E-$C999` | 44 | `MonsterHitBoxTable` | 22 x 16-bit LE pairs, one per monster type; the low and high byte are compared separately against the value in RAM, so the pair is the monster's hit-test window | `lda $C96E,x` at `$C7B0` and `lda $C96F,x` at `$C7B5`, stored in `Zp8F`/`Zp90` and compared at `$CBBF` and `$CBC3` | | `$CB7D-$CBBC` | 64 | see the two rows below | the hit-reaction dispatch table plus the code it dispatches to | - | | `$CB7D-$CBA8` | 44 | `MonsterHitReactionDispatchTable` | 22 x 16-bit LE code pointers into `$CBA9-$CBBC` | `lda $CB7D,x` at `$CB6E` and `lda $CB7E,x` at `$CB73`, jumped to through `jmp (Zp00)` at `$CB7A` | | `$CBA9-$CBBC` | 20 | - | unreached code. The CDL marks `$CBB2-$CBBC` as code | not referenced by any instruction | | `$CBCD-$CC7A` | 174 | - | unreached code: decodes as 88 consecutive instructions, so it is code the tracer could not reach, not data | not referenced by any instruction | | `$CCAD-$CE8B` | 479 | see the two rows below | the monster-sprite dispatch table plus the code it dispatches to | - | | `$CCAD-$CCD8` | 44 | `MonsterSpriteDispatchTable` | 22 x 16-bit LE code pointers into `$CCD9-$CE8B` | `lda $CCAD,x` at `$CCA0` and `lda $CCAE,x` at `$CCA5`, jumped to through `jmp (Zp00)` at `$CCAA` | | `$CCD9-$CE8B` | 435 | - | unreached code: the monster-sprite handlers. The CDL marks `$CCF7-$CD47` as code | not referenced by any instruction | | `$CEDE-$CEE0` | 3 | - | unreached code: the single instruction `jsr $CEE1` | not referenced by any instruction | | `$CF0D-$CF45` | 57 | - | unreached code: decodes as 32 consecutive instructions, so it is code the tracer could not reach, not data | not referenced by any instruction | | `$CFFB-$D143` | 329 | see the two rows below | the monster-spawn dispatch table plus the code it dispatches to | - | | `$CFFB-$D026` | 44 | `MonsterSpawnDispatchTable` | 22 x 16-bit LE code pointers into `$D027-$D143` | `lda $CFFB,x` at `$CF96` and `lda $CFFC,x` at `$CF9B`, then `jsr a:Zp12` at `$CFA4` | | `$D027-$D143` | 285 | - | unreached code: the monster-spawn handlers. The CDL marks runs of code from `$D032` to `$D0E0` | not referenced by any instruction | | `$D176-$D4BA` | 837 | see the eight rows below | the per-monster-type parameter tables, the monster-name pool and the object movement-pattern tables | - | | `$D176-$D18B` | 22 | `MonsterSpriteAttributeTable` | 22 x 8-bit sprite attributes (`$00`-`$03`, i.e. the sprite palette), one per monster type | `lda $D176,x` at `$CC92` and `lda $CF84`, stored in `ZpAA` and written to the OAM shadow at `$0200` | | `$D18C-$D1A1` | 22 | `MonsterSpriteTileTable` | 22 x 8-bit CHR tile indices (`$12`-`$C9`), one per monster type: the first tile of that monster's sprite | `lda $D18C,x` at `$CC97` and `lda $CF89`, stored in `ZpAB` and written to the OAM shadow at `$0200` | | `$D1A2-$D1B9` | 24 | `MonsterParameterTable` | 12 x 16-bit LE values; only the low byte of each entry is used, so it is a per-monster-type byte table at a stride of two. The role of the value is not established by this dump | `lda $D1A2,x` at `$D148`, with x = 2 x the monster type | | `$D1BA-$D236` | 125 | - | 125 bytes of small values and a few ASCII-looking bytes (`$4B`,`$4A`,`$6E`,`$6A` at `$D1C6-$D1C9`); no instruction in this dump references them | not referenced by any instruction | | `$D237-$D2D5` | 159 | `MonsterNamePool` | ASCII, `$40` as the space: a sequence of words - the monster roster. The space-separated runs are `LADYAM`, `GOLDAM`, `BLACKAM`, `ZOMBIEROPER`, `SKELTONVAMPIRESLIME`, `KOBOLD`, `WORM`, `EEL`, `WIZARD`, `ROCK`, `TREEGONSTIRGE`, `WASP`, `WISP`, `FIREBALDRAGON`, `VARALYSHYPER`, `GOBLIN`. The message pool at `$9861` shows that the ROM does put two words together with no separator (it holds `MEDICINE` and `POT` both separately and joined), so these runs may each be two names (`LADY`+`AM`, `ZOMBIE`+`ROPER`, `SKELTON`+`VAMPIRE`+`SLIME`, ...) - the ROM gives no way to split them | no instruction operand lands in it; the CDL capture marks `$D26D-$D273` (` KOBOLD`) as read as data | | `$D2D6-$D2DC` | 7 | - | seven bytes (`$8E`-`$94`) that look like CHR tile indices; no instruction in this dump references them | not referenced by any instruction | | `$D2DD-$D345` | 105 | `ObjectPatternRecordTable` | 35 x 3-byte records: one byte of pattern length followed by the 16-bit LE address of that pattern. The 35 records address the 35 object types of `$EC00` | `lda $D2DD,x` at `$C9A0`, `lda $D2DE,x` at `$C9A5` and `lda $D2DF,x` at `$C9AA`, with x = 3 x the object type | | `$D346-$D4BA` | 373 | `ObjectPatternData` | the movement patterns the records above point at: 35 runs of 3-8 bytes each, values `$01`-`$0F` | reached through the records at `$D2DD`; the CDL capture marks `$D346-$D349` as read as data | | `$D60C-$DAC3` | 1208 | see the two rows below | the object-script dispatch table plus the code it dispatches to | - | | `$D60C-$D63F` | 52 | `ObjectScriptDispatchTable` | 26 x 16-bit LE code pointers into `$D640-$DAC3` | `lda $D60C,x` at `$D5FE` and `lda $D60C,x` at `$D604`, jumped to through `jmp (Zp00)` at `$D609` | | `$D640-$DAC3` | 1156 | - | unreached code: the object-script handlers. The CDL marks `$D790-$D793` and `$D7B2-$D7B8` as code | not referenced by any instruction | | `$DC8D-$DD67` | 219 | - | unreached code: the CDL marks every one of the 219 bytes as code | not referenced by any instruction | | `$DE16-$DE50` | 59 | - | unreached code: the CDL marks 37 of the 59 bytes as code | not referenced by any instruction | | `$DE93-$DEB8` | 38 | - | unreached code: decodes as 21 consecutive instructions, so it is code the tracer could not reach, not data | not referenced by any instruction | | `$DEE5-$DF10` | 44 | - | unreached code: decodes as 26 consecutive instructions, so it is code the tracer could not reach, not data | not referenced by any instruction | | `$DF91-$E141` | 433 | see the five rows below | the entity parameter, sprite-tile, bitmap and script tables | - | | `$DF91-$DFC3` | 51 | `EntityParamTable` | 51 x 8-bit values `$00`-`$12`, indexed by entity number | `lda $DF91,x` at `$A622` | | `$DFC4-$DFF0` | 45 | `NametablePaletteTable` | 45 x 8-bit palette numbers `$00`-`$03`; the value is shifted left by four bits and OR-ed into the attribute byte of a nametable cell | `lda $DFC4,x` at `$8DE4`, `$8E4B`, `$DB3F` and `$DBDF` | | `$DFF1-$DFF2` | 2 | - | two bytes (`$01`, `$00`); the mask table below starts at `$DFF3`, so these two are not reached | not referenced by any instruction | | `$DFF3-$E020` | 46 | `ObjectStateFlagMaskTable` | 46 byte masks, `$00` or `$80`, indexed by an entity's state byte; the mask is OR-ed into the state byte at `$04E4+x` | `lda $DFF3,y` at `$DE74` and `lda $DFF3,y` at `$DE84` | | `$E021-$E141` | 289 | `EntityScriptData` | a script of (value, duration) byte pairs in the same format as the music script at `$E396`; the values are `$00`-`$3B` and the durations `$01`-`$10` | reached through a computed pointer; the CDL capture marks `$E022-$E031`, `$E0C2-$E0D1`, `$E102-$E111` and `$E122-$E141` as read as data | | `$E2B5-$E2C3` | 15 | - | unreached code: a run of `lda #$NN` / `jmp $E271` stubs | not referenced by any instruction | | `$E2CE-$FFFF` | 7474 | see the sixteen rows below | the APU note/envelope tables, the music scripts, the fixed sprite definitions and the per-object scripts | - | | `$E2CE-$E32F` | 98 | `ApuNotePeriodTable` | 49 x 16-bit LE APU timer periods (`$0800`, `$0B57`, `$0B27`, ... `$0839`), one per note, written straight into the pulse-channel timer registers | `lda $E2CE,x` at `$E1DD` and `lda $E2CF,x` at `$E1E3`, with x = 2 x the note number; the result is stored in `APUPULSE2TIMERLO`/`APUPULSE2TIMERHI` | | `$E330-$E347` | 24 | `MusicScriptPointerTable` | 12 x 16-bit LE pointers into the script pool at `$E396` - the note sequence of each sound | `lda $E330,y` at `$E15F`, `$E165`, `$E200`, `$E206`, `$E20D` and `$E213` | | `$E348-$E35F` | 24 | `MusicBassScriptPointerTable` | 12 x 16-bit LE pointers into the script pool at `$E396`; a second script per sound, kept in `ZpC3`/`ZpC4` next to the first one | `lda $E348,y` at `$E170`, `$E176`, `$E248`, `$E24E`, `$E255` and `$E25B` | | `$E360-$E377` | 24 | `Pulse2EnvelopeTable` | 6 x 4-byte APU presets for the second pulse channel: control, sweep, timer low, timer high | `lda $E360,y` at `$E181`, `$E188`, `$E18F` and `$E196` | | `$E378-$E38F` | 24 | `TriangleEnvelopeTable` | 6 x 4-byte APU presets for the triangle channel: control, `$4009`, timer low, timer high | `lda $E378,y` at `$E19E`, `$E1A5`, `$E1AC` and `$E1B3` | | `$E390-$E395` | 6 | `MusicTempoTable` | 6 x 8-bit playback rates (`$FF`, `$07`, `$07`, `$07`, `$07`, `$03`), one per sound, counted down once per frame | `lda $E390,y` at `$E152`, with y = the sound number divided by four | | `$E396-$E69D` | 776 | `MusicScriptData` | the music scripts: (note, duration) byte pairs. The note is doubled and used as an index into the 49-entry period table at `$E2CE`, so a note above `$31` falls outside that table and is a control code rather than a note (`$91`, `$A1`, `$D1`, `$F1`, `$FF` and friends); the duration is counted down in frames | reached through the pointers at `$E330`/`$E348`; the CDL capture marks `$E396-$E41F` as read as data | | `$E69E-$E6CD` | 48 | `FixedSpriteDefinitionTable` | 12 x 4-byte OAM records: Y position, CHR tile, sprite attribute, X position - the fixed status-bar sprites (the meter bars at the bottom of the screen) | `lda $E69E,y` at `$E27A`, `lda $E69F,y` at `$E280`, `lda $E6A0,y` at `$E286` and `lda $E6A1,y` at `$E28C` | | `$E6CE-$E6E1` | 20 | `FixedSpriteDefinitionTableB` | 5 x 4-byte OAM records in the same (Y, tile, attribute, X) layout | `lda $E6CE,y` at `$E29C`, `lda $E6CF,y` at `$E2A2`, `lda $E6D0,y` at `$E2A8` and `lda $E6D1,y` at `$E2AE` | | `$E6E2-$E9FF` | 798 | - | 798 bytes of `$00` - padding between the fixed sprite definitions and the animation scripts | not referenced by any instruction | | `$EA00-$EAFF` | 256 | `AnimationFrameScript` | a byte script read one byte at a time: the low nibble is a frame count and the high nibble a parameter. A count of zero is replaced by 16 | `lda $EA00,y` at `$8728`, with y the running script index in `ZpB0` | | `$EB00-$EBFF` | 256 | `AnimationDirectionScript` | a byte script of the same shape: the low nibble is a frame count and bit 3 of the high nibble picks the direction - the value stored in `ZpB1` is 0 or -1 (`asl a` / `bcc` / `dey` at `$8758-$875B`). The first 128 bytes repeat once | `lda $EB00,y` at `$8746`, with y the running script index in `ZpB3` | | `$EC00-$EC45` | 70 | `ObjectScriptPointerTable` | 35 x 16-bit LE pointers into the script pool at `$EC46` - the script of each of the 35 object types | `lda $EC00,x` at `$D513` and `lda $EC00,x` at `$D519`, with x = 2 x the object type | | `$EC46-$FF17` | 4818 | `ObjectScriptData` | the object scripts: (value, duration) byte pairs in the same format as the music script at `$E396` | reached through the pointers at `$EC00`; the CDL capture marks `$EC46-$ECC3` as read as data | | `$FF18-$FFF9` | 226 | - | unreferenced; a run of 16-bit little-endian values that read as the ASCII `BaCbCaBa` (the high bytes are `$61`/`$62`/`$63`). No instruction in this dump references it | not referenced by any instruction | | `$FFFA-$FFFF` | 6 | - | the CPU vectors: NMI `$6142`, RESET `$6243`, IRQ `$6142` | read by the CPU, not by an instruction | ## 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 | --- ## Entry-point naming (2026-10-21) Every `jsr`/`jmp` target that carried an address-derived `SubCxxxx` name was reviewed against the routine it points at, the code that calls it and the RAM fields it touches. | | count | |---|---| | Address-derived entry points before | 190 | | Entry points now carrying a semantic name | **124** | | Entry points still address-derived | **66** | The names now in `prg.s` cover: * **PPU and VRAM** — `InitHardware`, `InitScreen`, `SetPpuAddress`, `WritePpuDataByte`, `SetPpuControl`, `UploadSprites`, `ClearSpriteBuffer`, `BuildSpriteList`, `RefreshSpriteList`, `SetSpriteSlot`, `SetBackgroundPaletteAddress`, `SetSpritePaletteAddress`, `ComputeTileAddress`, `ComputeAttributeAddress`, `LoadTileCoordinates`, `FetchTileCoordinates`, `ComputeScreenPosition`, `SetEntityScreenPosition`, `DrawHudTilePair`, `DrawStatusBar`, `DrawHudPanel`, `DrawStatusTileRow`, `WriteTileToPpu`, `WriteTilePairToPpu`. * **Sprites** — `WriteOamSprite`, `WriteOamSpriteTile`, `WriteOamSpriteEntry`, `DrawItemIconSprite`, `DrawEntitySprite`, `SetSpriteDrawParams`, `SetEntitySpriteParams`, `GetMonsterSpriteTile`, `GetMonsterSpriteAttribute`, `SetMonsterSpriteAttribute`, `ClearUpperSpriteBuffer`. * **Music and sound** — `UpdateMusic`, `PlayPulseNote`, `PlayBassNote`, `PlaySoundEffect`, `PlayNoiseEffect`, `EnableNoiseChannel`, `EnableAllSoundChannels`. * **Input and menus** — `ReadControllers`, `CheckPlayerInput`, `HandleMenuInput`. * **Game set-up** — `StartNewGame`, `StartNewGameAlt`, `InitGameScreen`, `InitLevelState`, `InitMonsterState`, `InitBoard`, `ResetEntityState`, `ResetEntityList`, `ResetEntityListAlt`, `ClearSpritesAndMusic`, `ResetScreenAndSprites`, `ResetScreenAndSpritesAlt`, `NextEntityRecord`, `GetEntityScreenPosition`, `GetEntityScreenPositionAlt`, `GetEntityScreenPositionAlt2`, `WriteEntityRecord`, `WriteEntityRecordAlt`, `WriteEntityRecordAlt3`, `WriteEntityRecordAlt4`, `WriteEntityRecordAt`, `CopyEntityRecord`, `ClearEntityFlag`, `ClearEntityFlagAlt`, `ClearEntityFlagAlt2`. * **Objects, doors and monsters** — `CallObjectTypeHandler`, `CallMonsterHitReaction`, `GetMonsterParameter`, `GetObjectTypeHandler`, `CheckMonsterHit`, `CheckMonsterHitAlt`, `PlayMonsterHitSound`, `PlayItemSound`, `OpenDoor`, `CloseDoor`, `DrawDoorFrame`, `UpdateMonsterSprites`, `UpdatePlayerFacing`, `ReverseFacing`, `GetSpriteSlotForEntity`, `ComputeGridIndex`, `ReadTileMapByte`, `GetTileMapIndex`, `SetTilePosition`, `SetScreenTileAddress`, `SetSpriteYPosition`, `SetSpriteYPositionAlt`, `SetSpriteYPositionAlt2`, `SetSpriteYPositionAlt3`, `ModuloDistance`, `NextRandomNumber`. **Residue — 66 entry points remain address-derived.** They are the one-instruction fragments of the entity and menu state machines: helpers that load a constant, branch on a flag the dump does not explain, or select one of the game's per-entity slots. The dump does not reveal a role that would distinguish them from their neighbours, so they keep their address-derived names. They are `SubC820E`, `SubC85E0`, `SubC877A`, `SubC8825`, `SubC8893`, `SubC891C`, `SubC8974`, `SubC89E6`, `SubC89F0`, `SubC8A06`, `SubC8A10`, `SubC8AE8`, `SubC8AF9`, `SubC8B08`, `SubC8B19`, `SubC8E41`, `SubC8F02`, `SubC8F06`, `SubC8F41`, `SubC90F6`, `SubC9144`, `SubC92E8`, `SubC9326`, `SubC963C`, `SubC9A76`, `SubC9B35`, `SubC9BB0`, `SubC9D07`, `SubC9D64`, `SubC9EA7`, `SubCA053`, `SubCA070`, `SubCA0A6`, `SubCA158`, `SubCA1BD`, `SubCA2EB`, `SubCA46B`, `SubCA4CB`, `SubCA50A`, `SubCA57E`, `SubCA5EF`, `SubCA60D`, `SubCA67E`, `SubCA927`, `SubCAB5A`, `SubCAE97`, `SubCAEB3`, `SubCAF7E`, `SubCB082`, `SubCB08D`, `SubCB22F`, `SubCB50B`, `SubCB694`, `SubCB902`, `SubCB90D`, `SubCC7C1`, `SubCC86D`, `SubCCB4D`, `SubCCC7B`, `SubCD154`, `SubCD4BB`, `SubCD550`, `SubCDAC4`, `SubCDBD3`, `SubCDD75`, `SubCDE6E`.