# Port Test Cartridge (U) ## Status **Pass 1 + Pass 2 + Pass 3 complete.** 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`, links them with `ld65` and compares the result against `rom.nes`: ``` Byte-identical ROM verified. ``` ### The split The cartridge is the NES Control Deck Test: a menu-driven diagnostic whose engine is small and whose *data* is most of the PRG. The Pass 1 split was redone from a Mesen Code Data Logger capture of a real session. The capture's jump-target flag names the addresses the CPU branched to, and those are the seeds a static trace cannot work out; seeding the tracer with them, and with the start of every data block the first trace left behind, reaches the code behind each menu entry. | | | |---|---| | Instructions | 813 | | Code bytes | 1,697 of 32,768 (5.2%) | | Labels | 114 | | Data blocks | 18 | The coverage is low *by design*: the cartridge has no CHR ROM, so the 16 KB character font at `$8001` is uploaded into CHR RAM at run time, and the remainder is command tables and `$FF` padding. The engine that reads those tables is 1,697 bytes. ## 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 | none | | Trainer | none | | Footer | none | | NMI vector | $CC49 | | RESET vector | $C000 | | IRQ vector | $CC17 | | ROM file | `Port Test Cartridge (U).nes` | | SHA-256 | `72c020701de2c4f79fc559a001c8c276562c38f9df70e7a10023fb9c209118bc` | ### Header notes - none ## Open work - The **character font's exact record format** is not settled. The bytes are provably 16-byte records that each carry one 7-pixel-wide row of a character, but whether a tile is eight such records or the table is a list of single rows is not established: the code that reads the font sits in the region the capture never executed. - The **command tables** at `$C2F2` and `$C342` have the shape of a character-to-glyph index table (values `$04`-`$24`, one per character of a string), but the code that indexes them was never executed either, so that reading is not proven. - The **routine names** are semantic for 18 of the entry points (`EnableNmi`, `SetPpuAddress`, `StrobeController`, `DrawGlyph`, `GetReturnAddress`, `SelectNametable0`, `SelectNametable1` and the rest of the list in the naming-residue section below) and address-derived for the remaining nine. The cartridge's menu roles are not established from the code alone, so the nine are listed with the reason. - The **zero-page aliases** in `locals.inc` are the generated `ZpXX` names with no behavioural alias applied: the roles are not proven. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. **RESET** is at `$C000` (`Reset`). The CPU comes here out of reset with the PPU and APU in their power-on state and the zero page and RAM uninitialised. The reset path calls: | Address | Routine | |---------|---------| | `$C010` | `SubCC010` (falls through) | | `$D1A2` | `SubCD1A2` | | `$CE68` | `SubCCE68` | | `$C040` | `SubCC03D` (falls through) | | `$D1A5` | `SubCD1A5` (falls through) | | `$CEBB` | `SubCCEBB` | | `$CE7C` | `SubCCE7C` (falls through) | | `$C048` | `SubCC045` (falls through) | **NMI** is at `$CC49` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$CE68` | `SubCCE68` | **IRQ** is at `$CC17` (`IrqHandler`) - a different address from RESET. ## Pass 3 data-table inventory Every data block in the PRG is listed here. A block with a name was named from the evidence below; a block with `-` is recorded as unreferenced data with its ROM provenance, or as code the tracer could not reach, and is deliberately not named after something it might be. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$8001-$BFFF` | 16,383 | `ChrRamCharacterFont` | 16-byte records; each carries one 7-pixel-wide row of a character at offset 8. The cartridge has no CHR ROM, so this is the font uploaded into CHR RAM. | `ora $BB20,y` at `$CCE7` (in the region the capture never executed) | | `$C04D-$C050` | 4 | - | unreached code (`lda #$80` / `sta $1A`) | not referenced by any instruction | | `$C0A3-$C0AD` | 11 | - | unreached code | not referenced by any instruction | | `$C164-$C181` | 30 | - | unreached code | not referenced by any instruction | | `$C1CA-$C1CD` | 4 | - | unreached code | not referenced by any instruction | | `$C2F2-$C30D` | 28 | - | first 20 bytes are values in `$04`-`$24`, the shape of a character-to-glyph index table; the last 8 are unreached code | not referenced by any instruction | | `$C342-$C3F8` | 183 | - | first 14 bytes are values in `$04`-`$24`, the shape of a character-to-glyph index table; the rest is unreached code | not referenced by any instruction | | `$C425-$C428` | 4 | - | unreached code (`ldx #$00` / `ldy #$00`) | not referenced by any instruction | | `$C437` | 1 | - | unreached code (undocumented `$99`) | not referenced by any instruction | | `$C465-$CC16` | 1,970 | - | unreached code, then a run of `$FF` fill | not referenced by any instruction | | `$CCC6-$CCD8` | 19 | - | 19 bytes of PPU attribute values: `$0F` (background), `$30` and `$10` (highlight) | not referenced by any instruction | | `$CCE4` | 1 | - | unreached code | not referenced by any instruction | | `$CCE6` | 1 | - | unreached code | not referenced by any instruction | | `$CCEA` | 1 | - | unreached code | not referenced by any instruction | | `$CDC0-$CDC6` | 7 | - | unreached code (`lda #$00` / `sta $26` / `jmp $CDA4`) | not referenced by any instruction | | `$CF3F-$CF42` | 4 | - | the tail of a character-font record | not referenced by any instruction | | `$CF44-$D1A1` | 606 | `SecondaryCharacterFont` | the same 16-byte record format as `$8001`, offset by five bytes: 37 more character rows | not referenced by any instruction | | `$D1D4-$FFFF` | 11,820 | - | unreached code, then `$FF` fill to `$FFF9`, then the three vectors at `$FFFA` (`$49 $CC` NMI, `$00 $C0` RESET, `$17 $CC` IRQ) | not referenced by any instruction | **Why so much is unreferenced.** The Control Deck Test only executes the path the menu cursor is on. The capture marked 1,382 bytes as code and the rest of the PRG as unknown, so the code behind the other menu entries was never observed running. Those bytes are still classified as code where they decode as such; the rest stay `.byte`. ## Files | File | Purpose | |------|---------| | `prg.s` | the disassembly: 813 instructions and the data blocks listed above | | `header.s` | iNES header include | | `prg.bin` | 32768 bytes (32 KB) PRG image | | `rom.nes` | byte-identical copy of the original ROM | | `linker.cfg` | HEADER / PRG memory areas | | `symbols.inc` | NES hardware register names | | `locals.inc` | zero-page aliases | | `routinemap.tsv` | the Pass 2 address-to-name map | | `Makefile` | build and byte-identity check | --- ## Naming residue — address-derived entry points Every `jsr`/`jmp` target in this project now carries a semantic name except **9**, which keep the honest address-derived `SubCCXXXX` form. This is a menu-driven diagnostic: the ROM is mostly a character font and command tables, and the code that would say what each menu entry *is* was never executed by the capture the split was made from. | Label | Address | Why the dump does not settle the name | |--------|---------|-------------------------------------| | `SubCC0AE` | `$C0AE` | The first menu entry's setup: it writes the value `$55` into the display shift register, calls the bit-shift helper twice, then writes `$24` to the VRAM pointer and starts a second shift with the value `$04`. Which menu item this prepares is not stated by the dump. | | `SubCC195` | `$C195` | Loads a byte into the shift register, sets the overflow flag to `$FF` and points the VRAM write cursor at the second nametable. It is the tail of the display path for one of the menu entries, but the entry is not identified. | | `SubCC322` | `$C322` | Rotates A through all eight bit positions, stores the eight results in `$00`-`$07`, then rotates A again and compares it against those eight bytes. It is an eight-byte comparison or checksum, but the dump does not say what it checks. | | `SubCC339` | `$C339` | Enables the PPU's NMI, clears the character index, takes the return address, and then runs into the command tables at `$C342` — the tracer split the routine there, so the rest of its body is filed as data. Its role is the "read the ports" menu entry, but the dump does not name the menu. | | `SubCC3F9` | `$C3F9` | Sets the PPUCTRL shadow to `$10`, clears the row and column counters, sets the page index to `$0A` and the menu index to `$02`, then enters the bit-shift helper. It is a menu-entry initialiser, but which menu is not identified. | | `SubCCD21` | `$CD21` | Sets X to `$FF` when A is non-zero and to `$00` when it is zero — a two-valued flag. The dump does not say what the flag is used for. | | `SubCCE31` | `$CE31` | A single `ldx #$08` — a loop count on its own. The cartridge gives it nothing else to be named after. | | `SubCCE45` | `$CE45` | A single `ldx #$08` — a loop count on its own, like `$CE31`. | | `SubCCF3B` | `$CF3B` | Calls the return-address helper and is followed by inline data. Its role is the tail of one of the display paths. | The other 18 entry points are named: `ShiftBitIntoAccumulator`, `SelectNametable0`, `SelectNametable1`, `RenderScreen`, `EnableNmi`, `GetReturnAddress`, `IncrementCounter`, `DrawTextRow`, `StrobeController`, `TestLowTwoBits`, `FetchReturnAddress`, `DrawGlyph`, `ReadCharacterData`, `HangForever`, `SetPpuAddress`, `ClearIndex`, `ResetPpuAddress` and `SetNmiEnableBit`.