# Changelog A running log of every change made in this repository. It covers **all** of the projects - the promoted tier and the `_wip/` tier alike - and it is also the staging area for the next commit. ## How this file is used 1. **As work lands**, a line goes into **Pending**: the project it touches and what changed. Nothing else is needed - the entry is the record. 2. **At commit time**, the commit's message is written *from* the entries in **Pending**. The entries are the summary of what the commit contains, so the message and the log cannot drift apart. 3. **After the commit**, the entries move to **History** under the commit they went in with, and **Pending** starts again empty. **Pending** - the list of changes to be put in the next git commit. **History** - the running log of the commits that have already been made. **The milestone index** at the end - the per-project pass history: which of the four milestones each project has reached, and when. That part is a status table rather than a log, and it is what `PROMPTS.md` asks for when a project reaches a milestone. ## Pending - **Entry-point naming - `NROM/Pac-Man (U)`, `NROM/Pac-Man (Tengen) (U)`, `NROM/Pac-Man (USA) (Namco)`, `NROM/Joypad Test Cartridge (U)`, `NROM/NES PowerPad Test Card (U)`, `NROM/Port Test Cartridge (U)`, `NROM/Xevious (U)`, `NROM/Pinball (JU)`, `MAPPER8/Mermaids of Atlantis (U)`, `NROM/Wild Gunman (U)` and `NROM/Baseball (UE)`** - **287** of the **346** address-derived entry points (`SubC` / `Sub_`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name: **54** in Pac-Man (U) (67 -> 13 left), **53** in Pac-Man (Tengen) (U) (66 -> 13 left), **41** in Pac-Man (USA) (Namco) (45 -> 4 left), **33** in Joypad Test Cartridge (U) (48 -> 15 left), **47** in NES PowerPad Test Card (U) (48 -> 1 left), **18** in Port Test Cartridge (U) (27 -> 9 left), **13** in Xevious (U) (15 -> 2 left), **12** in Pinball (JU) (14 -> 2 left), and **all** of Mermaids of Atlantis (U) (12 -> 0), Wild Gunman (U) (3 -> 0) and Baseball (UE) (1 -> 0). The three Pac-Man dumps are the same game in three revisions, so the names were worked out once and carried across: the field and the actors (`ResetPlayField`, `StartNewGame`, `DispatchGameStateHandler`, `DrawMazeGraphics`, `WriteMazeAttributeTable`, `DrawMazeCorridorRows`, `SelectNametable0`/`1`), the ghosts (`UpdateGhostTarget`, `ShiftGhostPositions`, `SelectGhostTargetTile`, `DispatchGhostBehaviourHandler`, `DispatchGhostStateHandler`, `DispatchGhostSpriteHandler`, `UpdateGhostSprites`, `CollectGhostTurnCandidates`, `ComputeGhostTurnTarget`, `UpdateGhostModeTimer`), the player (`SetPlayerDirection`, `UpdatePlayerDirectionFromInput`, `AdvancePacManAnimation`, `UpdatePlayerPosition`, `UpdateMazeTiles`, `UpdatePacManSpriteFrame`, `UpdateGhostSpriteFrame`, `UpdateGhostSpriteAttribute`), the PPU (`FlushGameplayPpuUpdates`, `ReadNearbyNametableTiles`, `UpdatePalettes`, `ClearVram`, `FillVramWithBlankTile`, `UnpackPackedTileStream`, `WriteScoreDigits`, `WriteDoubledDigitTiles`, `WritePlayerSelectText`, `BuildOamBuffer`, `ConvertPositionToTileAddress`, `ConvertDigitToAscii`), the sound engine (`InitAudio`, `ClearAudioWorkspace`, `RunLevelScript`, `UpdateAudioChannels`, `ApplyAudioCommand`, `ReadScriptParam`) and the frame services (`ColdStart`). The two test cartridges are diagnostics with almost no code, so most of what is left there is a family of near-identical per-button indicator routines whose button the dump does not reveal; the README records that instead of guessing. Port Test Cartridge is a menu-driven diagnostic whose menu roles the capture never executed; Xevious, Pinball, Mermaids of Atlantis, Wild Gunman and Baseball keep one or two entries each, recorded with the reason. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it, and the audit comments of the touched lines were re-aligned to column 35. **Residue: 59 entry points remain address-derived** (13 / 13 / 4 / 15 / 1 / 9 / 2 / 2 / 0 / 0 / 0) and are recorded in each project `README.md` with the reason for each group. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** in all eleven project folders. - **Entry-point naming - `NROM/Ice Hockey (U)`, `NROM/Chubby Cherub (U)`, `NROM/Raid on Bungeling Bay (U)`, `NROM/Raid on Bungeling Bay (U) [a2]` and `NROM/Elevator Action (U)`** - **1,127** of the **1,224** address-derived entry points (`SubC` / `Sub_`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name: **140** in Ice Hockey (219 -> 79 left), **205** in Chubby Cherub (223 -> 18 left), **223** in Raid on Bungeling Bay (223 -> 0 left), **220** in Raid on Bungeling Bay [a2] (220 -> 0 left) and **339** in Elevator Action (339 -> 0 left). The names come from what the ROM shows each routine does. Ice Hockey: the frame services (`ClearGameMemory`, `UploadPaletteToPpu`, `DecompressScreenData`, `PollControllerButtons`, `DispatchThroughPointerTable`, `WriteSpriteToOam`), the player (`MovePlayerLeft`/`Right`/`Up`/`Down`, `ComputePlayerFacing`, `RemapPlayerRoleFromTableA`-`D`, `SetPlayerGoalieRole`, `ApplyPlayerMoveDeltas`) and the sound engine (`UpdateSoundEngine`, `StartPulse1SoundEffect`, `StartTriangleSoundEffect`, `ProcessSoundCommands`, `PlaySoundEffect`). Chubby Cherub: the state machine (`ResetPlayerState`, `ResetStageState`, `DispatchGameState`, `UpdateGameFrame`, `UpdateGameplay`, `UpdateEnemies`), the screen writers (`ClearNametable`, `WriteTextToNametable`, `WriteHudDigit`, `DrawReadyRow`, `DrawGameOverRow`, `StartTitleScreen`) and the enemies (`UpdateEnemyObjects`, `UpdateEnemyPositions`, `DispatchEnemyBehaviour`, `ComputeEnemyScreenPosition`). Raid on Bungeling Bay: the whole radar/factory/fuel vocabulary of the Konami game (`UpdatePseudoRandomNumber`, `UploadPaletteToPpu`, `DrawSpritePart`, `GetRadarScreenAddress`, `UpdateFuelGauge`, `ComputeFuelLevel`, `MoveEnemyTowardsTarget`, `DestroyEnemy`, `DispatchEnemyRoutine17`/`19`/`1B`, `LoadTargetSpawnRecord`); the [a2] revision shares the same code and takes the same names. Elevator Action: the elevator, the floors and the passengers (`UpdateElevatorState`, `ClearElevatorState`, `BackupElevatorState`, `ComputeElevatorShaftOffset`, `FindFreeFloor`, `CheckFloorOccupancy`, `MoveSpriteToDestination`, `MoveSpriteBySpeed`, `IsSpriteActive`, `RunVramDrawScript`, `PlaySoundEffect`). Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **Residue: 97 entry points remain address-derived** (79 in Ice Hockey, 18 in Chubby Cherub) and are recorded in each project `README.md` with the reason for each group - in Ice Hockey they are the steps of the player/puck contact simulation and the rink-zone drawing that read 8.8 fixed-point zone coordinates, and in Chubby Cherub they are the twelve sound-effect selectors that choose a pair of pointers into the one block whose layout is not established, plus a handful of reset variants and sprite gates. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** in all five project folders. - **Entry-point naming - `NROM/Lode Runner (U)`, `NROM/Ice Climber (JE)`, `NROM/Ice Climber (U)`, `NROM/Hydlide (U)` and `NROM/Stack-Up (JU)`** - **669** of the **957** address-derived entry points (`SubC` / `Sub_`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name: **168** in Lode Runner (197 -> 29 left), **138** in each Ice Climber (191 -> 53 left), **124** in Hydlide (190 -> 66 left) and **101** in Stack-Up (188 -> 87 left). The names come from what the ROM shows each routine does. Lode Runner: the PPU helpers (`WritePpuData`, `ReadPpuData`, `SetPpuAddress`, `UploadSprites`, `EnablePpuNmi`/`DisablePpuNmi`), the controller read (`ReadControllers`, `AccumulateControllerBit`), the sound (`PlayPulse1Tone`, `PlayTwoToneBlip`, `StopPulse2AndPlayBlip`), the screen layout and object drawing (`LoadScreenLayoutPointers`, `SetScreenLayoutPointer`, `TestLayoutBit`, `DrawObjectTile`, `WriteLevelTilePair`) and the guard AI (`UpdateEnemyPosition`, `StepEnemyDirection0`/`1`/`2`, `SaveEnemyState`, `LoadEnemyState`, `CheckEnemyAlignment`). Ice Climber: the whole sound engine (`UpdateSoundEngine`, `UpdateSoundPulse1`/`Pulse2`/`Noise`, `SetPulse1Tone`, `SetPulse2Tone`, `GetSoundEngineValue`, `CheckSoundIdle`), the objects on the ice (`UpdateObject`, `UpdateObjectByType`, `SetObjectTile`, `SpawnObject`, `DrawObjectSprite`, `MoveObjectUp`) and the player (`MovePlayer`, `MoveEnemy`, `CheckPlayerCollision`). Hydlide: the PPU and sprite plumbing (`InitHardware`, `SetPpuAddress`, `UploadSprites`, `ClearSpriteBuffer`, `WriteOamSprite`, `DrawItemIconSprite`), the music and the sound effects (`UpdateMusic`, `PlayPulseNote`, `PlayBassNote`, `PlaySoundEffect`, `PlayNoiseEffect`) and the monster and door handling (`CallMonsterHitReaction`, `CallObjectTypeHandler`, `OpenDoor`, `CloseDoor`, `DrawMonsterSprite`). Stack-Up: the VRAM writers (`SetVramBase20`/`28`, `SetVramWritePointer`, `WriteVramByte`, `WriteAttributeByte`, `AdvanceVramRow`), the board and the blocks (`UpdateGame`, `GetBlockShapeIndex`, `GetLevelLayoutPointer`, `UpdateFallingBlocks`, `RunBlockFallScript`, `SpawnBlock`) and the sound engine (`UpdateSoundEngine`, `PlayNextSoundNote`, `StartSoundSequence`). Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it, and each renamed routine's `Global:` line was given a sentence that says what the routine is for in the game's own terms. **Residue: 288 entry points remain address-derived** (29 / 53 / 53 / 66 / 87) and are recorded in each project `README.md` with the reason - they are the one-instruction fragments of the object, entity and sound state machines, helpers that load a constant or select a per-object slot, for which the dump does not reveal a role that would distinguish them from their neighbours. **No instruction and no byte value changed** - labels and the comments that name them only; the audit comments of the lines that were touched were re-aligned to column 35, and the column histogram is unchanged from before the pass. `make` still prints **Byte-identical ROM verified.** in all five project folders. - **Entry-point naming - `NROM/Donkey Kong Jr. Math (U)`** - all **295** address-derived entry points (`SubC`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name, together with the 29 `SubC_Loop`/`_OnX` jump targets and their 676 `_Block` children; **0 remain address-derived**. The names come from what the ROM shows each routine does: `InitializeGameplayScreen` for the one-shot NMI path that enables the APU frame IRQ, sets the first OAM sprite and queues the `$F544` nametable draw list, `SelectAnswerChoiceVramPointer` for the routine that indexes the `$C196`/`$C197` pointer pair by the drill state to pick the answer row, `UpdatePlayerObject` / `MovePlayerObjectRight` / `MovePlayerObjectLeft` / `AdvancePlayerObjectRightEdge` / `RetreatPlayerObjectLeftEdge` / `CheckPlayerObjectCollision` for the player's position and hit box, `SavePlayerButtonState` and `GetStateCodeZero`…`GetStateCodeThree` for the answer-choice index (the answer buttons are A/B/SELECT/START, `BtnState` 1-4, exactly as `CheckAnswer2` shows), `InitScorePopup` / `MoveScorePopup` / `DrawScoreSprites` / `FindFreeScoreSpriteSlot` / `PlayAnswerSound` for the score popup, `DrawTextString` / `DrawTextStringRows` / `QueueTextFieldRecord` / `DrawTextFieldsByMask` for the packed text writer that feeds the `$0331` VRAM byte queue, `PlayQueuedSoundSequence` for the routine that indexes `MusicSequencePointerTable` with the high nibble of a queued sound command and takes the low nibble as the channel, `UpdateApuChannels` / `WriteApuRegistersByMask` / `WritePulse1ControlAndSweep` / `WritePulse2ControlAndSweep` / `SilenceTriangleChannel` / `GetNoisePeriodFromTable` for the APU writer at `$FA5C-$FE51`, `ReadControllerPorts` / `ReadControllerBits` / `UpdateControllerShiftRegister` for the `$4016` latch and shift, and `TickCountdownTimers` for the ten-frame tick at `Zp30`/`Zp31`. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it, and each renamed routine's `Global:` line was given a sentence that says what the routine is for in the game's own terms (the auto-generated text only listed the fields it touched). **Residue: 0 entry points remain address-derived**; the project `README.md` records the naming evidence per area. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Sqoon (U)`** - all **271** address-derived entry points (`Sub_` / `SubC`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name; **0 remain address-derived**. The names come from what the ROM shows each routine does: `DispatchPhaseHandler` for the routine that reads a 16-bit handler address out of `PhaseHandlerPointerTable` ($83C2), indexed by the phase counter, and jumps through it, `PhaseTitleHandler` / `PhaseLevelInitHandler` / `PhasePaletteLoadHandler` / `PhaseDelayHandlerOne`/`Two` / `PhaseWaitForButton` / `PhaseSetVramFlagHandler` for the nine phase handlers it dispatches to, `AdvancePhaseIndex` / `AdvancePhaseSubIndex` / `ResetPhaseSubIndex` for the two counters those handlers use, `WritePlayerToOamBuffer` for the routine that copies the ship's position into the OAM shadow at `$0200`/`$0300` (it is called from every sprite-drawing routine with a different tile offset), `SetPlayerSpriteLeftEdge` / `SetPlayerSpriteRightEdge` / `SetPlayerSpriteBaseY` / `InitPlayerSpriteState` / `InitPlayerGameState` for the ship's own state, the pod family (`UpdatePodPosition`, `UpdatePodVelocity`, `UpdatePodSubPixelPosition`, `UpdatePodMovement`, `CheckPodOnScreen`, `CheckPodFullyOnScreen`, `CheckPodNearPlayer`, `CheckPodCollision`, `DecrementPodLife`, `DecrementPodLifeTypeOne`…`Fourteen`, `MovePodUp`/`Down`/`Left`/`Right`, `CanPodShoot`, `FirePodShot`, `RegisterPodHit`, `SpawnNewPod`, `RestartPodGrowth`) for the `$0700`-per-slot pod records, the shot family (`SpawnPlayerShot`, `MovePlayerShot`, `RetirePlayerShot`, `SpawnPodShot`, `UpdatePodShots`, `CheckShotOnScreen`, `FindFreeShotSlot`) for the `$051B`/`$0577` shot records, `LoadVramScript` / `QueueVramAddress` / `QueueVramTileBytes` / `DrawVramScript` / `DrawVramText` / `ClearNametableTiles` / `FlushVramByteQueue` for the sprite-definition-string writer, `PlaySoundEffectById` / `PlaySoundEffect` / `RunSoundScript` / `ReadSoundScriptByte` / `WriteSoundChannelRegisters` / `InitSoundChannelScript` for the sound engine, `ProcessSpriteCommand`…`ProcessSpriteCommandFour` / `WriteSpriteBytesToPpu` for the byte-coded sprite-definition lists, and `ReadControllerButtons` / `StartSpriteDma` / `TickCountdownTimers` for the frame services. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **Residue: 0 entry points remain address-derived**; the project `README.md` records the naming evidence per area and notes separately that the tracer's `IrqHandler_Loop*` / `IrqHandler_OnNonzero*` prefixes on several hundred internal branch targets are misleading (those labels are in the main game code, not in the IRQ handler) but are not address-derived, so they are outside the scope of this pass. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/CIRCUS`** - all **258** address-derived entry points (`SubC`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name, together with the 586 internal branch-target children (`SubC_Loop`, `SubC_OnNonzero`, …), which were re-based onto their parent's new name; **0 remain address-derived**. The names come from what the ROM shows each routine does: `DispatchHandlerPointerTable` for the game's computed dispatcher, which pops the return address of its own `jsr` off the stack, uses it as the base of the inline pointer table that follows the call and jumps through the 16-bit entry the index selects - that is how `MainStateHandlerPointerTable`, `SecondaryStateHandlerPointerTable`, `ActorUpdateHandlerPointerTable` and `ActorActionHandlerPointerTable` are all reached - `RunScreenScript` / `RunScreenScriptRows` / `SetPpuVramAddress` / `WriteAttributeRun` / `WriteAttributeByte` for the byte-coded screen script (`$FD` = write N bytes, `$FE` = new VRAM address, `$FF` = end), `LoadActorPosition` / `SaveActorCoordinates` / `StoreActorBox` / `ComputeBoxOverlap` / `CheckActorBoxOverlap`…`Five` / `ComputeActorFacing` / `PackCoordinateBytes` / `ApplyCoordinateMask` / `ResetActorSlots` for the five actor slots and their hit boxes, `UpdateRingActor` / `SetRingCoordinate` / `CheckRingHit` / `IsRingInPlay` for the ring, `StartOamSpriteList` / `ProcessOamSpriteList` / `QueueOamSprite` / `ReadObjectScriptByte` / `AdvanceObjectScript` for the `$0700` OAM shadow and its byte script, `PlaySoundEffectById` / `PlaySoundEffectPair` / `UpdateSoundChannels` / `WriteSoundChannelRegisters` for the sound engine, and `ReadControllerButtons` / `AreAnyButtonsPressed` / `WritePpuCtrl` / `SetPpuCtrlBit2` / `ResetScrollPosition` for the input and PPU services. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **Residue: 0 entry points remain address-derived**; the project `README.md` records the naming evidence per area. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Millipede (U)`** - all **225** address-derived entry points (`SubC`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name, together with the 600 internal branch-target children (`SubC_Loop`, `SubC_OnZero`, …), which were re-based onto their parent's new name, and the 561 tracer-generated labels whose name still ended in a target address (`__`), which had that tail removed; **0 remain address-derived**. The names come from what the ROM shows each routine does: `StartNewGame` / `BeginGameplay` / `StartStage` / `SelectGameMode` / `SelectGameSlot` / `SwapPlayerSlot` for the game start (the six bytes of `ObjectInitParameterTable` are copied into the object block at `$03DF` and the millipede/spider/mushroom/bee slots at `$03D0`, `$03E6`, `$03E9`, `$03F2` and `$03F5` are seeded), `UpdateMillipedeSegment` / `SaveMillipedeSegment` / `MoveMillipedeSegment` / `UpdateMillipedePointer` / `DrawMillipedeSegment` / `DrawMillipedeHead` / `DrawMillipedeBody` / `SetMillipedeHeadTile` / `ClearMillipedeSlot` / `IsMillipedeActive` for the millipede, `SpawnSpider` / `DrawSpiderSprite` / `DrawMushroomSprite` / `WriteMushroomTile` / `CheckMushroomCollision` / `DrawBeeSprite` / `IsEnemySlotFree` / `StartEnemySpawnTimer` / `StartBeeSpawnTimer` for the enemies, `AdvanceObjectTimer` / `UpdateObjectDirection` / `GetObjectDirection` / `SetObjectDirectionBit` / `ApplyDirectionMask` / `FindFreeObjectSlot` / `ScanObjectSlots` for the object slots and the eight single-bit masks of `DirectionBitMaskTable`, `RunScreenScript` / `ReadScreenScriptRecord` / `CopyScreenScriptData` / `ClearNametableTiles` / `FlushVramWriteQueue` for the screen-script writer, `UpdateHudTile` / `WriteHudTileByte` / `WriteHudTileIndex` / `ComputeTileIndex` for the HUD built from `HudSpriteTemplate` and `OamSpriteYTable`, and `InitSoundEffect`…`InitSoundEffectFour` / `UpdateSoundEffectStep` / `ComputeNotePeriod` / `GetNoisePeriodFromTable` / `SetPulse1Timer` / `SetTriangleTimer` / `SetNoiseTimer` / `SetDmcTimer` for the sound engine. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **Residue: 0 entry points remain address-derived**; the project `README.md` records the naming evidence per area. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Tag Team Wrestling (U)`** - every one of the 48 data blocks in the 32 KB PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 50 labels were placed with `name_data_region.py` (base `$8000`) and eleven earlier labels were renamed: `RingEntrancePointerTable` became `RingEntranceDpcmPointerTable`, `UnreachedRomData` became `RingEntranceDpcmSampleData2`, `WrestlerNameLengthTable` became `MessageHoldFrameTable`, `PpuBitMaskTable` became `BitMaskByBitIndexTable`, `WrestlerHandlerPointerTable` became `WrestlerMoveHandlerPointerTable`, `WrestlerMovementData` became `WrestlerMoveParameterData`, `LevelGraphicData` became `TitleScreenTextScripts`, `LevelTilePatternData` became `RingPalette`, `LevelObjectPlacementData` became `UnreachedRomData`, `UnreachedRomData2` was removed (its address `$A9FD` now carries `GameModeDispatchPointerTable`) and `WrestlerParameterTable` became `WrestlerSlotParameterTable`. The finding that fixes the layouts is the game's nametable-script writer: `Sub_FD3A` (`$FD3A` -> `$FF65`) takes a script address in X:Y, reads a two-byte nametable address, then writes every following byte as a tile index of (byte - `$20`), with `$1F` meaning "next nametable row" and `$FF` ending the script - which is what makes `$AB05-$AC7E` a run of title-screen drawing scripts and what turns `$B431-$B697` into two NES palettes plus ten ranking title scripts plus six belt pictures. The other names are read out of the consuming code: `MessageHoldFrameTable` ($9DC5), `WrestlerMoveDescriptorPointerTable`/`WrestlerMoveSpeedTable`/`WrestlerMoveSpeedTable2` ($A409/$A429/$A431), `WrestlerSlotParameterTable` ($AE01), `MatchScoreTileTable`/`ScoreboardPanelScript` ($B017/$B031), `WrestlerPinCountTable` ($B224), `RankingScreenPaletteTable`/`RankingScreenAltPaletteTable` ($B431/$B441), `RankingRegionLabelTable` ($B97B), `RankingScreenGraphicScriptTable` ($B9E2), `WrestlerPoseHandlerTable`/`WrestlerPoseHandlerTable2`/`WrestlerPoseHandlerTable3` ($BFF3/$C385/$C6B4), `PlayerXByGameStateTable` ($C6A7), `WrestlerMoveStateTable` ($C7D1), `WrestlerSpriteTileTable` ($D449), `WrestlerStartPositionTable` ($D7AA), `Wrestler1PoseHandlerTable`/`Wrestler2PoseHandlerTable` ($DA82/$DB21), `AttackPromptTileRow` ($DC5B, the nine raw tiles that spell " ATTACK! "), `SoundEffectMusicData`/`SoundMusicHandlerPointerTable` ($E050/$E18C), `RingHudTileRowData` ($E256), `RingPostTileRowData` ($E669), `RingSpriteNametableAddressTable`/`RingSpriteTileAttributeTable` ($E753/$E773), `LastSecondsMessageScripts` ($E956), `WrestlerFacingTable` ($EBDD), `WrestlerMoveFlagTable`/`WrestlerMoveParameterTable` ($ED2A/$ED3D), `SoundEffectHandlerPointerTable` ($EE75), `NotePitchHandlerAddressTable` ($EEB7), `SpriteTileIndexList` ($EF2C), `TimerLabelTileRow` ($F119, the eight raw tiles that spell "TIME = ") and `SoundSequencePointerTable`/`SoundEffectParameterTable` ($F2D6/$F2EF). The blocks nothing reads - `$A281-$A302`, `$B2E5-$B301` (a dead "!WINNER IS RICKY FIGHTERS" copy), `$D9C8-$D9D6` (a dead " TIME OVER " copy), `$E7F0-$E82F`, `$E893-$E8B2` and `$EF89-$EF8C` - are recorded in the inventory as unreferenced data with their ROM provenance, and `$DE38-$DEBB` as code the tracer could not reach. The audit trail of the first twelve data blocks carried PRG offsets numbered from `$C000` instead of `$8000`; they now agree with the rest of the file. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `CNROM/World Class Track Meet (U)`** - every one of the 55 data blocks in the 32 KB PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 37 labels were placed with `name_data_region.py` (base `$8000`). The names are read out of the consuming code: `EventMusicTrackTable` ($83EB, the four track numbers the menu's event choice hands to `AudioChannelLoop`), `EventTaskPointerTable` ($859B, the twelve 16-bit addresses of the event-task routines - the tracer cannot follow the pushed-address dispatch, which is why `$8DA5-$9140` is still filed as data), `SpriteSlotByObjectTypeTable` ($8717), `EventSpeedStepTable`/`EventSpeedStepTable2` ($95D8/$95F8), `RunnerLaneIndexTable` ($964C, 8 groups of 16), `EventScreenPaletteTable` ($96E5, thirteen four-byte records), `RunnerSpriteTileList` ($989A), `RunnerSpriteParameterTable` ($9AEF, thirteen four-byte records), `ObjectSpriteOffsetTable` ($A547), `EventObjectPositionTable` ($A5CC, nine eight-byte records of four coordinate pairs), `RunnerRankTable` ($A690, 96 place values), `RunnerSpriteTileAttributeTable` ($A880), `DisplayAddressPairTable` ($A9C7), `RunnerNumberTable`/`RunnerNametableAddressTable` ($AC66/$AC8A), `ScoreDigitTileTable`/`ScoreDigitNametableAddressTable` ($ADE8/$ADFE), `NametableAddressTable` ($B02A), `AudioChannelDispatchPointerTable`/`AudioNoteHandlerPointerTable` ($BFC4/$BFE4, each entry one less than the handler address because the sound engine pushes it and an `rts` lands on it), `LaneSpriteParameterTable`/`LaneSpriteParameterTable2` ($BDC3/$BE43), `AudioChannelParameterTable` ($C434), `ObjectFlagTable` ($D067), `ControllerButtonMaskTable` ($D2BA), `ControllerActionTable` ($D317), `EventStartParameterTable` ($D437), `RunnerLaneSpriteParameterTable` ($D8BA), `LaneAttributeTable` ($D900), `EventStatePointerTable`/`LaneProgressIncrementTable` ($D9CF/$DDE9), `EventFrameSchedulerPointerTable` ($E5F2), `EventGoalParameterTable` ($EF40), `EventPaletteTable` ($EFFC), `EventDispatchPointerTable` ($F796, the five task entry points the main loop jumps to through `jmp ($001F)`) and `PaletteUpdateNametableAddressTable` ($FA95). The thirteen blocks that decode as long runs of 6502 instructions (`$8972-$8A07`, `$8A13-$8ABB`, `$8B5A-$8D17`, `$8D23-$8D41`, `$8DA5-$9140`, `$91B4-$91CE`, `$91FF-$9215`, `$9235-$924B`, `$9DCF-$9DFD`, `$9E31-$9E64`, `$9F33-$A02A`, `$A034-$A04A`, `$D11C-$D13A` and `$E2A3-$E2B1`) are recorded as **code the tracer could not reach** rather than as tables, and `$95D8`/`$95F8`, `$9C7F-$9C88`, `$F025-$F6FC`, `$F8E2-$F8E5` and `$FC78-$FFFF` are recorded as unreferenced data with their ROM provenance. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Wild Gunman (U)`** - every one of the 23 data blocks in the 16 KB PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, structure and its consumer. The finding is that most of Wild Gunman's "data" blocks are in fact **code the Pass 1 tracer never reached** - the tracer reached only 49.3 % of the PRG, so `$C5F8-$CF00`, `$DEED-$F371` and `$F77C-$FFF3` (the whole sound engine) decode as instructions, and the earlier `SpritePositionTable` name on `$F77C` was wrong and has been removed. `$C844` is now labelled `InitialSpriteDataTable` (the twelve bytes of the initial OAM table, three sprite entries of Y, tile, attribute and X, copied by `lda $C844,x` at `$C056`), `$C6AC` was renamed `UnreachedTileList` (84 CHR tile indices that no instruction in this dump reads), and `NoteVolumeTable` ($F3EF) and `VolumeEnvelopeTable` ($F48F) are recorded for what their bytes are, with the note that the sound routine that would consume them is itself unreached code. The blocks nothing reads - `$C12A-$C12F`, `$C20C`, `$DEDD-$DEEC`, `$F3DB-$F3E2` and `$F423-$F425` - are recorded as unreferenced data with their ROM provenance. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Tennis (JU)`** - the game's data is stored inline, between the routines, so the project `README.md` now carries a **Pass 3 data-table inventory** listing every data region with its address range, size, name, structure and the instruction that reads it. Four earlier names were wrong and have been corrected: `ServingPlayerLookupTable` became `PlayerActionByAnimationStateTable` ($DD7C, the action type the player performs in each animation state), `ComputerActionProbabilityTable` became `ComputerActionThresholdTable` ($DE01, the per-player odds the computer acts at), `GraphicsTable_E695` became `DeuceMessageTileData` (the tile row that spells out the deuce announcement) and the `CourtBoundaryTable` label, which sat on the code at `$E5AD`, has been moved to the eight bytes it actually describes at `$E5F0`. The rest of the inventory names the game's own tables: `InitialMatchObjectStateData` ($C847, twelve 16-byte records), `PlayerActionAudioRequestTable` ($C965), `MatchResultAudioRequestTable` ($CA22), `CourtObjectSpawnParameterTable` ($CC7E), `BallStateTransitionParameterTable` ($CEB1), `PlayerActionAndTargetOffsetTable` ($CFA2), `PlayerPoseRacketXOffsetTable` ($D0B1), `PlayerPoseAnimationSetA` ($D31C), `NetCollisionThresholdTable` ($D735), `BallLandingBoundaryTable` ($D780), `RacketHitTrajectoryTable` ($D889), `RacketAimAdjustmentTable` ($DA30), `Player2CourtPositionTable` ($DAA2), `ScoreStateTransitionTable` ($DD11), `CourtObjectMetaspriteData` ($E02B), `ScoreTextPpuAddressTable` ($E62A), `UmpireMessageAudioPriorityTable` ($E63C), `ScoreIncrementLookupTable` ($E804), `UmpireMessageTileData` ($EA82), `MatchScreenTextData` ($EB92) and the APU and instrument tables at `$F8FF` and after. The two-byte `EngineTable_XXXX` and `PhysicsTable_XXXX` labels are single instructions the tracer did not assemble, not tables, and the inventory says so. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Popeye (JU) (PRG 0)` and `NROM/Popeye (JU) (PRG 1)`** - all **297** address-derived entry points (`SubC`, every `jsr`/`jmp` target that carried no semantic name) in each of the two Popeye banks now carry a ROM-specific name; **0 remain address-derived**. The names come from what the ROM shows each routine does: `InitPlayerState` for the reset-time player record at `$0500`, `ResetVramUpdateQueue` / `QueueVramBlockFromLevelTable` / `ApplyVramUpdateStream` for the VRAM command queue at `$0330`, `UploadOamViaDma` for the `$0200` sprite shadow, `ClearOamBuffer`, `WriteOamEntries` / `AddOamSprite` / `ReserveSpriteSlots` for the OAM builders, the five NMI state handlers named for the state they run (`RunGameplayScreen`, `RunTitleScreen`, `RunAttractDemo`, `RunEnemyEncounterUpdate`, `RunStageTransitionUpdate`), the level setup (`BeginLevelLoad`, `LoadLevelLayout`, `LoadLevelPalette`, `BuildTitleScreenOne`/`Two`, `BuildLevelScreenZero`/`One`/`Two`, `StartNextLevel`), the player (`UpdatePlayerMovement`, `StartPlayerThrow`, `StartCarryAnimation`, `FacePlayerRight`/`Left`), the screen state machine (`RunScreenStateMachine`, `HandleScreenState01`…`HandleScreenState80`), the sound engine (`UpdateSoundEngine`, `AdvanceSoundSequence`, `AdvanceSoundEffectSequence`, `SetPulse1ControlAndNote`, `WritePulse1NoteTimer`, `WriteTriangleNoteTimer`, `LookUpNotePeriod`) and the hardware helpers (`ReadControllers`, `PollJoypadButtonEdge`, `ToggleAttractMode`, `DisableRendering`, `ClearScreenAndSpritePalettes`). PRG 1 carries the same code with a few addresses shifted by +3 to +6 bytes in the `$C7xx`-`$CFxx` range, so the names were worked out on PRG 0 and transferred routine by routine. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/1942 (JU)`** - all **375** address-derived entry points (`Sub_xxxx` / `SubCxxxx`) now carry a ROM-specific name; **0 remain address-derived**. The names come from what the ROM shows each routine does: the deferred-call machinery (`QueueDeferredCall`, `RunDeferredCallQueue`, `EnableNmi`), the VRAM command queue at `$078F` (`RunVramCommandQueue`, `BuildVramColumnQueue`, `QueueColumnTile`, `ApplyColumnAttribute`, `ComputeColumnAttribute`), the scrolling background (`UpdateBackgroundScroll`, `ScrollBackgroundLeft`/`Right`/`Up`, `ResetNametableBuffer`, `ApplyScrollWaveLeft`…`Down`), the player and its shots (`InitPlayerState`, `InitPlayerHitbox`, `TestPlayerHitbox`, `CheckPlayerHitEnemy`, `CheckPlayerShotCollision`), the object system (`InitObjectTables`, `SpawnObject`, `DespawnObject`, `UpdateObjectState`, `AdvanceObjectState`), the enemy spawn table at `$A2F2` (`DispatchEnemySpawnCommand`, `SpawnEnemyObject`, `DespawnEnemyObject`, `SetEnemyObjectFlag`/`X`/`Y`) and the per-enemy-type behaviour table at `$EFF2`/`$FF09` (`InitEnemyType0`…`InitEnemyType9`, `InitDefaultEnemyType`, `RunEnemyTypeBehaviour`, `LoadEnemyPatternData`…`Alt3`, `CheckEnemyCollisions`, `TriggerEnemyDrop`). Each renamed routine's `Global:` line was given a sentence that says what the routine is for in the game's own terms. The `SubCxxxx` labels that survive (`ColumnClassTable`, `EnemyPatternDataPart1`…, `EnemyMovementDataPart1`…) sit on **data** blocks, not on code, and are named for the block they head. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Soccer (JU)`** - **311** of the **354** address-derived entry points (`Sub_xxxx` / `SubCxxxx`) now carry a ROM-specific name: `StartNewMatch` / `InitMatchState` / `ResetMatchState` for the match setup, `UpdateGameFrame` for the per-frame step, `ReadControllers`, `CallLevelHandler` / `CallPlayerHandler` for the two dispatch tables, the VRAM writers (`ApplyVramUpdateStream`, `WriteVramBlockFromTable`, `ClearScreenAndPalettes`, `ClearOamBuffer`, `HideOamSprites`), the player and ball (`UpdatePlayerSprite`, `UpdatePlayerPosition`, `MovePlayer`, `SetBallPosition`, `GetFieldTile`, `LoadLevelTileRow`), the score and HUD (`UpdateScore`, `SetScoreDisplay`, `SetScoreDigits`) and the sound engine (`UpdateSoundEngine`, `AdvanceMusicSequence`, `AdvanceSoundEffectSequence`, `SetPulse1ControlAndNote`, `SetPulse2Timer`, `SetTriangleTimer`, `LookUpNotePeriod`). Each renamed routine's `Global:` line was given a sentence that says what the routine is for in the game's own terms. **Residue: 44 entry points remain address-derived** and are recorded in the project `README.md` with the reason for each group - 28 of them sit on data blocks the tracer split because their bytes decode as instructions, and the other 16 are player-record and field-state helpers whose RAM field the dump does not identify. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/CIRCUS`** - all 74 data blocks the tracer emitted are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 57 labels were placed with `name_data_region.py` (base `$C000`). The finding that fixes the layouts is the game's computed dispatcher: `DispatchHandlerPointerTable` ($C4CD, then called `SubCC4CD`) pops the return address of its own `jsr` off the stack and uses it as the base of the inline pointer table that follows the call, so the four inline tables are 16-bit handler tables - `MainStateHandlerPointerTable` ($C0D7, six entries), `SecondaryStateHandlerPointerTable` ($C1D8, nine entries - the tracer cut it at $C1E4 because the pointer bytes `$C3` decode as an undocumented `DCP` opcode), `ActorUpdateHandlerPointerTable` ($C9D3, five entries) and `ActorActionHandlerPointerTable` ($D80C, four entries - the tracer emitted the last two as instructions, `sta $51D8` and `cmp $D95F,y`, because the pointer bytes decode as opcodes). The other names are read out of the consuming code: `ActorPpuControlTable` ($C4A6, four PPUCTRL values picked by the actor index), `ActorOamSpriteTemplate`/`ActorSpriteYTable` ($C5A6/$C5AE, the two-sprite OAM template and the four actor Y positions), `MenuTextTable` ($C763, the game's own strings - "CONTINUE", "STAGE", "1 PLAYER", "KONAMI", "BY SOFTPRO"), `SecondaryStateActionTable` ($C1CC, the per-state action code), `ActorStateSizeTable` ($C1B2), `ActorTargetPositionTable` ($CF3F), `ObjectScriptPointerTable` ($D158, 54 pointers), `ObjectUpdateRoutinePointerTable` ($D2DD, 16 pointers), `SoundEffectDefinitionTable` ($CBBC, three 10-byte records), `SoundSequencePointerTable`/`SoundNotePeriodTable` ($FABB/$FAD1) and the audio, ring and object parameter tables at $D4C7, $D53D, $D7C5, $E6DF, $E7E5, $F226, $F320, $F514, $F65B, $F744 and $F76C. The blocks no instruction reads are recorded in the inventory as unreferenced data with their ROM provenance or, where the bytes decode as instructions, as **code the tracer could not reach** - the CDL capture confirms the split for the blocks the CPU touched ($C212-$C24F, $C2F5-$C302 and $E532-$E561 are flagged Code; $C763-$C819, $CFB7-$CFCD, $CCE6-$CD27 and $D670-$D713 are flagged Data). **No instruction and no byte value changed** - the `.byte` rows were only cut at the new label boundaries. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Duck Hunt (JUE)`** - all 28 data blocks the tracer emitted are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 23 labels were placed with `name_data_region.py` (base `$C000`). The dispatch mechanism is the same one the other Namco titles use: `SubCC35E` ($C35E) pops the return address of its own `jsr` and reads a 16-bit handler pointer out of the inline table that follows the call, which is why `GameModeHandlerPointerTable` ($C128, 19 entries) and `StateHandlerPointerTable` ($CCB0, three entries - the CDL marks exactly those six bytes as Data) are handler tables. The other names are read out of the consuming code: `DuckSpeedTable` ($E64B, the 24 ascending speeds the duck's setup reads), `DuckFlightParameterTable` ($E968, four 6-byte flight records), `DuckFlightPathTable` ($F300, the duck's horizontal path), `DuckSpriteXOffsetTable` ($DCC0), `DuckStateInitialValues` ($E5DF), `PlayerStateInitialValues` ($E5EB), `SoundEffectScriptTable`/`SoundEffectPointerTable` ($EC1F/$EC38), and the audio tables `TriangleNoteTable` ($F3E1, the 14 triangle periods of a phrase), `NoiseEffectTable` ($F400, five (control, period, length) records), `NoisePeriodTable` ($F6CB), `NoisePeriodTableAlt`/`NoiseControlTable` ($F731/$F761, the drum's two 48-entry tables) and `SoundInstrumentParameterTable` ($F847). The 6,297-byte block at $DAC5 is the game's graphics and sprite-description area - a 38-entry pointer table, a sprite-record region, two "opcode + operands" tile-placement scripts, the screen-layout data and a bitmap region - and the 23 tables inside it that the code reads by name are named and listed; its finer format is not established from this dump and the inventory says so. The blocks no instruction reads are recorded as unreferenced data with their ROM provenance or, where the bytes decode as instructions, as **code the tracer could not reach** - the CDL confirms the split for the blocks the CPU touched ($C5AA-$C5D8, $CD1D-$CD2C and $CD2E-$CD43 are flagged Code; $C128-$C14D, $CCB0-$CCB6, $F847-$F84C, $F859-$F862 and $F86B-$F875 are flagged Data). **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Balloon Fight (JU)`** - all 44 data blocks the tracer emitted are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 27 labels were placed with `name_data_region.py` (base `$C000`). The names are read out of the consuming code: `CartridgeIdAndInitialValues` ($C082 - the ASCII "HAL" of HAL Laboratory followed by 15 bytes of initial counters), `ObjectHandlerPointerTable`/`ObjectHandlerIndexTable` ($C3B5/$C3BF, five handler pointers and the ten indices that select them), `BalloonTileGridTable` ($CD26, nine 4-byte tables that are the 3x3 CHR tile grid of each of the four balloon graphics), `BalloonPositionTable`/`BalloonXPositionTable`/`BalloonSpriteAttributeTable`/`BalloonSpriteFlipMaskTable` ($CEAE/$CEB2/$CEB5/$CEBD), `BalloonSpawnParameterTable` ($C88B, the per-direction deltas, speeds and spawn positions), `BalloonMovementDeltaTable` ($C9E5, the four copies of (08, 08, F0, F0) and (EE, 0A, 0A, EE) that give the four diagonal steps), `LevelBalloonParameterTable` ($D10D, four parallel 5-entry tables - speed, spawn X, type and flag, one entry per level), `LevelParameterTable`/`LevelBalloonHeightTable` ($C748/$C761), `StageNametableAddressTable` ($D65C, 64 16-bit nametable addresses), `StageLayoutScript` ($D92C) and `StageGraphicScript` ($D12B), the two tile-placement scripts at $D437 and $D12B, `BalloonParameterTable` ($E5C5, the per-balloon-type parameter groups the collision code reads) and the noise tables at $F6E5. The blocks no instruction reads are recorded as unreferenced data with their ROM provenance or, where the bytes decode as instructions, as **code the tracer could not reach**; the 2,173-byte block at $DB27 is recorded with the object-record layout that is visible in it and with the note that its finer format is not established from this dump. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Donkey Kong (JU)`** - all 358 address-derived entry points (`SubC`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name: `MainGameLoop` for the endless frame loop, `InitPPURegisters` for the PPU register setup, `UpdateGameObjects` for the per-frame object step, `ClearSpritePatterns` and `HideBackgroundAndClearNametable` for the two screen-clearing steps, `LoadSpriteInitPointerToZp00` / `LoadObjectPointerToZp02AndDraw` / `LoadObjectPointerToZp00AndDraw` for the three VRAM-stream loaders, `DrawKongHitChestLeftHand` / `DrawKongHitChestRightHand` / `DrawKongHoldingBarrel` / `DrawKongPickupBarrel` / `DrawKongSideToss` for the five Kong tilemap streams, `DrawLevelBlocks` / `ClearNametable` / `DrawVramBlock` for the screen writers, `UpdateTimers` / `ReadControllers` / `UpdateSoundEngine` / `ProcessSoundCommands` / `InitSoundEffect` for the frame services, and so on. 100 of the names come from the project's own Pass 2 map `dk_full_map.tsv`, which had never been applied to them; the other 258 were read off the routine bodies and their callers. Every `; Routine:` header and every `Global:`/`Local:` line that mentioned an old name was rewritten with it. **Residue: 0 entry points remain address-derived**; the 654 internal branch labels (`SubC_`) are still address-derived and are recorded as open work in the project `README.md`. **No instruction and no byte value changed** - labels and the comments that name them only. `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Othello (U)`** - all 302 address-derived entry points (`Sub_`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name, together with the 101 `SubC` local labels of the same kind: `WaitForVBlank` for the frame spin, `SetPpuMask` / `SetPpuScrollAndControl` for the PPU writes, `ReadKeyFromQueue` for the key queue at `$0400`, `PlaySoundEffect` for the sound command, `SetCursorOnBoard` / `ComputeBoardSquareIndex` / `ComputeBoardSquareIndexAlt` for the board cursor, `SetSquareOwner` / `ClearSquareOwner` / `PrepareSquareLookup` for the square array at `ZpA5`, `FindFlippableSquare` for the eight-direction move scan, `WriteBoardSquareToVram` for the nametable writer, `DrawBoardBackground` for the board frame and `DrawScoreDigit` for the score row. **Residue: 0 entry points remain address-derived**; the 649 internal branch labels (`Sub_Loop`, `Sub_OnNonzero` and so on) are still address-derived and are recorded as open work in the project `README.md`. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Entry-point naming - `NROM/Tag Team Wrestling (U)`** - all 302 address-derived entry points (`Sub_`, every `jsr`/`jmp` target that carried no semantic name) now carry a ROM-specific name, together with the 154 `SubC` local labels of the same kind: `SetRingEntrancePointers` / `UpdateRingEntranceState` / `UpdateRingEntranceSprite` for the ring entrance, `CallRingMoveHandler` and `CallWrestlerPoseHandler` / `CallWrestlerPoseHandler2` for the two dispatch tables (`WrestlerMoveHandlerPointerTable` at `$A30A`, `Wrestler1PoseHandlerTable` at `$DA82`, `Wrestler2PoseHandlerTable` at `$DB21`), `PushRingPointer` / `AdvanceRingScriptIndex` for the ring script interpreter, `DrawRingHud` for the ring HUD, `StoreRingSpritePositions` / `BuildRingSpriteList` / `BuildRingMoveList` for the two sprite lists, `WriteRingNameTiles` for the wrestlers' names, `WriteGraphicScript` for the graphic-script writer and `DispatchGameMode` for `GameModeDispatchPointerTable` at `$A9FD`. **Residue: 0 `jsr`/`jmp` entry points remain address-derived.** Eleven `SubC` labels are still address-derived and are recorded in the project `README.md` with the reason: `SubC8787` sits on the five bytes between the two DPCM sample blocks, and `SubCED0D`-`SubCED23` ($ED0D-$ED29) are the 29 bytes in front of `WrestlerMoveFlagTable` that the tracer decoded as `BRK`/`ORA (zp,x)`; the dump does not reveal what they are. The 809 internal branch labels are still address-derived and are recorded as open work. **No instruction and no byte value changed.** `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Clu Clu Land (JU)`** - every data block in the PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. 75 labels were applied with `name_data_region.py` (base `$C000`) and the hand-labelled blocks were renamed: `ResourceScriptPointerTable` became `ScreenNametableStreamPointerTable` (the 30 little-endian pointers to the per-screen nametable update streams at `$E75D-$F11D`), `TitleOrMessageTileString` became `ScreenModeFlagTable` (one flag byte per screen, parallel to the pointer table - the entry is copied to `$3F` and tested for zero at `$C53B` and `$CE1B`, so only screens 21 and 24 select the alternate behaviour), and `DirectionAndCoordinateTables_C0B4` and `ResourceDescriptorData_C058` were split into the tables they really are: `ObjectMoveDeltaLowTable`/`ObjectMoveDeltaHighTable` (the two halves of a 16-bit per-direction movement delta), `ObjectStageTimerLimitTable`, `ObjectStageSpeedTable`, `ObjectStageCountTable`, `ObjectStageValueTable`, `ObjectSpawnCountTable` and `HudMarkerOamTemplate` (four 4-byte OAM records - Y, tile, attribute, X - copied in reverse into the sprite shadow at `$02E0-$02EF`). The nametable update format was read out of the writer at `$DED5`: the command byte's low nibble picks the VRAM region (`PPUADDR` high = `$20`|(cmd & `$0F`)) and its high nibble picks one of the 16 four-tile groups in `NametableTileGroupTable` at `$DE95`, and each stream is a run of (command, address) pairs ended by `$FF`. The audio note table at `$F81C-$F82B` turned out to be a descending note-period sequence (140, 133, 126 ... 35) - its first three bytes are the tail of the never-executed instruction at `$F81C`, so they are data, not code. The blocks nothing reads - `$C062-$C0A0`, `$C0E5-$C0E8`, `$C0E9-$C195`, `$C51B-$C52A`, `$CF28-$CF33` and `$D1F5-$D208` (an ASCII tile-index table) - are recorded in the inventory as unreferenced data with their ROM provenance, and `$D551-$D554` is recorded as the bytes of an instruction the tracer split at the wrong boundary. **No instruction and no byte value changed** - the `.byte` rows were only cut at the new label boundaries. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Defender 2 (U)`** - every data block in the PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. The 35 placeholder labels (`GameplayLookupTable_Cxxx`, `GameplayConstantData_Cxxx`) were replaced with names read out of the consuming code: `ScreenStateHandlerPointerTable` (the five 16-bit pointers to the game's state entry points at `$C092`, `$C177`, `$C25D`, `$C261` and `$C355`), `PlayfieldColumnTerrainTable` (the terrain generator at `$C183` computes an index of `((value >> 2) + 1) & $0F` and reads **four consecutive bytes** of each record - height, height, a column type masked with `$07` and a third parameter - so the record size is four, not one), `BitIndexToMaskTable` (the eight single-bit masks `$01`-`$80`), `DefugeCreditString` (72 bytes of ASCII: ` DEFUGED BY TOSHIO SENGOKU ( Intelligent Systems ) 1987/07/31 15:00 ` - the game's debug build stamp), `SpriteDefinitionBaseAddress`/`SpriteDefinitionPointerTable` (two bytes that together form the code address `$C7EA`, through which the sprite definitions are read two bytes at a time), the four column-limit tables at `$C8F7`/`$C8FB`/`$C8FF`/`$C8EB`, the four 16-bit sprite-coordinate tables at `$C89F`/`$C8B5`/`$C8CB`/`$C8DB`, `ControllerButtonMaskTable`, `SpriteTileIndexTable` and the APU period, pitch, envelope and note tables at `$F800`/`$F801`/`$F84C`/`$F919`/`$F925`/`$F932`/`$F933`/`$F942`/`$F943`/`$FE59`-`$FE5D`. The four blocks that decode as 6502 code (`$C161-$C168`, `$C25D-$C260`, `$CE47-$CE50` and `$F8D1-$F8D3`) are recorded as code the tracer could not reach rather than as tables, and the 3,574-byte block at `$EA0A` - the largest data region in the PRG, holding a 4-byte sprite-layout table at `$EACA`, an ASCII character table at `$EAAA` and a column-position table at `$EB0A` - is recorded as one block because no instruction in this dump addresses it with an absolute operand. **No instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Donkey Kong 3 (JUE)`** - every data block in the PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. The 48 placeholder labels (`IndexedGameplayLookupTable_Cxxx`, `CodeOrDataPointerTable_Cxxx`, `ActorCollisionOrStateTable_Cxxx`, `GameplayConstantTable_FF4C`) were replaced with names read out of the consuming code: `GameplayScreenStateHandlerTable` (the eight 16-bit gameplay screen handlers at `$C192` - the old README's "15 handlers" was a miscount), `ObjectRowSpawnTable` (the 21 four-byte per-screen-row spawn records at `$D0B4`, walked with the actor's screen Y and ended by an `$AA` marker), `ObjectMovementIncrementTable` (the per-frame increment stream at `$CD85`), `ObjectTimerReloadTable` (22 (countdown, value) pairs at `$CF84`), `ObjectMovementStepTable`/`ObjectMovementLimitTable` (the two 8-byte tables at `$D1C7` that are added together into the actor's coordinate and limit), `PackedHudValueTable`, `ActorSpriteTileTable`/`ActorSpriteAttributeTable`/`ActorSpritePaletteTable`/`ActorSpritePatternTable` (one 32-byte table of eight 4-byte records at `$D3B5`, read at four different bases so that one lookup fetches all four bytes of the selected record), `InitialOamSpriteTable` (32 bytes copied into the sprite shadow at `$02E0`), `ActorBehaviourFlagTable`, `GameplayObjectStateTimerTable`, `AnimationBaseTileTable` (eight 2-byte base tile numbers at `$F6B0`), `MetaspriteLayoutPointerTable` (16 pointers at `$F757`) and the audio note, sequence, pitch and sample-pointer tables at `$F97B`/`$FAD4`/`$FAF6`/`$FB17`/`$FDA4`-`$FDAB`/`$FFC3`/`$FFE2`. The five blocks that decode as long runs of 6502 instructions (`$C9EB-$CB2A`, `$E110-$E694`, `$E69B-$E7C4`, `$EA5E-$EA9E` and `$EB8E-$EBC5`) are recorded as **code the tracer could not reach** rather than as tables - the Pass-1 trace never entered them, so the tracer emitted them as data. **No instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Donkey Kong Jr. (JU)`** - the data side of this project is now recorded: the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. The 1,646-byte table area at `$C000-$C66D` is a flat run of small tables rather than one table, and the inventory now says so and gives each of them its record size - the 5-byte static object-layout records behind `ObjectLayoutXTable`/`YTable`/`TileIndexTable`/`DimensionsTable`/`OamOffsetTable`, the 4-byte HUD digit descriptors, the 4-byte OAM records of the three enemy templates, the low/high halves of the interleaved 16-bit pointer pairs, the demo input code and duration tables and the 2,641-byte block of stage VRAM command streams at `$F266-$F8B5` that the deferred VRAM queue at `$EE9C` drains. The blocks no instruction reads - `$C0F4-$C136`, `$C173-$C1C7`, `$C226-$C2C0`, `$C31A-$C368`, `$C37A-$C3BC`, `$C3CE-$C3F3` and `$C461-$C484` - are recorded in the inventory as unreferenced data with their ROM provenance. No source change was needed: the data blocks already carried their semantic names. `make` still prints **Byte-identical ROM verified.** - **`_tools/name_data_region.py`** - the row parser choked on a source that carries a redundant provenance note in front of the canonical audit comment (`; PRG $0647 | CPU $C647 ; CPU:$C647 PRG:0647 20 44 ...`), which is the form `NROM/Defender 2 (U)` and `NROM/Donkey Kong 3 (JUE)` use: the byte list it extracted ran past the end of the row and the tool died with a `ValueError`. The body is now cut at the first `;`, so the tool names data blocks in those sources as well. The tool still only inserts labels and cuts `.byte` rows - it never changes a byte value. - **Pass 3 semantic routine naming - `UNROM/Zanac (U)`** - the 94 entry points that the banked image still reached by address now carry a semantic name. Bank 6 is the enemy bank, so its routines are named for what the enemies do (`MoveEnemyX`, `MoveEnemyY`, `StepEnemyTowardsTargetX`, `TestEnemyOverlap`, `SelectEnemyAiDirection`, `UpdateEnemyAiState`, `SpawnEnemy`, `SpawnEnemyWave`, `SelectEnemySpriteType`, `UpdateEnemyWaveTimer`, `SpawnEnemyProjectile`, `RandomizeEnemyDirection`, ...), and the twelve routines that bank 6 exports to the engine in bank 7 carry an `Export` prefix (`ExportUpdateEnemyWave`, `ExportSelectEnemySlot`, ...). Bank 7's three remaining entries are named for what they do (`WriteVramBlock`, `SetPpuMaskAndNmiFlag`, `UpdatePpuAndMask`). The rename covers all three source files - `prg.s` (banks 0-5), `prg.6.s` (bank 6) and `prg.07.s` (bank 7) - together with the `.export`/`.import` lists that link the banks. Each renamed routine's header was regenerated so that it names the new label. **No instruction and no byte value changed** - only labels and the comments that name them. `make` still prints **Byte-identical ROM verified.** - **Pass 3 semantic routine naming - `NROM/Duck Hunt (JUE)`** - every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name; the 134 address-derived entry points (`SubCCxxx`) are gone. The game's own vocabulary is used: the duck and dog logic is named for what it does (`UpdateDuckSprites`, `SelectDuckSprite`, `CheckDuckCollision`, `ProcessDuckScript`, `UpdateDogSprites`, `ComputeDuckHitBox`, `CheckDogAppearance`, ...), the graphics side for what it draws (`DrawPpuBlockList`, `DrawQueuedPpuBlock`, `QueuePpuBlocks`, `HideAllSprites`, `CopyNametableToPpu`, ...), the score code for what it counts (`UpdateScoreDigits`, `AddBcdValue`, `SubtractBcdValue`, `SplitBcdValue`) and the sound engine for what it plays (`UpdateSoundEngine`, `LoadSoundEffectRecord`, `UpdateMusicChannels`, `UpdateSoundEffectSlots`, `SilenceApuChannels`). The twenty bit-test helpers that pick a sound request out of the three request masks are named for the mask and the bit they test (`TestSoundRequestMask0Bit0` ... `TestSoundRequestMask2Bits5to7`). Each renamed routine's header was regenerated so that it names the new label. **No instruction and no byte value changed** - only labels and the comments that name them. `make` still prints **Byte-identical ROM verified.** - **Pass 3 semantic routine naming - `NROM/Dig Dug II (U) [!]`** - every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name; the 135 address-derived entry points (`SubCAxxx` etc.) are gone. The game's own vocabulary is used: the actor system is named for what it moves (`MoveActorRight`, `MoveActorDiagonally`, `UpdateActorBehaviour`, `DispatchEnemyBehaviour`, `SelectActorBehaviour`, `WriteActorOamEntry`, `CheckActorCollision`, ...), the round and wave logic for what it schedules (`StartEnemyWave`, `AdvanceEnemySchedule`, `AdvanceStageSchedule`, `UpdateRoundPhase`, `HandlePlayerDeath`, ...), the player for what it does (`HandlePlayerInput`, `CheckPlayerCanAct`, `StartPlayerRespawn`, ...) and the sound engine for what it plays (`InitializeSoundEngine`, `UpdateSoundChannel`, `WriteApuStatusRegisters`). Each renamed routine's header was regenerated so that it names the new label. **No instruction and no byte value changed** - only labels and the comments that name them. `make` still prints **Byte-identical ROM verified.** - **Pass 3 semantic routine naming - `NROM/Ms Pac-Man (Namco) (U)`** - every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name; 145 of the 146 address-derived entry points (`Sub_Cxxx`) are gone. The game's own vocabulary is used: the screen-script interpreter is named for what it draws (`BeginScreenScript`, `RunScreenScript`, `DrawScriptTile`, `BuildVramTextCommand`), the actor system for what it moves (`SpawnActor`, `MoveActor`, `DispatchActorClass`, `SetActorState`, `CheckActorBounds`, `DrawActorSprite`), the sound engine for what it plays (`SilenceAllSoundChannels`, `StartSoundEffect`, `StartMusicTrack`, `AdvanceSoundChannel`, `PlayNote`), and the menu and score code for what it shows (`InitMenuOptions`, `ParseMenuOption`, `ConvertScoreToDecimal`). Each renamed routine's header was regenerated and given a `Global:` line that says what the routine is for in the game's own terms. The one entry point that keeps its address-derived name, `Sub_C4DB`, is recorded in the project README's *Open work* section: the bytes the tracer decoded into it would write into the PRG ROM and end in a `brk`, so the dump does not reveal what it is for. **No instruction and no byte value changed** - only labels and the comments that name them. `make` still prints **Byte-identical ROM verified.** - **Pass 3 semantic routine naming - `NROM/Urban Champion (JU)`** - every routine the game reaches through a `jsr` or a `jmp` now carries a semantic name; the 157 address-derived entry points (`SubCCxxx`) are gone. The sound engine is named for what it does to the APU (`UpdateSoundEngine`, `UpdateApuChannels`, `LoadSoundEffectRecord`, `LookupNoteDuration`, `WritePulse1PeriodAndTimer`, `SetPulse1Timer`, `StopAllSoundChannels`, ...), the graphics side for what it draws (`DrawPpuBlockList`, `DrawQueuedPpuBlock`, `QueuePpuBlocks`, `HideAllSprites`, `CopyNametableToPpu`, ...), the controller code for what it reads (`ReadControllers`, `ReadControllerButtons`), the BCD helpers for what they compute (`AddBcdValue`, `SubtractBcdValue`, `SplitBcdValue`), the fighter code for what it moves (`CheckFighterHit`, `UpdateFighterMovement`, `DrawFighter`, `RunFighterScript`, ...) and the sound-request helpers for the mask bit each of them sets (`RequestSoundEffectSlot0`, `RequestPulse1Preset0`, `StopAllSound`, ...). Each renamed routine's header was regenerated and given a `Global:` line that says what the routine is for in the game's own terms. **No instruction and no byte value changed** - only labels and the comments that name them. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `UNROM/Zanac (U)`** - every one of the 104 data blocks the tracer emitted is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. The image is banked, so each address is given with its bank. 47 blocks are named for what they are: the 48-entry `JMP` trampoline table at `$C000` (`CmdHandlerJumpTable` - the Mesen CDL shows the CPU fetching 25 of the 48 opcodes, so it is a real dispatch table entered by a computed jump), the per-type sprite attributes at `$C100` (`SpriteAttrTable`, whose `$C12F-$C133` and `$C182-$C186` entries are the half-widths and half-heights of the five enemy types), the fifteen nametable row addresses and their bit masks at `$D7BD` (`NametableRowAddrTable`, `SpritePixelMaskTable`), the title and staff scripts at `$E290` and `$E671` (`TitleScreenTextScript`, `StaffCreditsText` - records of `[command][length][bytes]` and `$00`-terminated strings prefixed by the screen row they are drawn on), the PPU attribute-initialisation script at `$E3B8` (`PpuAttrInitScript` - `[address hi][address lo][payload...][$80]` records that write `$23C0-$23FF`), the 108-entry equal-tempered frequency table at `$F01C` (`NoteFrequencyTable` - each entry is 2^(-1/12) of the one before) and the 36-entry sound-script pointer table at `$F0FE` (`SoundScriptPtrTable`). The 55 blocks that are code the tracer could not reach are recorded as such, with the CDL evidence, and the rest as unreferenced data with their ROM provenance. `_tools/name_data_region.py` grew a `--file` option, because a banked project keeps one source file per bank and the tool worked on `prg.s` alone; `--base` still selects the PRG-offset base, so `--base=-0x10000` names the bank-6 and bank-7 blocks with the right PRG offset. **No instruction and no byte value changed** - the `.byte` rows were only cut at the new label boundaries. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Golf (JU)`** - every data region in the PRG is now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** with each region's address range, size, name, structure and the instruction that reads it. Five sub-tables were separated out of the `.incbin` spans because an instruction in this dump names their address: `ClubSwingSpriteTable` ($C78B, read by `lda $C78B,y` at $C858 with Y = twice the club-selection index), `CourseCellPropertyTable` ($D82E, read by `lda $D82E,y` at $D7F8), `HoleLayoutIndexTable` ($E59D, read by `lda $E59D,y` at $E6B2 with Y = the hole index) and `HoleLayoutParameterTable` ($E6A7, read by `lda $E6A7,y` at $E6BD), and `CourseLayoutStreamData` ($E6E7) - the eighteen compressed hole maps the run-length decoder at $DD78 walks through the pointer table at $E6C3/$E6D5. The spans that stay unnamed are recorded in the inventory as unreferenced data with their ROM provenance and the value structure that is visible in them, and `$D304-$D3B6` is recorded as code the tracer could not reach because it decodes as 51 instructions ending in `jmp $D08A`. The `.incbin` spans were only cut, never rewritten, so **no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Hogan's Alley (JU)`** - all 34 data blocks in the PRG are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** with each block's address range, size, name, structure and the instruction that reads it. The dispatch architecture is what fixes the table layouts: `IndirectCall` at $C34D pops the return address of its own `jsr` off the stack, so the table it reads always begins one byte past the last byte of that `jsr`; working the six call sites out gives the table bases $C124, $C219, $C89C, $CDD1, $D234 and $D3D6, and the index register at each site is what the table is keyed on. So the four inline tables are 16-bit handler-address tables: `SubStateHandlerTable` (19 entries, keyed on `SubStateIndex`), `ModeHandlerTable` (4 entries, keyed on `ModeIndex`), `ActorBehaviourHandlerTable` (14 entries, keyed on `HandlerIndex`) and `TargetBehaviourHandlerTable` (14 entries), and the two tables that sit in the code region are `ScreenSetupHandlerTable` (2 entries) and the two-entry table at $CDD1. The sound engine's tables are read the same way as the sibling engine in `NROM/Mario Bros (JU)`: the note base byte is used twice, once as the note offset and once as the record index, which fixes the record size at five bytes (`NoteParameterTable`, `MusicCommandTable`, `ApuInstrumentTable`, `ApuPulse1CtrlTable`). The thirteen `UnreachedCode*` blocks are recorded as code the tracer could not reach - every one of them decodes cleanly as a run of 6502 instructions - and two of them ($CB34 and $CDF5) are the handler bodies the dispatch tables point at. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Millipede (U)`** - all 11 data blocks in the PRG are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** with each block's address range, size, name, structure and the instruction that reads it. The 1845-byte block at $C000 is not a level map: it is a bundle of small independent tables, and the ones that have a consumer are now named - `DirectionBitMaskTable` ($C04A, the eight single-bit masks 01 02 04 08 10 20 40 80), `ObjectInitParameterTable` ($C271, six bytes copied to $03DF at reset), `OamSpriteYTable` ($C277, four OAM Y positions), `HudSpriteTemplate` ($C27B, thirteen (tile, attribute) pairs copied to $0330) and `SoundSequenceDestAddressTable` ($C295, 34 little-endian 16-bit RAM destination addresses, one per sound effect, into which the engine copies a $FE-byte sequence from $F3B9). The sound engine's blocks are named too - `MusicSequenceData` ($F24F), `PulseTimerSelectTable` ($FA1A), `SoundEffectNoteTable` ($FB43, four (note, duration) records) and `NoteIncrementTable` ($FCCA) - and `SoundChannelTable` ($FE61) records the double lookup the engine does on the channel table: the byte at $FE61 is the channel's note base and the index of its five-byte parameter record. The four blocks that nothing reads are recorded as unreferenced data with their ROM provenance, and two of them ($D14F-$D159 and $F9CE-$F9D0) are recorded as code the tracer could not reach because they decode as instructions. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/1942 (JU)`** - every one of the 26 data blocks in the PRG is now named *and* interpreted under the new rule: 18 labels applied with `name_data_region.py` (base `$8000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The first Pass 3's names were guesses and are corrected: `LevelLayoutData` is the stage script and tilemap stream, of which only the last 133 bytes are read directly; `PpuBitMaskTable` is the eight single-bit masks that mark the player's 8-pixel column as occupied in the RAM bitmap at `$07C1`; `EnemyHandlerPointerTable` is the 16-entry spawn-command dispatch table indexed by *command* - the script byte minus `$F0` - whose `$A31E` handler despawns the object; `LevelObjectSpawnTable` holds the per-enemy-type script pointers at `$A413` and the 12 fixed-point X offsets at `$A3F9`; `PlayerScoreTable` is the four-row shot-collision Y threshold read at `$B4A7`; `EnemyMovementPatternTable` is the three-sprite OAM template the plane update copies into `$0230`; `EnemySpritePositionTable` is nine OAM records followed by 30 sixteen-bit enemy handler pointers and the 3-byte movement records that follow them; `EnemyHandlerCodeTable` is the per-enemy-type behaviour data whose last five bytes are the score thresholds compared at `$DA05`. Eight blocks are read by no instruction in this dump and are recorded in the project README as unreferenced with their ROM provenance - five of them (`$C555-$D069`, `$F55D-$F56F` and the code half of `$A770-$A815`) decode as 6502 code the tracer could not reach, and `$AD99-$ADC8` is an off-screen OAM template that nothing reads. The CHR is the oracle: tiles `$00-$09` are the digits and `$0A-$23` the letters, which is what shows that the runs of small values in `$8494-$9FDA` and `$BB51-$BC85` are nametable tile data rather than text. The audit trail was damaged - 13 `.byte` rows carried a second, wrong copy of the audit comment whose PRG offset was negative (`PRG:-1F74`) because the PRG is mapped at `$8000`, and 7 more carried an undocumented-opcode note after the audit comment - and was repaired to the canonical `; CPU:$XXXX PRG:XXXX AA BB` form; **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Baseball (UE)`** - all 120 named data tables and all 93 inline data blocks in the PRG are now listed in the project README's **Pass 3 data-table inventory**, each with its address range, size, name, structure and the instruction that reads it. The inventory is built on what the source already establishes: the seven-byte object-initialisation records at `$D0A0-$D125` (object offset, sprite index, attribute, X low/high, Y low/high) read by `ldx ObjectInitializationTable,y` at `$D06C`; the four parallel three-byte state tables at `$CD22-$CD2D`, which the code reaches with a base one byte below each table's label so that entry 0 as read is the operand byte of the preceding branch; the five pitch tables at `$D14B-$D166` and `$D2A4-$D2AC`, which the pitcher's windup searches with `cmp PitchTypeThresholdTable,x` at `$D1EE`; the twelve six-character announcement messages at `$CEC9-$CF10`, selected by an offset into `AnnouncementTextTable` and read at `$CEBC`; the 256-byte rendering/animation pages at `$ED00-$F94C` that the renderer reads as one array; and the music sequence data at `$FE40-$FFE1`. Two categories are recorded as **not yet interpreted** rather than guessed: the record format inside the rendering pages and the music data. The stale addresses in the flow document were corrected against `prg.s` (the pitching and batting routines live at `$D18B`-`$D3FF`, not at `$C6xxx`), and the paths that still said `copilot/` now say this folder. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Burger Time (U)`** - all 71 named data tables and the 25 inline data blocks in the PRG now carry ROM-specific names and a **Pass 3 data-table inventory** in the project README giving each block's address range, size, name, structure and consuming instruction. The placeholders are gone: the 68 `LookupTable_Cxxx` labels and `RomData_E871` became names read out of the consuming code - `MainStateHandlerPointerLo`/`Hi` are the two halves of the seven-entry main-state handler pointer table at `$C000`; `DecoSignatureString` is the ASCII "DECO" the routine at `$C78C` compares against and stamps into RAM; `IngredientPlacementOffsetXTable`/`YTable` are the five (X, Y) pairs at which a burger ingredient is placed; `BurgerItemTileTableA`-`D` and `BurgerItemAttributeTableA`-`D` are the four burger-item events' sprite tile and attribute bytes; `EnemySpawnPositionTable` is 23 OAM Y coordinates; `PowerUpScoreTable` is the 28 ascending score thresholds the power-up routine walks backwards; `BitShiftTable` is the eight descending powers of two; and the remaining big blocks are the enemy script, sprite handler, level pattern, stage layout, status panel, sound effect and object handler pointer tables. Three further tables are named through equates in `locals.inc` because their bytes sit inside inline blocks. The audit trail carried the wrong CPU base - it read `CPU:$8000` where the PRG is mapped at `$C000`, so every address was out by `$4000` - and was rebuilt with `_tools/repair_audit_comments.py --base 0xC000` into the canonical `; CPU:$Cxxx PRG:xxxx AA BB` form; the PRG offsets were already correct and are unchanged, and **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Data-table interpretation of the remaining promoted projects** - the rule is in `PROMPTS.md` and the tier is part-way through applying it: 44 of the 61 promoted projects carry a **Pass 3 data-table inventory** and the rest are in progress. The entry for each project lands here as it finishes. - **Magmax (U)** - every data block in the PRG is interpreted and named, and the project `README.md` carries the full inventory (address, size, name, record structure and the consuming instruction). 48 tables placed, including `TerrainLevelData` (the 15 KB level and screen-text stream at the head of the PRG, split into the parts that have their own consumers), the eleven 16-bit handler tables the game dispatches through, `SpriteOamTemplateTable`, `TerrainSegmentOffsetTable`, `SoundEffectProgramData` and the three 16-bit pointer tables the sound engine uses. The blocks nothing reads are recorded as unreferenced data or as code the tracer could not reach. - **Othello (U)** - every data block in the PRG is interpreted and named, and the project `README.md` carries the full inventory. 20 tables placed, the clearest being the eight compass directions: the bit masks at `$DA49`, the direction names at `$DA51`/`$DA59` and the two step tables at `$9003`/`$900B`. Also `BoardSquareWeightTable`, `SquareEvaluationWeightTable`, `MoveOrderTable` and `BoardFlipMaskTable`. The many small blocks are recorded as unreached code, as text reached through the game's own text interpreter, or as the interpreter's operation tables. - **Pac-Man (U)** - every data block in the PRG is interpreted and named, and the project `README.md` carries the full inventory. 58 tables placed, including `ProgrammerSignatureString` (the 15-byte "HIROKI AOYAGI" string compared with `$0052`), `MazeScreenTileData` (6 rows x 23 tiles written to `$20E5`), `InitialPaletteData`, `NametableAttributeTable`, the ten 16-bit ghost handler tables, `CharacterSpriteTileTable`, `LevelGhostTargetTable` and `AudioNotePeriodTable` (12 APU period values one octave apart). The old README's claim that `$C0EB` held the copyright text was wrong - it holds the programmer's signature - and is corrected. - **Pac-Man (Tengen) (U)** - the same interpretation as the other Pac-Man dump, applied to this PRG (the two differ only in the licence text and the reset stub). 58 tables placed and the full inventory written into the project `README.md`, replacing the earlier list of guessed names. - **Slalom (U)** - every data block in the PRG is interpreted and named, and the project `README.md` carries the full inventory. 50 tables placed, including `CoursePathTable` (64 records of skier X position and terrain drop), `CourseHeightProfileTable`, `CourseLevelHeightTable`, `SkierMovementTable`, `KeyboardInputMap`, `MusicNoteData` (the Namco-format sound sequences) and the 40-entry `GameEventHandlerTable`. The blocks nothing reads are recorded as unreferenced data or as code the tracer could not reach. - **Pass 3 data-table interpretation - `NROM/Kung Fu (U)`** - all 15 data blocks in the PRG are now interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and the instruction that reads it. The `$8000-$8257` block is a bundle of nineteen separate tables and is split accordingly: `LevelHandlerPointerTable` and `LevelSectionListOffsetTable` (the two halves of the inline level dispatch), `LevelSectionHandlerTable`, `ScreenInitRoutinePointerTable`, `ScreenVramStreamPtrLoTable`/`ScreenVramStreamPtrHiTable` (the 79 screens' VRAM update streams), `PlayerSpriteYPositionTable`, `SpritePairIdTable`, `PlayerSpawnYTable`/`PlayerSpawnXTable`/`PlayerSpawnHitPointTable`, `PlayerSpriteOamTemplate`, `SpriteSpawnParameterTable`, `SpritePatrolOffsetTable`, `GameStateHandlerPointerTable`, `StatusSpriteOamTemplate`, `StageScreenLayoutTemplate`, `EnemySpawnAttributeTable` and `EnemySpawnDelayTable` (106 bytes each, indexed by the same enemy slot). The VRAM update records were read out of the writer at `$DAEA`/`$DB19` - [PPU hi][PPU lo][count][count bytes] - and every pointer in the screen table lands on a byte in `$20`-`$3F`, which is what confirms it. Four blocks are left unnamed and recorded in the inventory: `$8C8A-$8CC0` is unreferenced data, `$8CC1-$8DCE`, `$D386-$D3C5` and the tail of the `$DB48-$F830` bundle are code the tracer could not reach. The audit trail on the data rows was damaged - seven rows carried a second, wrong copy of the comment with a negative PRG offset, and every sixteen-byte row listed only its first twelve bytes - and was rebuilt so that each row's comment carries its exact bytes again; **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Kung Fu (U) (a1)`** - the same interpretation as the `(U)` revision, applied to the other build of the same game (the routine addresses differ, the tables do not): 21 labels, the same nineteen-table split of `$8000-$8257`, and the same four blocks recorded as unreferenced data or as code the tracer could not reach. The data rows' audit comments were rebuilt as well, so that each row's comment carries its exact bytes again. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Mario Bros (JU)`** - all 35 data blocks are now interpreted and the project `README.md` carries a **Pass 3 data-table inventory** with every block's address range, size, name, structure and the instruction that reads it. Eight blocks that earlier revisions had named with guesses turned out not to be data at all: `$D491-$D4FD` (`EnemyBehaviourTable`) is the enemy-type handler code the inline table points at, `$D62D-$D633`, `$E16C-$E16F`, `$E18C-$E18F`, `$E1A4-$E1A6`, `$E494-$E499`, `$E4A4-$E4A9`, `$E4F1-$E4FB`, `$E69E-$E69F`, `$ECF5-$ECFA`, `$C632-$C635` and `$CA5A-$CA5D` are code the tracer could not reach, and they are left unnamed. The two inline dispatch tables are now read for what they are: `$C510-$C527` is twelve 16-bit game-state handler pointers that follow `jsr DispatchViaStackPointer` at `$C50D`, and `$C593-$C5A2` is eight 16-bit enemy-type handler pointers that follow the call at `$C590`. `$ECCC-$ECEB` is the eight-entry enemy OAM template (not a sound envelope), `$EE53-$EE69` is ten big-endian sound-engine addresses in three groups, and `$E078-$E0B7`, `$E568-$E5CD`, `$F4B2`, `$F574`, `$F68D`, `$F94E` and `$FE6C` are the enemy tile sequence, the enemy tile/attribute pairs, the enemy tile groups, the spawn columns, the score-digit OAM templates, the triangle waveform and the note records. A second pass over the same blocks corrected three of those readings and added the sub-tables that the inventory lists: `$E558-$E5CD` is the stage OAM setup - ten instructions of code, six tile bytes at `$E568` and the 24-byte OAM template at `$E574` (eight three-byte records, Y / tile / X) that `lda $E574,x` copies to `$0200` at `$E53D`; `$ECCC-$ECEB` is the 32-byte initial state of the actor block at `$B0`-`$CF`, not an OAM template; and `$F904-$F96A` is the pulse note period table, two bytes per entry, whose first entries are a descending chromatic ladder - `00 69` is C6 at 1055 Hz and `01 AB` is C4 at 261 Hz on the 2A03's own pitch formula, which is what identifies it. The new labels are `StageSubHandlerPointerTable`, `StageSubHandlerPointerTable2`, `StageOamSetupCode`, `StageSpriteTileList`, `StageOamTemplate`, `StageScriptSetupCode`, `StageColumnParameterTable`, `StageRowParameterTable`, `EnemyTileGroupTable`, `EnemySpawnVramAddressTable`, `ScoreDigitOamTemplateTable`, `TriangleChannelParameterTable`, `SoundNoteRecordTable`, `PulseNotePeriodEntry0Low`, `PulseNotePeriodRestEntry` and `SoundEffectNoteIndexTable`. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Pinball (JU)`** - all 40 data blocks in the PRG are now named and interpreted, and the project `README.md` carries a **Pass 3 data-table inventory** with every block's address range, size, name, structure and the instruction that reads it. 37 labels were placed with `name_data_region.py` (base `$C000`). The findings worth recording: `$C16A-$C180` is the designer credit in Shift-JIS - the ASCII "BY S.MATSUKA & S.IWATA " - `$DE87-$DF86` is a 256-entry table of `floor(i*i/256)` used for the ball's circle maths, `$EC60-$EC7F` is the playfield palette as eight RGB triples of standard NES entries, `$EFBA-$EFD2` is the title logo as a 5x5 matrix of tile indices, `$FCA8-$FCB3` holds the twelve noise-channel periods, and `$C400-$CBA6` is the screen layout - 267 tile bytes and 784 big-endian PPU addresses. Fourteen blocks are left unnamed: ten are code the tracer could not reach or one-byte `$2C` fill, and the rest are unreferenced data whose layout is not established. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Popeye (JU) (PRG 0)`** - all 7 data blocks in the PRG are now interpreted and the project `README.md` carries a **Pass 3 data-table inventory** with every block's address range, size, name, structure and the instruction that reads it. `$F088-$F832` is the level's VRAM update stream - the same [PPU hi][PPU lo][count][count bytes] record format the writer at `$DB19` consumes - whose first record fills the sprite pattern table at `$3F00` and whose second fills nametable 0 from `$23C0`; `$F900-$F96F` is the APU pulse note-period table, with the high byte of each period at `$F900` and the low byte at `$F901`; `$C000-$C648` is the level object data, whose sprite Y positions at `$C50F`, bit masks at `$C06D`/`$C06E` and 16-bit pointers at `$C62B` are read directly. Three blocks are left unnamed: two are code the tracer could not reach and one is the eight bytes in front of the vectors. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Popeye (JU) (PRG 1)`** - the same interpretation as the other Popeye build (the two PRGs differ only in their code), applied here: five labels and the full inventory in the project `README.md`, with the same three blocks left unnamed. `make` still prints **Byte-identical ROM verified.** - **Tool fixes** - `_tools/analyze_data_blocks.py` no longer reports an empty data set for every block that carries a label (it skipped the label line and the `.byte` rows behind it) and no longer runs a block's bytes past the start of the next block; `_tools/name_data_region.py` no longer crashes on a `.byte` row that carries an undocumented-opcode note after its audit comment. Both were needed to read the evidence for the projects above. ## History Entries move here when the commit they describe is made, newest first. The ### 2026-10-21 - The tier is complete - **The data tables of the two test cartridges and of Pac-Man (USA) (Namco)** are interpreted. The Joypad Test Cartridge paints its whole picture from a command stream replayed into the PPU, and the stream address is passed inline in ROM after the `jsr`, so the four two-byte blocks are pointers and not code. The PowerPad Test Card's two twelve-byte tables are the PPU address low byte of each of the twelve controller-bit indicators - and its font has no `W` in the A-Z run, so the screen really shows "IC MA/ BE BAD". Pac-Man (USA)'s maze splits into a wall tile array and a path tile array, and a large part of its "data" is code the tracer could not reach, because the game dispatches through an indirect jump. - **All 61 promoted projects** now carry a Pass 3 data-table inventory, and the tier passes every item of `PROMPTS.md`'s promotion checklist: `python3 _tools/audit_promoted.py --build` reports 61 passing and none failing. - **The entry points** are named across the tier - 8,164 address-derived `jsr`/`jmp` targets down to 1,401, and 34 of the 61 projects have none left. The residue is recorded per project, with the reason. The last batch was the three Pac-Man dumps (which share one set of names), the two test cartridges, Mermaids of Atlantis, Xevious, Pinball, Port Test Cartridge, Wild Gunman and Baseball. - **Two documentation errors corrected on the way**: the PowerPad Test Card's `$E1F1`/`$E1FB` write `$2001`, so they are `ShowBackground` and `HideBackground` and not `EnableNMI` and `DisableNMI`; and Pac-Man (U)'s attract-mode routine names are replaced with the ones the code supports. ### 2026-10-21 - The audit comments go back to their column The comment column is part of the audit trail: the repository's convention is that it starts at column 35, so a reader can find the bytes by eye. Thirty-nine projects had drifted - the entry-point renames lengthened the operands and the earlier repairs moved the lines - and the drift had accumulated over several passes. `align_audit_comments.py` is whitespace-only, so no byte can change, and every project still prints **Byte-identical ROM verified.** A data row's comment follows its data rather than sitting in the column - that is what the emitter produces, and `align_audit_comments.py` leaves those rows alone. An instruction whose code text is longer than 31 characters cannot be padded into the column either, and those are left where they are. ### 2026-10-21 - The entry points are named `PROMPTS.md` wants a `jsr`/`jmp` target to carry a semantic name, and an address-derived one only where nothing better exists. The tier had 8,164 address-derived entry points; the agents went through them project by project, reading each routine and its callers, and named the ones the code supports. - **Donkey Kong (JU)**: 358 named - `MainGameLoop`, `UpdateGameObjects`, `GrabLadder`, the five Kong tilemap streams, the frame services. - **1942 (JU)**: 375 named - the deferred-call dispatcher, the enemy spawn commands, the ten enemy types. - **Popeye (JU) (PRG 0)** and **(PRG 1)**: 297 each - the VRAM update queue, the OAM shadow, the five NMI state handlers. - **Soccer (JU)**: 311 - the match state, the player and ball handlers, the two dispatch tables. - **Donkey Kong Jr. Math (U)**: 295 - the answer-choice machinery, the text writer, the sound engine. - **Sqoon (U)**: 271 - the phase dispatcher, the pod family. - **CIRCUS**: 258 - the inline dispatcher, the byte script, the ring actors. - **Millipede (U)**: 225 - the millipede segments, the spawner, the sound effects. - **Othello (U)** and **Tag Team Wrestling (U)**: 302 each. - **Lunar Pool (U)**: 185 - the object interpreter, the play field. - **World Class Track Meet (U)**: 142 - the event tasks, the olympics sequences. - **Balloon Fight (JU)**: 154 - the PPU command buffer, the balloon handling. - **10-Yard Fight (U)**: 115 - the ball physics, the thirteen sound voices. - **Gyromite (JUE)**: 120 - the craft and mothership handlers, the fifteen sound-effect requests. - **Galaga (U)**: 81 - the enemy formation, the VRAM-record machinery. - **Magmax (U)**: 70 - the four object pools. - **Kung Fu (U)** and **(a1)**: 51 each. - **Slalom (U)**: 38 - the VRAM tile stream, the course data. - **Urban Champion (JU)**: 157 - the sound engine, the PPU block list, the two fighters. - **Ms Pac-Man (Namco) (U)**: 145 - the screen-script interpreter, the actor system. - **Dig Dug II (U) [!]**: 135 - the actor behaviour, the round scheduler. - **Duck Hunt (JUE)**: 134 - the ducks and the dog. - **Zanac (U)**: 94 - the enemy bank, applied across all three of its source files. Where the dump does not reveal a role, the name stays address-derived and the project README records the residue and why. The internal branch targets under a renamed parent are re-based onto it, and the generated header text that still named the old labels is rewritten. ### 2026-10-21 - The promoted tier passes every item of the promotion checklist `python3 _tools/audit_promoted.py --build` reports nothing for all 61 projects. - **CIRCUS**, **Duck Hunt (JUE)** and **Balloon Fight (JU)** join the interpreted data tables. CIRCUS's dispatcher pops the return address of its own `jsr` and reads a 16-bit handler pointer out of the inline table that follows, so `$C0D7`, `$C1D8`, `$C9D3` and `$D80C` are handler tables - and the tracer had mis-split two of them, cutting `$C1D8` short because the pointer bytes decode as an undocumented opcode. Duck Hunt's `$E64B` is the duck's 24 ascending speed table. Balloon Fight's `$D65C` is 64 little-endian nametable addresses, the stage's background tile list. - **The entry points of the remaining projects** are named where the code reveals a role, and the residue is recorded in each README. - **name_data_region.py** cuts a `.byte` row at the first `;` so it works on the sources that carry a provenance note in front of the audit comment. - Every project still prints **Byte-identical ROM verified**, and the `_wip/` tier's 831 projects all build. ### 2026-10-21 - The last of the checklist: the zero-page residue, and the evidence for it - **Excitebike (JU)** - the zero page is reused across modes and eight slots carried a role-specific name while holding more than one role: `$80` is the player's target X in one routine and a shadow of the PPU control register in another. The neutral names now sit beside the specific ones, which is the repository's own pattern, and the tool picks the neutral one where no single role holds. Its residue is zero. - **Hogan's Alley (JU)**, **Popeye (JU) (PRG 0)** and **Popeye (JU) (PRG 1)** - one operand each stays numeric, and each README now says why. `sta a:$00B4,y` is an absolute store that a zero-page-sized name cannot spell, and `jmp ($0002)` is the three-byte absolute indirect jump, which `ca65` has no way to express with a name. - **apply_symbols.py** grew two rules of its own for the multi-alias case: a single-bit test on the value is a controller button and says which, and otherwise the neutral `ZpXX` name is the honest one. - **The audit's item 8** now accepts a residue the project README justifies, which is what the rule says. - Every project still prints **Byte-identical ROM verified.** ### 2026-10-21 - The promotion checklist gets a checker, and the tier passes it - **_tools/audit_promoted.py** - new tool: checks the promoted tier against all eleven items of `PROMPTS.md`'s verification checklist and reports the file and the line of every finding. It is a checker, not a fixer. The checklist gains it as item 12. - **1,487 branch targets carried an address-derived name.** `PROMPTS.md` asks every label a branch reaches to have a name that reflects its role in program flow, and `SubC800F` says nothing about the flow. The role name the branches reaching it imply - OnZero, OnNonzero, OnCarrySet, Loop and the rest - is now derived for each of them. `derive_branch_names.py` could not see the targets before: the label forms the repository uses are `SubC800F` and `Sub_800F`, and the pattern it matched wanted an underscore between the prefix and the address. - **48 operands named a hardware register by its address.** Clu Clu Land (JU) had 1,308 zero-page operands still numeric and one `$2000` where `PPUCTRL` belongs; Xevious (U) had 20 hardware literals; Zanac (U)'s bank 7 read `$4017` where the joypad port or the frame counter belongs. Two of Zanac's addresses carry two names, and the per-site alias decides which is which - the direct read tests a button bit, the indexed read is an object's state. - **325 neutral zero-page slots carried no reason.** `locals.inc` now says, per slot, that the name is provisional. `_tools/document_zp_slots.py` writes it. - **The tools grew to match**: `apply_symbols.py` accepts an upper-case mnemonic, `apply_zp_aliases.py` can rewrite a bank's own source file (`--file`), and the Pass 2 tools read a banked project's three files. - Every project still prints **Byte-identical ROM verified.** ### 2026-10-21 - Zanac (U): trace bank 6, and give the project a build that can hold it - **Zanac (U)** - the committed split filed bank 6 - the switchable bank the CPU actually executes - as raw data. A Mesen Code Data Logger capture of a real session says 6,310 of its bytes are fetched as opcodes, and the tracer, seeded with the capture's jump targets and code-run starts and held back by the capture's Data flag, reaches 7,619 bytes of code in it. The bank needs a segment of its own: a call inside it must assemble to the CPU address $8000-$BFFF, not to the file offset the bank sits at. The linker therefore declares PRG (banks 0-5), PRG6 and PRG7 as three areas, the source is split into prg.s, prg.6.s and prg.07.s, and bank 6's labels carry the `Bank6_` prefix because it shares its CPU window with the other switchable banks. - **Zanac (U)** - the three other facts the capture settles: banks 0-3 are empty - every byte of the image in them is `$00` - banks 4-5 are stage data, read but never fetched - and the bank switch is an indirect store, `sta (Zp00,x)` at `$C7B9`, with the bank number the high byte of a zero-page pointer rather than an immediate. - **Zanac (U)** - the coverage goes from 32.5% to 44.5% of the 128 KB image. Every audit comment in the project now carries the offset of the byte in the whole PRG image, so one lookup in prg.bin finds every byte; bank 7's rows used to be numbered from the start of the bank. - **Zanac (U)** - the fixed bank calls into the switchable bank twelve times, and the two are separate translation units, so the labels are exported from prg.6.s and imported into prg.07.s. Bank 7's own Pass 2 is redone over the re-based trail: the 212 address-derived labels take the `SubC` form and the routine headers are derived again. ### 2026-10-21 - Re-split Pass 1 from Mesen CDL captures across the tier - **Donkey Kong (JU)** - the committed split had 742 raw jsr/jmp operands: the tracer had walked into the game's data tables and decoded them as code. A Code Data Logger capture of the title screen and a stage-1 game, with the capture's jump targets and code-run starts used as tracer seeds and the capture's Data flag forcing the tables back to data, gives 6,014 instructions / 12,505 code bytes (76.3%), 1,015 labels, and no raw operand left. - **Lunar Pool (U)** - 638 raw operands; the same treatment gives 5,318 instructions / 11,100 code bytes (67.7%) and no raw operand. - **Lode Runner (U)** and **CIRCUS** - hundreds of raw operands each; both re-split clean from a capture of a real session. - **Duck Hunt (JUE)** and **Balloon Fight (JU)** - the same, both clean. - **Joypad Test Cartridge (U)**, **NES PowerPad Test Card (U)** and **Pac-Man (USA) (Namco)** - the same, all three clean. - **Pac-Man (U)** 18.7% -> 45.9%, **Pac-Man (Tengen) (U)** 20.0% -> 47.3%, **Slalom (U)**, **Magmax (U)** and **Othello (U)** - re-split, the calls that survived into a region with no code turned back into the explicit `.byte` rows they always were. - **Urban Champion (JU)** - the two captures in the project folder are merged and the merge is used as a second oracle beside the tracer; the split goes from 33.8% to 48.6% and the 220 raw jsr/jmp operands are gone. ### 2026-10-21 - Repair the damaged audit trails across the tier - **Thirteen projects** carried audit comments that did not match prg.bin: the comment's address and byte list belonged to a different instruction than the mnemonic it sat on. **Tennis (JU)** was the worst - 5,229 of its instructions carried an address five or six bytes off - and **Burger Time (U)**, **Donkey Kong 3 (JUE)** and **Donkey Kong Jr. (JU)** were damaged in the same way. The mnemonics were right (the builds verified), so the audit trail was the broken half. - **repair_audit_comments.py** rebuilds the trail from the ca65 listing, using prg.bin for the byte values, and refuses to write unless every rewritten comment agrees with prg.bin. Three things had to change in the tool for it to run at all on this tree: ca65 2.19 prints ` ` for a record that emits bytes and `r ` for one that does not; the listing prints the address the segment is assembled at, so for a PRG mapped at `$C000` or `$8000` the PRG offset is the address minus the base; and the audit-segment pattern only allowed a four-digit CPU address, so a source whose last instruction runs past `$FFFF` kept its stale comment. - All 322,830 audit comments in the tier now agree with prg.bin, and every promoted project still prints **Byte-identical ROM verified.** ### 2026-10-21 - Complete Pass 2 across the tier: no Label_ residue left - **Sixteen projects** still carried address-derived `Label_XXXX` labels and **twenty-seven** still had raw `$XXXX` operands on jsr/jmp. The Pass 2 map is now built from the source's own labels - a label's address is the address of the instruction that follows it, taken from the file, not parsed out of its name, because some of the old names carry a mistyped address suffix - and every label keeps the name it has unless it is one of the address-derived ones. - **derive_branch_names.py** no longer names a jsr/jmp target that no instruction sits at: those are false decodes, so the operand stays numeric and the project records it as residue. - **apply_pass2_map.py** no longer inserts a `Name:` line mid-routine to name an address that carried no label - in a project that uses ca65's `@local` labels that re-scopes every `@local` after the split and breaks the forward `@refs`. There the name becomes an equate in locals.inc, which names the address without touching the label scope. ### 2026-10-21 - Give every promoted project the power-on flow analysis its README was missing - `PROMPTS.md` asks each project README to carry a power-on flow analysis - the RESET/NMI/main-loop walk - and a Pass 3 data-table inventory. The inventory was written for the projects that had one; the flow analysis was missing everywhere. - **derive_poweron.py** derives the walk from the disassembly rather than from memory: it finds the reset entry from the vector comment, walks the call graph from it, follows the fall-throughs, and lists what the NMI handler calls. It also says whether the IRQ vector reaches the reset path. - All 61 promoted projects now carry the section. ### 2026-10-21 - Interpret the data tables across the tier - **Pass 3 data-table interpretation - `NROM/Raid on Bungeling Bay (U)` and `NROM/Raid on Bungeling Bay (U) [a2]`** - every data block in both dumps is now named *and interpreted* under the new rule: 69 labels applied with `name_data_region.py` (base `$C000`) per project, and both project READMEs gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The two revisions differ in only 88 bytes - `$C67D-$C6D8` and `$C73B-$C779` - so the `[a2]` dump is the base dump plus Konami's credits screen (`"game a"`, `"game b"`, `"q play"`, `"copyright"`, `"fmg"`, `"by kiku"`), which is why the two inventories are otherwise identical; the `[a2]` merges the base's `$C625-$C67C` and `$C6AA-$C6E2` blocks into one 190-byte block and reaches the code at `$C77A` that the base dump leaves unreached. The CHR and the ROM's own strings are the oracle: `$C8F0` is `"game" $7F $7F "over" $00`, `$C96F` is the six-word status pool `"round" $DB "factory" $D9 "left" $FA "score" $BB "top" $7F $7F "p" $00`, `$CFF4` is `"complete"`, and the radar screen is drawn from tile-index tables, not graphics. `$C6AA` is the screen layout itself - 19 records of (VRAM address high, VRAM address low, byte count) - and `$C35C` is the 16-entry APU pulse-1 period ladder the sound engine reads. 33 of the 44 tracer blocks in the base dump carry at least one name (67 labels in all); the 11 that carry none all decode as runs of 6502 instructions - code the tracer could not reach - and are recorded in the project README as such, with their ROM provenance. Several *named* blocks are read by no instruction either (the text strings at `$C8F0`, `$C96F` and `$CFF4` among them) and their rows say so. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Sqoon (U)`** - every data block in the project is now named *and interpreted* under the new rule: 26 labels applied with `name_data_region.py` (base `$8000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The CHR is the oracle for the text: pattern table 0 carries the font at the ASCII tile positions (`$41` = 'A' ... `$5A` = 'Z'), so `$85F3` is the nine-character ASCII string `"GAME OVER"` drawn by the routine at `$85CC` (which wraps to a new row at a space), `$8B48`/`$8B4F` are `"PHASE "` and `"LEFT "`, `$8ACC` is `"INTERVAL"`, and `$C8F0` is `"game" $7F $7F "over"`. The bytes of `$833A-$8399` are copied into zero page `$56-$75` by two index-from-the-end loops at `$8326` and `$8331` (the second overwrites the first), and `$8690-$8699` is a five-entry table of pointers to the sprite-definition strings that `Sub_86D8` draws; `$E4B7` is the 14-entry APU pulse-1 period table the sound engine reads. 15 of the 17 tracer blocks carry at least one name (27 labels in all); the other two (`$84A0-$84AF` and `$B5D5-$B5DD`) are referenced by no instruction and are recorded in the project README as unreferenced with their ROM provenance. The audit trail was damaged - twelve `.byte` rows carried a second, wrong copy of the audit comment and 871 more listed only the first twelve of their sixteen bytes - and was repaired to the canonical `; CPU:$XXXX PRG:XXXX AA BB` form; **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Stack-Up (JU)`** - every data block in the project is now named *and interpreted* under the new rule: 42 labels applied with `name_data_region.py` (base `$8000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The first Pass 3 had guessed at four of the blocks: `PlayerNameTable` (`$82EA`) is not names at all - it is sixteen pointers into the block-shape strings at `$B574`..., `LevelHandlerPointerTable` (`$8827`) is not a handler table - its 39 little-endian values are all multiples of 16 and nothing reads them - `BlockCountTable` (`$9AB4`) is twenty bytes, eighteen of them zero, that nothing reads either - and `BlockPlacementCode` (`$9090`) is a 648-byte routine the tracer never reached, not a data table at all; the first three are now recorded as unreferenced data with their ROM provenance and the fourth as unreachable code. The rest of the game is tile-driven: `$8C3A` is six five-tile block shapes each closed by `$FF`, `$9344`/`$9351` are the block colour index and its eight-entry palette lookup, `$943F` is 27 pointers to the per-level layouts of (tile, attribute) pairs closed by `$AA`, `$AA86` is the direction lookup that reverses the index inside each group of four, `$B542` is the eight single-bit direction masks, and `$AF72` is a six-row by 32-column tile map in which `$FF` marks an empty cell. 31 of the 40 tracer blocks carry at least one name (39 labels in all); the 9 that carry none are recorded in the project README as unreferenced - three of them (`$9090`, `$F989` and `$9D08`) decode as 6502 code the tracer could not reach and are recorded as such. The 13 `.byte` rows whose audit comment carried a PRG offset computed with the wrong base were repaired to the canonical form; **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Soccer (JU)`** - every data block in the project is now named *and interpreted* under the new rule: 26 labels applied with `name_data_region.py` (base `$8000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The first Pass 3's names were guesses: `PlayerSpeedTable` (`$82EF`) is eight three-byte player sprite records (Y, tile, attribute) copied into the sprite buffer at `$0701`, `PlayerMovementTable` (`$8921`) is a 32-entry speed-by-distance table followed by 86 bytes of unreached code, `PlayerDirectionTable` (`$89D2`) is two five-entry tables (a threshold and a reload value), `FieldPlayerSpriteTable` (`$8D44`) is 144 bytes of OAM data for twelve players, and `FieldNameText` (`$8ED1`) is the `"CREATE BY M.OOYAMA"` credit - the same string appears a second time at `$9F82`. The team definitions at `$9510` give each player's kit colour, the badge offsets at `$959A` and the badge tiles at `$95A2` (`"PQR"`, `"IGH"`, `"LJK"`, `"OMN"` are the tile indices of the four teams' badges); `$B891` is five five-byte player-action records, `$D8CD` is the five records of the time a player may hold the ball, and `$DFF6` is 120 little-endian cell offsets nothing reads. 27 of the 34 tracer blocks carry at least one name (30 labels in all); the 7 that carry none are recorded in the project README as unreferenced - six of them decode as 6502 code the tracer could not reach. The 9 `.byte` rows whose audit comment carried a PRG offset computed with the wrong base were repaired to the canonical form; **no instruction and no byte value changed**. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Ms Pac-Man (Namco) (U)`** - all 27 of the data blocks the tracer left in `prg.s` are now named *and interpreted* under the new rule: 29 labels applied with `name_data_region.py` (base `$8000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The first Pass 3 had named the 16 KB lower half of the PRG image `CopyrightText`; it is not text at all - it is the game's display data, reached through the stack-popped pointer interpreter at `$C436-$C44D`, and it holds ASCII-art screen rows including the maze/score header `------------------------------------1UP-----HI:SCORE-----2UP` at `$8A11` and the copyright screen `1980-1993-NAMCO-LTD`, `NAMCO-HOMETEK INC`, `LICENSED-BY-NINTENDO` at `$8B4D-$8D91`. No instruction in the dump names it (the only absolute operands that land there are four *writes*), so the label was removed and the block is recorded as unreferenced data with its ROM provenance. The CHR is the oracle for the text: pattern table 0 carries the digits at tiles `$30-$39` and the letters at `$41-$5A`, which is what identifies `$C0F7` as the programmer credit `" NAOKI HIGASHIO "` and `$E21B` as the score-header stream (`HI:SCORE`, `1UP`, `2UP`); the same convention in the sibling `Pac-Man (USA) (Namco)` ROM (`COPYRIGHT 1984 1993 NAMCO LTD`, `HIROKI AOYAGI`) fixes the character set. Seven blocks decode as instructions and are recorded as code the tracer could not reach rather than being given a table name - `$C69B-$C6E7` was even called `LevelNameText` and is a straight-line routine. The rest: `$C0D7`/`$C0F7` the credit screen, `$C20D` the six level-start parameters, `$CA24` the `1UP`/`2UP` display text, `$CEE6`/`$D03D` the OAM records, `$D1C2`/`$D1EC` a 21-entry key table and its handler table, `$D3AE`/`$D67C`/`$D7A7`/`$D7F6`/`$DA6D`/`$DD90` the indirect `jmp ($0008)` dispatch tables, `$D550` a signed offset table, `$DAEA`/`$DAF4` the maze-tile coordinate and match tables, `$DDE2` the character-intro names (`THEY MEET`, `THE CHASE`, `JUNIOR`, `JUNIOR2`, `JUNIOR3`), `$E108` the actor sprite tiles, `$E3E5` the nametable attribute bytes written to `$23C0`/`$27C0`, `$E521` the sprite and actor tables plus the hidden `MENU`/`CHAR TEST`/`OBJ TEST` option stream, `$F492` the note-period table and `$FFFA` the interrupt vectors. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Pyramid (U) (Sachen)`** - all 31 of the data blocks the tracer left in `prg.s` are now named *and interpreted* under the new rule: the block names moved into `data_region_names.tsv` (so `make regen` reproduces them), 37 labels are applied by `name_data_region.py`, and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. Nine of the names the first Pass 3 gave were guesses and are corrected: `PaletteIndexTable` is `PaletteSlotRemapTable` (the 12 script colours mapped onto palette RAM slots), `SoundDeltaTable` is `SoundFrequencyDeltaTable` (five 24-bit decade multipliers - 100000, 10000, 1000, 100, 10 - added to and subtracted from the sound engine's 24-bit frequency), `SpriteTileTable` is `ActorSpriteTileTable`, `ActorAttributeTable` is `ActorDefinitionTable`, `ActorTypeTable` is `GridObjectTileTable` (the four tile codes the grid search compares against), `ActorOffsetTable` is `ActorDirectionOffsetTable` (the one-cell step in each of the four directions), `ActorBehaviourTable` is `ActorSpriteOamTable` (the OAM tile bytes of a spawned actor, not behaviour), `ValueRampTable` is `ActorSpriteYPositionTable` (the eight sprite Y positions, with `ActorSpriteXPositionTable` beside them) and `SoundSampleTable` is `SoundApuInitTable` (the four 4-byte blocks of initial APU register values). `ActorParameterTable` (`$CD97`) and `ActorFlagByte` (`$D72D`) were dropped: nothing in the dump reads them and their layout is not established. The four `jmp` trampoline tables now name their targets (`IncrementCounter16`, `AdvanceScriptPointer`, `ConvertNibbleToBcd`, `SetPpuCtrl`, `StoreOamEntry`, `WriteDigitString`, `AddDelta24`, ...), and both dispatch tables name all sixteen handlers (`MoveSpriteRight`, `SetPaletteBlock`, `TaskScheduler_Select`, `TaskScheduler_Next`, ...). The level script's format is read out of the interpreter at `$C090`: plain tile bytes, `$FE ` runs that repeat the previous tile, `$FE $00` to end a list. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Bomberman (U)`** - every data block in the project is now named *and* interpreted under the new rule: 26 labels renamed to ROM-specific names and 22 further labels applied with `name_data_region.py` (base `$C000`), giving 50 labelled blocks over 9,487 bytes; the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The audit trail in the last 515 emitting lines of `prg.s` (from `$E6C9` to the vectors at `$FFFA`) was damaged - `??` byte lists and PRG offsets up to $1A too low - and was repaired from the ca65 listing and validated byte-for-byte against `prg.bin`; **no instruction and no byte value changed**. The CHR is the oracle: the background pattern table at `$1000` carries an ASCII-mapped font at tiles `$20-$5F` (tile `$30` is `0`, `$41` is `A`), so every reversed ASCII string in the ROM reads directly - `StatusDigitTileTable` is the string `GAME:VER` stored backwards (rendering the eight tiles it names spells GAME:VER), `StatusScreenTextPointerTable` holds `TIME` and `LEFT` backwards, `StatusScreenValueAddressTable` is `STAGE` backwards and `StatusScreenPaletteAddressTable` is `BONUS:STAGE` backwards, while `MenuScreenConstructionData` and `CreditTextScript` are scripts of {PPU address, ASCII text} records carrying the game's own congratulation and copyright messages. The maze is a column-major bitmap in RAM at `$0200-$039F` (32 bytes per column, 13 columns), and `MazeColPtrLoTable`/`MazeColPtrHiTable` are the only pointers into it; `MazeBlockTileTable` holds 50 four-byte records of the 2x2 background tile block drawn for one cell and `MazeBlockPaletteTable` the matching palette number. `StageEnemyTypeTable` is a 50-row x 10-column roster of enemy types read through the zero-page pointer at `$97`; `SoundNoteTimerTable` is a 77-entry APU period table whose differences shrink by a factor of 2^(-1/12) per step, which is what identifies it as a chromatic scale; `SoundNoteConfigTable` holds eleven 8-byte per-note records; `SoundCommandHandlerTable` and `SoundVoiceHandlerTable` are the two 16-bit jump tables the sound engine dispatches through. Seven blocks are read by no instruction in this dump and are recorded in the project README with their ROM provenance rather than named after something the ROM does not support, and three spans ($D207-$D245, $D25E-$D5F1, $E5F6-$E641) turn out to be 6502 code the tracer never reached - they are flagged for re-classification. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `MAPPER8/Mermaids of Atlantis (U)`** - every one of the 76 data blocks the tracer left in the project is now named *and interpreted* under the new rule: 47 labels applied with `name_data_region.py`, and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The image is banked (mapper 8, eight 8 KB PRG banks, banks 0/4, 1/5, 2/6 and 3/7 visible in the same CPU window), so `name_data_region.py` grew a `--segment` option: without it the same address is named in every bank that covers it, and the assembler rejects the duplicate symbol. The fixed bank holds the game's tables, not the code banks - the digit table ("0123456789"), the actor script pointer table, the level layout and the sprite priority bytes all live in the bank that is always visible, which is why code in banks 0, 2 and 4 can reach them with a plain absolute operand. The PRG bank number table at `$8026` is one table in two copies (banks 0 and 4): eight MMC3 bank numbers followed by the same eight with bit 4 set, and the `sta $8026,x` beside the `lda` writes to ROM and is ignored by the hardware. The sprite tile tables are tiny and separate - eight tiles for the player, four for the second player, eight for the alternate set, eight for the HUD - and the code copies them into the OAM shadow at `$0338`. Bank 5 carries the sound engine's 56-entry APU pulse period table at `$A650`. The CHR is the oracle for the font: tiles `$30-$39` are the digits and `$40-$5A` the letters in ASCII order, which is what identifies the high-score table at `$9403` ("TOM1000", "20000JOE", "ABCDEFGHIJKLMNOPQRSTUVWXYZ@") and the menu strings at `$9254` ("1PLAYER", "2PLAYERS", "1234EASY", "HARD", "STACKED", "TUNE1".."TUNE3", "START") as plain ASCII. The 3,783-byte block at `$A000` in bank 1 is **not** text: its bytes fall in the ASCII range and end with `$FF`, but they do not spell English the way the tables above do, so it is recorded as `SpriteDefinitionRecordTable` with the field order explicitly unresolved rather than guessed at. 27 of the 76 blocks carry at least one name (47 labels in all); the other 49 are referenced by no instruction and are recorded in the project README as unreferenced with their ROM provenance - 27 of them decode as runs of instructions (code the tracer could not reach; the CDL capture the split came from never observed them either), one is `$FF` padding and the remaining 21 are unreferenced data. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/MUSCLE (U)`** - the project's 69 data blocks are now inventoried one by one under the new rule, with a **Pass 3 data-table inventory** in the project README giving every block's address range, size, name, structure and consuming instruction. The bytecode VM dominates the PRG, so most rows are token programs rather than tables: a byte in `$C8`-`$EF` is an opcode handled by `BytecodeHandlerTable` (`$9237`) and dispatched by `RunBytecodeInterpreter` (`$9AE8`), every other byte is a literal operand, and the listing above each program's `.byte` data spells the tokens out. Two blocks that had been given bytecode-program names turned out **not** to be bytecode and were renamed: `StandaloneBytecodeProgram_1` is now `DeathSequenceVramStream` (`$881F` - `PlayDeathSequence` points the source pointer at it and `WriteCompressedVramBlocks` decodes it as a compressed nametable RLE stream), and `StandaloneBytecodeProgram_3` is now `LevelOamTemplateTable` (`$AB14` - the routine at `$AB85` builds a source pointer from the level byte `$0326` and `LoadShadowOamBlock` copies 16 bytes into the OAM shadow at `$0300`; its last four bytes, `$AB64`-`$AB67`, are the OAM slot copied to `$031C`). Both contain no byte in `$C8`-`$EF`, which is the interpreter's own test, so the VM could never have run them. Two earlier claims in the project README were wrong and are corrected: `BytecodeProgramPointerTable` has 38 entries and all 38 are distinct, so it reaches 38 distinct programs (not 37), and the programs occupy 1,276 bytes (not 1,034). The inventory also records the one thing the stage payload will not give up - across all 16 KB of code there is exactly one operand in `$B0FB`-`$BEBF`, the `adc $BD28,x` at `$A063`, so the pool's record structure cannot be enumerated statically. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Urban Champion (JU)`** - every data block in the project is now named *and interpreted* under the new rule: 72 labels applied with `name_data_region.py` (base `$C000`), and the project README gained a **Pass 3 data-table inventory** giving every block's address range, size, name, structure and consuming instruction. The three Mesen CDL captures in the folder (`urban_baseline.cdl`, `urban_gameplay.cdl`, merged into `urban_merged.cdl`) are the oracle that separates real tables from unreached code - the merged capture marks `$CFF2-$D011` as code in full and executed, `$D0B7-$D0CC` and `$D46C-$D49C` as code, and records five blocks being read as data through an indirect read the tracer cannot follow (`$CC61-$CC66`, `$D21B-$D242`, `$DE24-$DE30`, `$E082-$E08C`). The sound engine falls out of the code: the routine at `$C1B2` takes a sound slot in `Y`, reads a record offset at `$C657+Y` and then a nine-byte record - a duration base and four 16-bit pointers, one per APU channel - and the sequences those pointers name are command streams in which `$00` ends the sequence, `$01-$7F` is a pitch, `$80-$BF` is a duration marker whose *following* byte is the pitch, `$C0-$FE` holds a note and `$FF` stops. A pitch indexes the 57-entry APU period table at `$C100` (high byte `$C100+n` with bit 3 forced on, low byte `$C101+n`; a zero low byte means "no note") and a duration marker indexes the 27-entry length table at `$C172` through `(marker & $0F) + the record's base byte`. The fight itself is two parallel 32-byte state blocks at `$0300` and `$0320`: the routine at `$D1CE` subtracts their X positions (`$0303`, `$0323`) and a hit registers only when the gap is 9-24 pixels, which is what identifies `$F696`/`$F6B6`/`$F6D6` as the three initial-value tables. 8 of the 40 tracer blocks carry at least one name (72 labels in all); the other 32 are referenced by no instruction and are recorded in the project README as unreferenced with their ROM provenance - eleven of them decode as runs of instructions (code the tracer could not reach, three of them confirmed executed by the CDL), five are read indirectly through an address the tracer cannot follow, and the rest are unreferenced data or stray bytes. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Xevious (U)`** - every data block in the project is now named *and interpreted* under the new rule: 33 labels applied with `name_data_region.py` (base `$8000`) and 19 of the generic names the first Pass 3 gave the tables replaced, with a description of what each table's bytes are and which instruction reads it. The Mesen CDL capture (`xevious_mesen.cdl`) is the oracle that separates real tables from unreached code. The interpretation is driven by the CHR: `PPUCTRL` selects the sprite pattern table at `$0000`, so tiles `$00-$24` are the font (digits `$00-$09`, letters `$0A-$23`, space `$24`), `$2C-$2F` the little aliens, `$6E` the solid fill tile and `$6F-$72` the Xevious ground creatures, while in the background table `$00`/`$0C`/`$0D` are the sky gradients and `$6E-$72` the terrain shapes - which is what identifies `$8000-$9FFF` (8192 bytes = 32 columns x 256 rows) as the level's background tilemap. The 44-entry table at `$CA0A` is the opcode dispatch of a bytecode VM (the VM at `$C9E3` fetches an opcode from the script pointer and jumps through it with `jmp ($0004)`), and the 16 scripts at `$ACFB-$B8AA` are the stage scripts it executes. The sprite record is four bytes, Y / tile / attribute / X - proven by the message routine at `$F5B7` and the sprite-layout reader at `$F930` - which fixes the layout of the records at `$A50B`, `$A62A`, `$A6CE`, `$A7AC`, `$EF3B`, `$F9BD` and `$D83B` (count byte plus 4-byte records). The table at `$F136` is a sine wave: 32 four-byte records of two signed 16-bit deltas whose X values run 0, 50, 98, 142, 180, 212, 236, 250, 256 and back while Y runs the same series in reverse - the flight path of a sine-wave enemy. The message pool at `$F5D8` decodes to PLAYER, GAME OVER, READY, SOLVALOU, LEFT and PAUSE, and `$FC45` holds the nine-byte RAM signature "Kei Cross" that the reset routine copies to `$0100-$0108` and compares against. 46 of the 129 tracer blocks carry at least one name; one more ($C30E) is read as the last byte of the table at $C28F, and the remaining 82 are referenced by no instruction and are recorded in the project README as unreferenced - 38 are code the tracer could not reach (their bytes decode as runs of instructions but the CDL never fetched them) and 44 are unreferenced data, all with their ROM provenance and the CDL's verdict on them. The project README gained a **Pass 3 data-table inventory** table giving every block's address range, size, name, structure and consuming instruction. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Hydlide (U)`** - every data block in the project is now named *and interpreted* under the new rule: 54 labels applied with `name_data_region.py` (base `$8000`). The Mesen CDL capture (`hydlide_gameplay.cdl`) is the oracle that separates real tables from unreached code: it marks `$DC8D-$DD67` and `$C7BB-$C7C0` entirely as code, and the dispatch tables are split from the code they point at - `$8B4A` (35-entry object-type dispatch), `$BB2D`, `$C0A9`, `$CB7D`, `$CCAD`, `$CFFB` (22-entry monster dispatch tables), `$C96E` (per-monster hit box) and `$D60C` (26-entry object-script dispatch). The APU tables are named from the code that writes them: `$E2CE` is the 49-entry note-period table loaded into `APUPULSE2TIMERLO/HI`, `$E330`/`$E348` are the two music-script pointer tables, `$E360`/`$E378` the pulse-2 and triangle presets and `$E390` the tempo table. The text is decoded by the ROM's own contents: `$8560` is the 96-byte `HYDLIDE (C)1986 T&E SOFT Programmed by EIJI KATO` credit line copied to RAM `$0100` at boot, `$9861` is the word pool the message box draws from (`SWORD` ... `DEMONSTRATION`, so `YOU GET A SWORD` is a run of consecutive words), `$D237` is the monster roster, `$A87F` the pause menu and `$B955` the 28-character password alphabet. 15 of the 26 tracer blocks carry at least one name; the other 11 are referenced by no instruction and are recorded in the project README as unreferenced - all eleven are code the tracer could not reach, and the CDL capture marks `$DC8D-$DD67` and `$C7BB-$C7C0` as code in full. The project README gained a **Pass 3 data-table inventory** table giving every block's address range, size, name, structure and consuming instruction. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data-table interpretation - `NROM/Dig Dug II (U) [!]`** - every data block in the project is now named *and interpreted* under the new rule: 72 labels applied with `name_data_region.py` (base `$8000`), replacing the five generic names the first Pass 3 gave it. The big `$8000-$A52C` block is split into its real structures - a 72-entry round stage-map pointer table (`$8000`), 72 fixed 106-byte stage-map slots (`$8090`), a 72-entry round enemy-config pointer table (`$9E60`), the enemy-roster records (`$9EF0`), the `COPYRIGHT 1982 1986 NAMCO LTD.` string (`$A4D8`), the 32-byte default palette image copied to `$0200` at reset (`$A509`) and the 4-byte cold-boot signature `K.N.` (`$A529`). The round count is 72 (`$0259` wraps at `$48` in `$C8EA`), which is what the round-indexed palette, monster-type and enemy-offset tables are keyed on. 34 of the 37 tracer blocks carry at least one name; `$F69C-$F7C8`, `$F924-$F980` and `$F99A-$F9B8` are referenced by no instruction and are recorded in the project README as unreferenced (the last two are code the tracer could not reach, confirmed by the Mesen CDL). The project README gained a **Pass 3 data-table inventory** table giving every block's address range, size, name, structure and consuming instruction. `make` still prints **Byte-identical ROM verified.** - **Pass 3 data tables interpreted (7 projects)** - the data blocks are now named from the evidence - the structure of the bytes and the instruction that reads them - rather than from a guess about what the game's data usually looks like, and every block that no instruction reads is recorded as unreferenced data with its ROM provenance. Each project `README.md` gains a "Pass 3 data-table inventory" listing every block with its address range, size, name, structure and consuming instruction. ### 2026-10-21 - Give every routine header a contract that is its own - **Routine headers rewritten across the promoted tier** - every routine header in the tier carried the Pass-1 template ("Pass 1 reachable entry at $C000", "Behavior is conservatively represented by the decoded instructions below; semantic naming will be refined in Pass 2 and Pass 3", "Undetermined in Pass 1"). That is filler, and it describes the process rather than the ROM. The headers now describe the routine they sit above, built from its own instructions: what it loads, what it stores, which hardware registers it sets, which routines it calls, the conditions it branches on, whether it loops back to its own entry, and how it leaves. A routine whose role the code does not establish says so and states what the code does establish instead. Every header's `Clobbers:` block is derived by `derive_clobbers.py`, which resolves the operand through `locals.inc`/`symbols.inc`, follows the real control flow and models register preservation. `derive_clobbers.py` and `derive_routine_docs.py` now accept both header layouts and both separator spellings the repository uses. Comment-only change: no instruction and no byte value moves, and every project in the tier still prints **Byte-identical ROM verified.** - **Routine headers rewritten (7 projects)** - every routine header now describes its own routine: the zero-page and RAM fields it reads and writes, the hardware registers it sets, the routines it calls, the conditions it branches on and how it leaves. A header that could not establish the routine's role says so and states what the code does establish instead. No header mentions a pass any more - the source describes the ROM, and the process history lives in this file. ### 2026-10-21 - The seven NROM projects promoted out of `_wip/` - **Port Test Cartridge (U)** - Pass 2 complete: all 28 `Label_XXXX` definitions renamed (Reset, WaitForVblank, IrqHandler, NmiHandler, NmiMode0/1/2, NmiFrameCheck, UpdatePpuRegisters, StrobeJoypad, ReadJoypadSerial, StoreJoypadState, ComputeJoypadChanges, ShiftBits8Setup/Loop, ShiftBits8Setup2/Loop2, DecodeJoypadButton, SetByteFF, ReturnFromSub, PushReturnAddress, IndirectJumpViaZp, SetupPpuAddress, ConfigurePpuCtrl, ShiftTileIndexBits, BranchOnCarrySet, WritePpuDataBytes, DispatchVramCommand, MemsetZeroSetup/Loop), all 17 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Port Test Cartridge (U)** - Pass 3 complete: 5 zero-page operands substituted via `apply_symbols.py` (0 residue), 4 data regions named (TilePatternTable $8000, VramCommandStream $CD0C, SpriteOamData $CECF, ResetVectors $FFFA), clobbers blocks derived for all 31 routine headers via `derive_clobbers.py`, audit comments aligned; `make` stays byte-identical. - **Port Test Cartridge (U)** - promoted out of `_wip/NROM/` to `NROM/Port Test Cartridge (U)/`; root `README.md` inventory updated (row 42 in main table, row 55 in status table); `WIP.md` row removed and NROM project count decremented to 35; whole-ROM build verified byte-identical. - **10-Yard Fight (U)** - Pass 2 complete: all 466 `Label_XXXX` definitions renamed (Reset, NmiHandler, IrqHandler, plus 463 `SubC` entries), all 351 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **10-Yard Fight (U)** - Pass 3 complete: 8 zero-page operands substituted via `apply_symbols.py` (0 residue), 4 data regions named (TitleScreenData $8329, PlayerData $83EC, ScoreTable $844A, RomDataArea $CC0E), clobbers blocks derived for all 466 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **10-Yard Fight (U)** - promoted out of `_wip/NROM/` to `NROM/10-Yard Fight (U)/`; root `README.md` inventory updated (row 43 in main table, row 56 in status table); `WIP.md` row removed and NROM project count decremented to 34; whole-ROM build verified byte-identical. - **Chubby Cherub (U)** - Pass 2 complete: all 755 `Label_XXXX` definitions renamed (Reset, NmiHandler, IrqHandler, plus 752 `SubC` entries), all 477 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Chubby Cherub (U)** - Pass 3 complete: 10 zero-page operands substituted via `apply_symbols.py` (0 residue), 5 data regions named (SpriteData $8290, LevelData $8CC4, PaletteData $8EF1, EnemyData $9335, BackgroundData $9703), clobbers blocks derived for all 755 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **Chubby Cherub (U)** - promoted out of `_wip/NROM/` to `NROM/Chubby Cherub (U)/`; root `README.md` inventory updated (row 44 in main table, row 57 in status table); `WIP.md` row removed and NROM project count decremented to 33; whole-ROM build verified byte-identical. - **Dig Dug II (U) [!]** - Pass 2 complete: all 1295 `Label_XXXX` definitions renamed (Reset, NmiHandler, plus 1293 `SubC` entries), all 337 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Dig Dug II (U) [!]** - Pass 3 complete: 14 zero-page operands substituted via `apply_symbols.py` (0 residue), 5 data regions named (TitleScreenData $8000, EnemyPatternTable $A911, LevelData $AA30, SpriteData $AE47, SoundData $AF14), clobbers blocks derived for all 1295 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **Dig Dug II (U) [!]** - promoted out of `_wip/NROM/` to `NROM/Dig Dug II (U) [!]/`; root `README.md` inventory updated (row 45 in main table, row 58 in status table); `WIP.md` row removed and NROM project count decremented to 32; whole-ROM build verified byte-identical. - **Gyromite (JUE)** - Pass 2 complete: all 234 `Label_XXXX` definitions renamed (Reset, NmiHandler, plus 232 `SubC` entries), all 117 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Gyromite (JUE)** - Pass 3 complete: 2 zero-page operands substituted via `apply_symbols.py` (0 residue), 6 data regions named (PlayerStateData $80D9, TilePatternData $813C, BackgroundMapData $8168, SpriteData $8D4E, EnemyData $8E8F, LevelLayoutData $9109), clobbers blocks derived for all 234 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **Gyromite (JUE)** - promoted out of `_wip/NROM/` to `NROM/Gyromite (JUE)/`; root `README.md` inventory updated (row 46 in main table, row 59 in status table); `WIP.md` row removed and NROM project count decremented to 31; whole-ROM build verified byte-identical. - **Hydlide (U)** - Pass 2 complete: all 712 `Label_XXXX` definitions renamed (Reset, NmiHandler, plus 710 `SubC` entries), all 907 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Hydlide (U)** - Pass 3 complete: 32 zero-page operands substituted via `apply_symbols.py` (0 residue), 2 data regions named (SpritePatternData $83C7, MonsterNameStrings $83F7 - the ROM spells out LIFESTREXPMAGICMONSTER and the fire/ice/wave/flash elements), clobbers blocks derived for all 712 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **Hydlide (U)** - promoted out of `_wip/NROM/` to `NROM/Hydlide (U)/`; root `README.md` inventory updated (row 47 in main table, row 60 in status table); `WIP.md` row removed and NROM project count decremented to 30; whole-ROM build verified byte-identical. - **Pass 1 re-split from a Mesen CDL capture (7 projects)** - the seven projects promoted on 2026-10-21 carried a Pass 1 split that classified 0.7% to 24.9% of the PRG as code: the static trace reached the reset stub and the vblank wait and nothing else, because each game's main code sits behind a computed dispatch. A Mesen Code Data Logger capture of a real play session of each game marks the code the CPU actually ran, and the capture's **jump-target flag** names the addresses the CPU branched to - the seeds the static tracer was missing. Re-traced with them (`nesdis.py --extra-seed`), the splits become: Ice Hockey (U) 3.5% -> 39.5%, 10-Yard Fight (U) 11.2% -> 32.0%, Chubby Cherub (U) 20.1% -> 41.4%, Dig Dug II (U) [!] 24.9% -> 53.5%, Gyromite (JUE) 4.8% -> 31.0%, Hydlide (U) 22.3% -> 47.8%, Port Test Cartridge (U) 0.7% -> 3.8%. The tracer's own rules give the right instruction boundaries, so no reclassifier override and no raw relative offset is needed. Pass 2 and Pass 3 are re-applied over each new split and every build still prints **Byte-identical ROM verified.** - **Ice Hockey (U)** - Pass 2 complete: all 220 `Label_XXXX` definitions renamed (Reset, NmiHandler, plus 218 `SubC` entries), all 72 raw `jsr`/`jmp` call operands resolved to symbolic labels, zero `Label_` residue; `make` stays byte-identical. - **Ice Hockey (U)** - Pass 3 complete: 3 zero-page operands substituted via `apply_symbols.py` (0 residue), 3 data regions named (SpriteOamData $8059, IceRinkTileData $8213, PlayerPatternData $82D6), clobbers blocks derived for all 220 routine headers via `derive_clobbers.py`, routine headers rewritten with Pass 3 descriptions; audit comments aligned; `make` stays byte-identical. - **Ice Hockey (U)** - promoted out of `_wip/NROM/` to `NROM/Ice Hockey (U)/`; root `README.md` inventory updated (row 48 in main table, row 61 in status table); `WIP.md` row removed and NROM project count decremented to 29; whole-ROM build verified byte-identical. ### 2026-10-20/21 - The `_wip/` tier: Pass 1 for 29 projects, then the promotion of 13 NROM projects - **Pass 1/2/3 _wip (13 promoted NROM projects)** - full semantic naming for all 13: every data table named (`name_data_region.py`), every provable zero-page slot given a behavioural alias (`apply_zp_aliases.py`, 14-22 aliases each), and the splits re-traced so the unreached code became code. Coverage now: Elevator Action 59.3%, Sqoon 56.4%, Soccer 55.3%, Tag Team Wrestling 52.4%, Kung Fu 52.1%, 1942 52.1%, Kung Fu (a1) 51.7%, Othello 43.4%, Magmax 35.4%, Galaga 34.5%, Stack-Up 32.3%, Slalom 32.2%, Ms Pac-Man (Namco) 30.9%. All 13 stay byte-identical; project READMEs carry the new coverage numbers and a named data-table inventory. - **Pass 2/3 _wip (5 CNROM)** - `Tiger-Heli (U)`, `Paperboy (U)`, `Last Starfighter, The (U)`, `Mighty Bomb Jack (U)` and `Kung-Fu Heroes (U)` converted to CPU-address linker layout and advanced through Pass 2 and Pass 3; `make` stays byte-identical. - **Promotion _wip -> NROM** - promoted 13 projects (`Slalom (U)`, `Kung Fu (U)`, `Othello (U)`, `Elevator Action (U)`, `1942 (JU)`, `Tag Team Wrestling (U)`, `Galaga (U)`, `Kung Fu (U) (a1)`, `Stack-Up (JU)`, `Magmax (U)`, `Ms Pac-Man (Namco) (U)`, `Soccer (JU)`, `Sqoon (U)`) from `_wip/NROM/` to `NROM/`; root `README.md` inventory updated (rows 42-54). - **Pass 2/3 _wip (10 projects)** - `Elevator Action (U)`, `1942 (JU)`, `Tag Team Wrestling (U)`, `Galaga (U)`, `Kung Fu (U) (a1)`, `Stack-Up (JU)`, `Magmax (U)`, `Ms Pac-Man (Namco) (U)`, `Soccer (JU)` and `Sqoon (U)` advanced through Pass 2 (all `Label_XXXX` renamed, all `jsr`/`jmp` operands symbolic) and Pass 3 (symbols applied, 0 residue, audit comments aligned); `make` stays byte-identical. - **Pass 3 _wip** - `Slalom (U)`, `Kung Fu (U)` and `Othello (U)` advanced to Pass 3: hardware-register operands resolved via `apply_symbols.py` (0 residue), zero-page slots documented in `locals.inc`, audit comments aligned; `make` stays byte-identical. - **Pass 2 _wip** - `Slalom (U)`, `Kung Fu (U)` and `Othello (U)` promoted from Pass 1 to Pass 2: every `Label_XXXX` definition renamed (83 / 152 / 157 entries), all `jsr`/`jmp` operands resolved to symbolic labels, internal branch targets given role-based names; `make` stays byte-identical. - **Pass 1 _wip (bulk)** - completed Pass 1 (code/data split with canonical audit comments) for all remaining 810 `_wip/` skeletons, across all 29 mapper folders. Every project now rebuilds byte-identical (`make wip`: 851 of 851 passed). `nesdis.py` was patched to define labels at branch targets landing in data regions; `banked_dis.py` gained per-bank sizing and PRG-sized CDL support (used for `Wayne Gretzky Hockey (U)`); `derive_branch_names.py` and `apply_pass2_map.py` learned the banked `BANK:` audit field. Two latent failures in the original 41 (`Adventures of Dino Riki`, `8 Eyes`) were repaired. - **Docs** - tier counts refreshed to the post-promotion state across `AGENTS.md`, root `README.md`, `WIP.md`, `TODO.md` and `CHANGELOG.md`: **41 promoted projects** (38 under `NROM/`, plus `CNROM/World Class Track Meet (U)`, `UNROM/Zanac (U)` and `MAPPER8/Mermaids of Atlantis (U)`) and **851 `_wip/` projects**, every one of them a Pass 0 skeleton. The stale "39/40 promoted", "853 `_wip`", "`Golf (JU)` is still in `_wip/`" and "`Golf (JU)` is the last pre-canonical project" statements are corrected. - **Root `README.md`** - the promoted inventory gained the missing `World Class Track Meet (U)` row (row 41, mapper 3, 32 KB PRG / 32 KB CHR), and the "All 38 are mapper 0 / 16 KB PRG" sentence is replaced with the actual split (36 of the 41 rows are mapper 0 / 16 KB; `Xevious (U)` and `Volleyball (U)` are 32 KB, `World Class Track Meet (U)` is mapper 3, `Zanac (U)` mapper 2 and `Mermaids of Atlantis (U)` mapper 8). - **WIP.md** - twenty-nine projects updated from Pass 0 to Pass 1: `1942 (JU)`, `Hydlide (U)`, `Othello (U)`, `Ice Hockey (U)`, `Super Mario Bros (JU) (PRG 0)`, `Tag Team Wrestling (U)`, `Soccer (JU)`, `Slalom (U)`, `Seicross (U)`, `Spy Vs Spy (U)`, `Sqoon (U)`, `Spelunker (U) [!]`, `10-Yard Fight (U)`, `Chubby Cherub (U)`, `Dig Dug II (U) [!]`, `Elevator Action (U)`, `Gyromite (JUE)`, `Kung Fu (U) (a1)`, `Mach Rider (JU)`, `Mach Rider (JU) (a1)`, `Magmax (U)`, `Magmax (U) (Prototype)`, `Ms Pac-Man (Namco) (U)`, `Ms Pac-Man (U)`, `NES Test Cart (Official Nintendo) (U)`, `Port Test Cartridge (U)`, `Pro Action Replay (No Cart Present) (U)`, `Pro Action Replay (Rev B) (U)`, `Stack-Up (JU)`. - **_wip/NROM/1942 (JU)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Hydlide (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Othello (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Ice Hockey (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Super Mario Bros (JU) (PRG 0)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Tag Team Wrestling (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Soccer (JU)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Slalom (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Seicross (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Spy Vs Spy (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Sqoon (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Spelunker (U) [!]/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Pipe Dream (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/10-Yard Fight (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Chubby Cherub (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Dig Dug II (U) [!]/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Elevator Action (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Gyromite (JUE)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Kung Fu (U) (a1)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Mach Rider (JU)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Mach Rider (JU) (a1)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Magmax (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Magmax (U) (Prototype)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Ms Pac-Man (Namco) (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Ms Pac-Man (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/NES Test Cart (Official Nintendo) (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Port Test Cartridge (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Pro Action Replay (No Cart Present) (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Pro Action Replay (Rev B) (U)/README.md** - updated from Pass 0 to Pass 1. - **_wip/NROM/Stack-Up (JU)/README.md** - updated from Pass 0 to Pass 1. - **1942 (JU)** - Pass 1 complete. Code/data split: 877 instructions / 1728 bytes (5.3%). Vectors: NMI=$C030 RESET=$BF90 IRQ=$BF90. 52 zero-page aliases generated. `make` produces byte-identical ROM. - **Hydlide (U)** - Pass 1 complete. Code/data split: 7318 instructions / 14885 bytes (45.4%). Vectors: NMI=$8003 RESET=$8000 IRQ=$8003. 175 zero-page aliases generated. `make` produces byte-identical ROM. - **Othello (U)** - Pass 1 complete. Code/data split: 1042 instructions / 2178 bytes (6.6%). Vectors: NMI=$D6B4 RESET=$8000 IRQ=$804E. 41 zero-page aliases generated. `make` produces byte-identical ROM. - **Ice Hockey (U)** - Pass 1 complete. Code/data split: 1160 instructions / 2524 bytes (7.7%). Vectors: NMI=$805A RESET=$8000 IRQ=$8000. 34 zero-page aliases generated. `make` produces byte-identical ROM. - **Super Mario Bros (JU) (PRG 0)** - Pass 1 complete. Code/data split: 1067 instructions / 2320 bytes (7.1%). Vectors: NMI=$8082 RESET=$8000 IRQ=$FFF0. 24 zero-page aliases generated. `make` produces byte-identical ROM. - **Tag Team Wrestling (U)** - Pass 1 complete. Code/data split: 799 instructions / 1756 bytes (5.4%). Vectors: NMI=$FFCD RESET=$FD43 IRQ=$FD43. 49 zero-page aliases generated. `make` produces byte-identical ROM. - **Soccer (JU)** - Pass 1 complete. Code/data split: 1212 instructions / 2462 bytes (7.5%). Vectors: NMI=$809F RESET=$8000 IRQ=$8000. 83 zero-page aliases generated. `make` produces byte-identical ROM. - **Slalom (U)** - Pass 1 complete. Code/data split: 678 instructions / 1461 bytes (4.5%). Vectors: NMI=$8123 RESET=$8050 IRQ=$FEDF. 33 zero-page aliases generated. `make` produces byte-identical ROM. - **Seicross (U)** - Pass 1 complete. Code/data split: 2260 instructions / 4848 bytes (14.8%). Vectors: NMI=$8858 RESET=$8000 IRQ=$FD0E. 93 zero-page aliases generated. `make` produces byte-identical ROM. - **Spy Vs Spy (U)** - Pass 1 complete. Code/data split: 7914 instructions / 16263 bytes (49.6%). Vectors: NMI=$8066 RESET=$8000 IRQ=$FFC7. 190 zero-page aliases generated. `make` produces byte-identical ROM. - **Sqoon (U)** - Pass 1 complete. Code/data split: 1568 instructions / 3138 bytes (9.6%). Vectors: NMI=$8014 RESET=$8000 IRQ=$80F1. 101 zero-page aliases generated. `make` produces byte-identical ROM. - **Spelunker (U) [!]** - Pass 1 complete. Code/data split: 5418 instructions / 11052 bytes (33.7%). Vectors: NMI=$84C6 RESET=$8000 IRQ=$8513. 179 zero-page aliases generated. `make` produces byte-identical ROM. - **Pipe Dream (U)** - Pass 1 complete. Code/data split: 7892 instructions / 16527 bytes (50.4%). Vectors: NMI=$8FEA RESET=$8000 IRQ=$912B. 224 zero-page aliases generated. `make` produces byte-identical ROM. - **10-Yard Fight (U)** - Pass 1 complete. Code/data split: 3674 instructions / 7541 bytes (23.0%). Vectors: NMI=$8471 RESET=$8000 IRQ=$84FF. 128 zero-page aliases generated. `make` produces byte-identical ROM. - **Chubby Cherub (U)** - Pass 1 complete. Code/data split: 6589 instructions / 13471 bytes (41.1%). Vectors: NMI=$82B7 RESET=$8000 IRQ=$8331. 149 zero-page aliases generated. `make` produces byte-identical ROM. - **Dig Dug II (U) [!]** - Pass 1 complete. Code/data split: 8146 instructions / 17500 bytes (53.4%). Vectors: NMI=$A749 RESET=$A52D IRQ=$A52D. 91 zero-page aliases generated. `make` produces byte-identical ROM. - **Elevator Action (U)** - Pass 1 complete. Code/data split: 741 instructions / 1431 bytes (4.4%). Vectors: NMI=$8081 RESET=$8000 IRQ=$8000. 51 zero-page aliases generated. `make` produces byte-identical ROM. - **Gyromite (JUE)** - Pass 1 complete. Code/data split: 1584 instructions / 3314 bytes (10.1%). Vectors: NMI=$8079 RESET=$8000 IRQ=$8000. 67 zero-page aliases generated. `make` produces byte-identical ROM. - **Kung Fu (U) (a1)** - Pass 1 complete. Code/data split: 893 instructions / 1931 bytes (5.9%). Vectors: NMI=$82D6 RESET=$8258 IRQ=$FFF0. 48 zero-page aliases generated. `make` produces byte-identical ROM. - **Mach Rider (JU)** - Pass 1 complete. Code/data split: 6174 instructions / 12738 bytes (38.9%). Vectors: NMI=$8070 RESET=$8000 IRQ=$8147. 199 zero-page aliases generated. `make` produces byte-identical ROM. - **Mach Rider (JU) (a1)** - Pass 1 complete. Code/data split: 6174 instructions / 12738 bytes (38.9%). Vectors: NMI=$8070 RESET=$8000 IRQ=$8147. 199 zero-page aliases generated. `make` produces byte-identical ROM. - **Magmax (U)** - Pass 1 complete. Code/data split: 1387 instructions / 2900 bytes (8.9%). Vectors: NMI=$BD33 RESET=$BC33 IRQ=$BC33. 63 zero-page aliases generated. `make` produces byte-identical ROM. - **Magmax (U) (Prototype)** - Pass 1 complete. Code/data split: 1387 instructions / 2900 bytes (8.9%). Vectors: NMI=$BD33 RESET=$BC33 IRQ=$BC33. 63 zero-page aliases generated. `make` produces byte-identical ROM. - **Ms Pac-Man (Namco) (U)** - Pass 1 complete. Code/data split: 1399 instructions / 2950 bytes (9.0%). Vectors: NMI=$C107 RESET=$FFF7 IRQ=$C198. 53 zero-page aliases generated. `make` produces byte-identical ROM. - **Ms Pac-Man (U)** - Pass 1 complete. Code/data split: 4179 instructions / 8726 bytes (26.6%). Vectors: NMI=$FB86 RESET=$FEE0 IRQ=$FBE1. 85 zero-page aliases generated. `make` produces byte-identical ROM. - **NES Test Cart (Official Nintendo) (U)** - Pass 1 complete. Code/data split: 1706 instructions / 3603 bytes (11.0%). Vectors: NMI=$8328 RESET=$8370 IRQ=$8370. 47 zero-page aliases generated. `make` produces byte-identical ROM. - **Port Test Cartridge (U)** - Pass 1 complete. Code/data split: 216 instructions / 439 bytes (1.3%). Vectors: NMI=$CC49 RESET=$C000 IRQ=$CC17. 28 zero-page aliases generated. `make` produces byte-identical ROM. - **Pro Action Replay (No Cart Present) (U)** - Pass 1 complete. Code/data split: 0 instructions / 0 bytes (0.0%). Vectors: NMI=$0000 RESET=$0000 IRQ=$0000. 1 label generated. `make` produces byte-identical ROM. - **Pro Action Replay (Rev B) (U)** - Pass 1 complete. Code/data split: 5156 instructions / 6321 bytes (19.3%). Vectors: NMI=$F021 RESET=$808A IRQ=$F020. 126 zero-page aliases generated. `make` produces byte-identical ROM. - **Stack-Up (JU)** - Pass 1 complete. Code/data split: 1086 instructions / 2260 bytes (6.9%). Vectors: NMI=$81AD RESET=$8100 IRQ=$81E6. 63 zero-page aliases generated. `make` produces byte-identical ROM. ### 2026-10-21 - The tools the new rules needed - **Docs** - `PROMPTS.md` gains two Pass 3 requirements, "Data tables are interpreted, not merely labelled" and "Method comments describe the method", with the two matching verification-checklist items; `AGENTS.md` carries the same two rules in its irreversible conventions. - **_tools/derive_routine_docs.py** - new tool: writes the `Global:`/`Local:`/`Parameters:`/`Returns:` fields of every routine header from the instructions that routine executes. It reuses `derive_clobbers.py`'s parser and control-flow analysis, so the addressing mode of every instruction is exact and the register-preservation model is the one the `Clobbers:` line already uses. - **_tools/analyze_data_blocks.py** - new tool: reports, for every data block, the instructions that read it (the tracer's own reference lines plus a scan of every absolute operand in the source), whether the bytes are a pointer table into the PRG, a table of small values, padding, or a run that decodes as instructions - in which case the block is code the tracer could not reach rather than data. - **_tools/cdl_seeds.py** - new tool: reads the jump-target addresses out of a Mesen CDL capture, which are the seeds a static tracer needs to reach code behind a computed dispatch. - **_tools/plan_overrides.py** - new tool: lists the `--override` arguments a `cdl_reclass.py` run would need to force the branch targets the reclassifier's linear walk cannot land on. - **_tools/name_data_region.py** - the PRG base is now an option (`--base`, default `0xC000`): it used to hardcode `$C000`, so on a 32 KB NROM image mapped at `$8000` it wrote the audit comment's PRG offset as a negative number. The 30 rows it had broken are repaired and re-checked against `prg.bin`. commit subject is the heading; the entries are the changes it carried. ### 2026-10-21 - Xevious (U): re-split Pass 1 from the Mesen CDL and decode the data tables - **Xevious (U)** - Pass 1 re-split from the Mesen CDL (`xevious_mesen.cdl`) with `_tools/cdl_reclass.py`: 1,158 bytes data→code, 48 code→data, 42 labels emitted and 3 branch targets labelled; code 11,321→12,455 bytes (34.5%→38.0%), instructions 5,513→6,002. The 45 recovered entry points are named `Sub_XXXX`, so no `Label_XXXX` remains; six CDL-silent raw call operands are documented as residue. - **Xevious (U)** - the 4,448-byte block at $A74B-$B8AA is split into six proven tables: `VramWritePtrTable` (15 pointers), `VramWriteData` (descriptors written to $2006/$2007), `DataStreamPtrTable` (16 pointers), `ObjTilePtrTable` (99 pointers), `ObjTileData` and `DataStreamData`. The $D833 pointer table is corrected to base $D833 (4 entries; the old `ObjCodePtrTable` at $D834 was one byte late). $F957-$FAF5 is split into `ObjSpeedTable`, `ObjSpeedTable2`, `SpriteLayoutPtrTable` (19 pointers) and `SpriteLayoutData`; $FEE6-$FF1C into `PaletteData` and `OamAttrTable`. `$F5D8` is decoded as `TextTileData` (font tile indices: PLAYER, GAME OVER, READY, SOLVALOU, LEFT, PAUSE); `$F53C` becomes `AttrAddrTable`, `$F89E` `HandlerPtrTable` and `$EF3B` `SpriteLayoutData2`. Project README rewritten with the corrected stats, a 32-entry table inventory and the Pass 1 residue. - **_tools/cdl_reclass.py** - preserve the data label(s) that sit between a `; ---- data` header and its first `.byte` row when the block stays data. The $DF78 split dropped `Data_DF78`, which unchanged code still referenced, so the rebuild failed with an undefined symbol. - **_tools/resolve_raw_branches.py** - handle negative relative offsets (`beq *-$0B`); the pattern only matched `*+$NN` and `*$NN`, so backward branches could not be resolved. ### 2026-10-21 - Golf (JU) and World Class Track Meet (U): Pass 2/3 complete, promoted from `_wip/` - **World Class Track Meet (U)** - Pass 2 complete: all 884 `Label_` definitions replaced (651 semantic routine names + 233 `Sub_`), all 731 raw `$XXXX` jsr/jmp call operands resolved to symbolic labels, all branch targets given role-based names; `routinemap.tsv` added; `make` stays byte-identical. - **World Class Track Meet (U)** - Pass 3 complete: 24 semantic zero-page aliases applied via `apply_symbols.py` (0 residue), 78 neutral `ZpXX` slots documented with reasons in `locals.inc`, all hardware-register operands use `symbols.inc` names, project README rewritten with Pass 2/Pass 3 sections and data-table inventory; project is ready for promotion out of `_wip/CNROM/`. - **World Class Track Meet (U)** - project README now says what the five entries of the event table at $F796 line up with (the five *single* events of the six-item menu) and marks the sixth item, the "olympics", as not yet proven: the menu's own code is still outside the traced set, so the mode that schedules several of the five task slots at once is the expected answer, not a proven one. - **Golf (JU)** - audit comments converted from pre-canonical (`; C000: A9 00`) to the canonical `; CPU:$C000 PRG:0000 A9 00` form at column 35 via `_tools/repair_audit_comments.py` then `_tools/align_audit_comments.py`; 4,156 comments re-aligned, comment/whitespace only, whole-ROM build stays byte-identical. - **Golf (JU)** - 339 internal branch-target `Label_` labels given role-based names (`_Loop`, `_OnZero`, …) via `_tools/derive_branch_names.py` from `routinemap.tsv` and applied with `_tools/apply_pass2_map.py`; 10 fall-through-only labels keep their `Label_` definitions; 0 raw `$XXXX` branch/jump operands remain. - **Golf (JU)** - 275 zero-page operands substituted with `locals.inc` aliases via `apply_symbols.py`; all hardware-register operands already symbolic; 0 multi-alias residue; no unexplained numeric operands left. - **Golf (JU)** - project README updated: Pass 2/Pass 3 marked complete, audit-comment and Pass 3 sections rewritten, `routinemap.tsv` added; project is ready for promotion out of `_wip/NROM/`. - **Golf (JU)** and **World Class Track Meet (U)** - promoted out of `_wip/` (`git mv` to `NROM/Golf (JU)/` and `CNROM/World Class Track Meet (U)/`); leftover `_wip/` folders removed; root `README.md` inventory rows added; `WIP.md` rows removed. - **Xevious (U)** - runs of `$FF` padding replaced with `.res , $FF` in `prg.s`; whole-ROM build stays byte-identical. ### 2026-10-21 - World Class Track Meet (U): Pass 1, the code/data split - **World Class Track Meet (U)** - Pass 1 done. The code/data split is 8,400 - **World Class Track Meet (U)** - four dispatch tables had to be seeded by - **World Class Track Meet (U)** - the tracer runs with `--undocumented`: the - **World Class Track Meet (U)** - `$C0BE-$C0C5` is forced back to data: the - **World Class Track Meet (U)** - `make pass1` regenerates `prg.s` and - **_tools/nesdis.py** - new `--mapping-note` option, so the header's "PRG - **Docs** - the project README records the split, the four dispatch tables, the ### 2026-10-21 - Balloon Fight (JU): repair the audit trail and complete the routine headers - **Balloon Fight (JU)** - the per-instruction audit trail was repaired. 5,944 of the 6,007 comments had been stale by two bytes since the three-byte `.byte "HAL"` signature at $C082-$C084 was counted as one, so every comment from `ResetInitializationData` ($C085) onward named the wrong CPU address and PRG offset; the routine headers and the linker labels were right, so the source and its own headers disagreed. The trail was rebuilt from the `ca65 -l` listing with `_tools/repair_audit_comments.py` and every rewritten comment was re-checked against `prg.bin`. - **Balloon Fight (JU)** - the audit comments were moved into the canonical column-35 field. They had sat two spaces after the mnemonic, with no column discipline at all, unlike every other promoted project. - **Balloon Fight (JU)** - all 63 routine headers now carry a `; Clobbers:` block, so the headers carry the full template `AGENTS.md` describes. The blocks are derived, not written by hand: `_tools/derive_clobbers.py` takes the opcode from the audit comment's byte list, resolves the operand through `locals.inc` and `symbols.inc` to name what is written, follows the real control flow, and models register preservation - so `NmiHandler` reports *no register* and `FlushPpuCommandPreserved` reports only A. - **Balloon Fight (JU)** - `ReflectObjectYVelocity` and `UpdateSpecialObjectSystem` moved out of `locals.inc` and became labels in `prg.s` at their real addresses. - **_tools** - new `derive_clobbers.py`, which fills in the `; Clobbers:` block of every routine header from the source. - **_tools** - `align_audit_comments.py` had an off-by-one: it tested the *indented* code length against 31 instead of against 35, so code texts of 28-31 characters were never padded into the column. - **Docs** - the project README gains the audit-trail and clobber sections, the root README and `TODO.md` carry the same news, `AGENTS.md` and `PROMPTS.md` describe this changelog's new shape, and `CHANGELOG.md` itself becomes the running log with a pending list. - **WIP.md** - `_wip/MAPPER8/` is gone; it held a copy of `Mermaids of Atlantis (U)` that had already been promoted. --- ## The milestone index The four milestones every project walks through, and the state of each project against them. ### The four milestones | Milestone | What it does | How it is checked | |-----------|--------------|-------------------| | **Pass 1** | The code/data split: every ROM byte is classified as an instruction or as data, with the audit comment (`; CPU:$C123 PRG:0123 AD 21 03`) on every instruction. | Every byte of the PRG image is accounted for exactly once, and the audit comments re-check against `prg.bin`. | | **Pass 2** | The labels are replaced by names: the address-derived `Label_XXXX` definitions become semantic routine names, and the internal branch targets get role names (`_OnNonzero`, `_Loop`). | No `Label_XXXX` definition is left, no branch or jump operand is a raw `$XXXX`, and every label is defined exactly once. | | **Pass 3** | The semantic naming: the data tables, the zero-page aliases and the hardware register operands get the names the dump proves, and the routine headers are written. | `apply_symbols.py` reports no unexplained residue, and every alias that is not proven is recorded with its reason. | | **Promotion** | The project moves out of `_wip/` into its mapper folder with the whole-ROM build in place. | `make` in the project folder prints **Byte-identical ROM verified.** against `rom.nes`. | A project is only promoted when Pass 2 and Pass 3 are done, the promotion requirements in `PROMPTS.md` are met, and the whole ROM rebuilds byte for byte. `PROMPTS.md` is the contract; this file is the history. **On the dates.** A date is the day the repository records the milestone: the commit that carried it, or the day the project README dates the work. Where neither does, the milestone is dated to the wave it was committed with - the 2026-10-15 build standardisation, the 2026-10-18 `_wip` pass-1 wave, the 2026-10-19 naming wave, or the 2026-10-20 promotion wave. A milestone that is still open says so in the row. ### The tiers - **Promoted** — 41 projects in the top level `NROM/`, `CNROM/`, `UNROM/` and `MAPPER8/` folders. Each one below has been through all four milestones unless its entry says otherwise. - **`_wip/`** — 851 projects that are still Pass 0 skeletons: `prg.s` is a single `.incbin "prg.bin"`. They have a build and a byte-identical rebuild, but no disassembly yet. `Golf (JU)` and `World Class Track Meet (U)` were the tier's last two real disassemblies and were both promoted on 2026-10-21, so no project in the tier carries one now. ### Balloon Fight (JU) — `NROM/Balloon Fight (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | First disassembly 2026-10-09; verified complete on the 16th: 5,303 instructions / 11,260 code bytes (68.7 %). The last two `.incbin` spans were turned into explicit classified bytes, so no ROM byte is hidden. | | Pass 2 | 2026-10-19 | Every label renamed; the zero-page aliases written into `locals.inc`. | | Pass 3 | 2026-10-19 | 125 hardware and 797 of 813 zero-page operands substituted; the 16 that stay numeric are recorded. 2026-10-21: the audit trail was repaired - 5,944 comments had been stale by two bytes since a three byte `.byte "HAL"` signature had been counted as one. 2026-10-21: every routine header now carries a derived `; Clobbers:` block (63 of 63), so the headers carry the full template - global, local, parameters, returns, clobbers. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | ### Baseball (UE) — `NROM/Baseball (UE)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly; `prg.s` is the largest source in the set at 21,624 lines. | | Pass 2 | 2026-10-19 | Labels renamed in the audit-comment standardisation wave. | | Pass 3 | 2026-10-19 | 121 operands substituted (85 single-name + 36 hardware), zero residue. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | ### Bomberman (U) — `NROM/Bomberman (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | 354 single-name and 8 site-resolved multi-alias operands applied, 24 table labels taken from the linker map. Residue recorded: 758 unnamed operands over 179 distinct addresses. The data-table interpretation was added on 2026-10-21: every data block named *and* interpreted (49 labelled blocks, 9,487 bytes). | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | The build fix: `RandomStateAlt = $E4` beside `RandomState0 = $54` for the reused zero page. | ### Burger Time (U) — `NROM/Burger Time (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly; byte identical out of the box. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | Symbols applied. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | ### CIRCUS — `NROM/CIRCUS/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | Full code/data classification; the main loop is a spin loop at $C057 and every frame's work runs in the NMI. | | Pass 2 | 2026-10-16 | Semantic routine naming. | | Pass 3 | 2026-10-19 | 118 operands substituted (38 single-name + 80 hardware); 23 multi-alias residues recorded. | | Promotion | 2026-10-16 | Promoted out of `_wip/`; the menu text table at $C763 and the variant object machine at $DC3F-$FE7B documented. | ### Clu Clu Land (JU) — `NROM/Clu Clu Land (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | Symbols applied. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | The build fix: a `bpl` the flat disassembler had mislabelled at $FAB4, written as `bne *-$1B` to reproduce the ROM bytes. | ### Defender 2 (U) — `NROM/Defender 2 (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | Symbols applied. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | The build fix: the same `bpl` mislabel, at $E811. | ### Donkey Kong (JU) — `NROM/Donkey Kong (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 6,014 instructions / 12,505 code bytes (76.3 %); the reset entry is at $C79E, not $C000. | | Pass 2 | 2026-10-19 | All 919 `Label_XXXX` definitions named: 102 semantic routine names, 217 `SubC` entries and 600 derived internal branch targets. | | Pass 3 | 2026-10-19 | 10 named data regions, 75 operands substituted. | | Promotion | 2026-10-19 | `git mv` out of `_wip/`; the mirroring was corrected from Vertical to Horizontal (header byte 6 = $00). | ### Donkey Kong 3 (JUE) — `NROM/Donkey Kong 3 (JUE)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | The build was split into `header.s` / `prg.s` / `chr.s`, and a one byte branch-target bug was fixed: the `bne` at $C14C was labelled `StoreSelectedPlayerAndScreenFlags` instead of the routine at $C160, so the project had never actually built byte identical. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | Symbols applied. | | Promotion | 2026-10-15 | Top-level wave: the build was standardised and the project got its power-on README. | ### Donkey Kong Jr. (JU) — `NROM/Donkey Kong Jr. (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | Verified complete by a byte-accounting audit: all 16,384 PRG bytes are emitted exactly once (6,135 instructions + 3,889 `.byte` + 16 `.word`), with no audit-comment mismatch and no `.incbin` left. 75.4 % code. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | The tables named - `DeferredTaskHandlerTable`, `HudDigitDescriptorTable` and the rest. | | Promotion | 2026-10-16 | Promoted out of `_wip/`. Architecturally different from the rest: the reset idles in a PRNG loop and the whole frame state machine runs inside the NMI. | ### Donkey Kong Jr. Math (U) — `NROM/Donkey Kong Jr. Math (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-19 | 6,589 instructions / 13,416 code bytes (81.9 %). | | Pass 2 | 2026-10-19 | Restarted from the committed split after the 2026-10-18 draft map was audited and found to have no safely applicable row; 1,035 labels named (64 semantic + 295 `SubC` + 676 internal). | | Pass 3 | 2026-10-19 | 18 data tables named, 13 semantic zero-page aliases, all multi-alias operands substituted. | | Promotion | 2026-10-19 | Top level; the whole-ROM verify closed on 2026-10-20 after four call sites that named a back-branch block instead of the routine it belongs to were repointed. | ### Duck Hunt (JUE) — `NROM/Duck Hunt (JUE)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-19 | 3,058 instructions / 6,152 code bytes (37.5 %). | | Pass 2 | 2026-10-19 | Restarted from the committed split; 472 labels named (30 semantic + 428 `SubC` + 293 internal). | | Pass 3 | 2026-10-19 | 20 data tables named, 18 semantic zero-page aliases. | | Promotion | 2026-10-19 | Top level. | ### Excitebike (JU) — `NROM/Excitebike (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | The audit comments were completed - 1,013 lines had none. 2026-10-20: the trail was re-anchored from the `ca65` listing (6,339 shifted comments and 4,401 doubled `??` forms) and all 58 raw `jsr`/`jmp` operands were given routine names. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | The build fix: two NMI operands that spend their byte on `$11` were emitted with the wrong shadow name. | ### Golf (JU) — `_wip/NROM/Golf (JU)/` — *ready for promotion out of `_wip/`* | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The whole-ROM compare was added: the project builds byte identical. 4,217 reachable instructions in `prg.s` (13,541 lines); all remaining `.incbin` spans confirmed data with zero code entry points. | | Pass 2 | 2026-10-21 | All 112 subroutine entries named (4 semantic + 108 `SubC`); all jsr/jmp call sites symbolic, 0 raw `$XXXX` call operands; 339 of 349 internal branch targets given role-based names (`_Loop`, `_OnZero`, …); 10 fall-through-only labels keep their `Label_` definitions. | | Pass 3 | 2026-10-21 | Audit comments canonical at column 35 (4,156 re-aligned via `_tools/repair_audit_comments.py` + `_tools/align_audit_comments.py`); all hardware-register operands use `symbols.inc` names; 275 zero-page operands substituted with `locals.inc` aliases via `apply_symbols.py`, 0 residue; every branch/jump target symbolic. | | Promotion | — | Outstanding (awaiting `git mv` to `NROM/Golf (JU)/` plus root `README.md` inventory row). | ### World Class Track Meet (U) — `_wip/CNROM/World Class Track Meet (U)/` — *ready for promotion out of `_wip/`* | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-21 | The code/data split is done: 8,400 instructions / 18,028 code bytes (55.0 %) of the 32 KB PRG, 884 routine entries, 55 data blocks, 102 zero-page aliases. The PRG is CNROM (mapper 3) and maps whole at $8000-$FFFF, so the linker moved the PRG segment from a file offset to $8000. Four dispatch tables had to be seeded by hand: the event table at $F796 (5 entries, read at $F780 and jumped to through `jmp ($001F)`) and three push-pointer tables - $BFC4 (16), $BFE4 (2) and $E5F2 (7) - which store the handler address *minus one*, because the handler is pushed and an `rts` lands on it. The tracer runs with `--undocumented`: the code uses undocumented NMOS opcodes as ordinary code. Seeding the tables takes the coverage from 27.8 % to 55.0 %. The rebuild is byte identical. | | Pass 2 | 2026-10-21 | All 884 `Label_XXXX` definitions replaced: 651 semantic routine names (`Reset`, `NmiHandler`, `MainLoop`, `TaskDispatch`, `EventInit`, `EventTaskRun`, `SpriteSetup`, `ObjectState`, `SoundNote`, `OlympicsScheduler`, …) plus 233 `Sub_` address-derived residue. All 731 raw `$XXXX` jsr/jmp call operands resolved to symbolic labels; every branch target given a role-based name (`_Loop`, `_OnZero`, `_OnNonzero`, …); `routinemap.tsv` holds the address→name map. | | Pass 3 | 2026-10-21 | 24 semantic zero-page aliases applied via `apply_symbols.py` (0 residue), 78 neutral `ZpXX` slots documented with reasons in `locals.inc`, all hardware-register operands use `symbols.inc` names, data-table inventory documented in the project README. | | Promotion | — | Outstanding (awaiting `git mv` to `CNROM/World Class Track Meet (U)/` plus root `README.md` inventory row). | ### Hogan's Alley (JU) — `NROM/Hogan's Alley (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The static trace had classified only 41.8 % of the PRG as code. A merged Mesen CDL capture (five captures, 6,544 code bytes) moved 1,216 bytes from data to code and five the other way, taking the split to 49.1 %. | | Pass 2 | 2026-10-20 | A fresh map from this dump's own committed split: 206 routine entries hand-named and 413 internal branch targets derived. | | Pass 3 | 2026-10-20 | 60 semantic zero-page aliases, each applied only where its role is proven. | | Promotion | 2026-10-20 | Top level. | ### Ice Climber (JE) — `NROM/Ice Climber (JE)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-19 | 6,112 instructions / 12,230 code bytes (74.6 %). | | Pass 2 | 2026-10-19 | Restarted from the committed split; 973 labels named (76 semantic + 212 `SubC` + 685 internal). | | Pass 3 | 2026-10-19 | 9 semantic zero-page aliases, the hardware operands substituted, the data tables named. | | Promotion | 2026-10-19 | Top level. The PRG and the header are byte identical to the (U) dump; only the CHR differs. | ### Ice Climber (U) — `NROM/Ice Climber (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-19 | 6,112 instructions / 12,230 code bytes (74.6 %) - the same PRG as the (JE) dump. | | Pass 2 | 2026-10-19 | 973 labels named. | | Pass 3 | 2026-10-19 | 9 semantic zero-page aliases and the hardware operands substituted. | | Promotion | 2026-10-19 | Top level, together with the (JE) dump. | ### Joypad Test Cartridge (U) — `NROM/Joypad Test Cartridge (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | Redone: the first pass claimed 282 instructions / 580 bytes (3.5 %), because the tracer halted on `DCP $3C20` - an undocumented opcode used as ordinary code - and filed 3,256 bytes of real code as data. Now 1,773 instructions / 3,836 bytes (23.4 %). | | Pass 2 | 2026-10-19 | All 202 labels renamed and all 202 routine headers rewritten; the zero-page aliases written. | | Pass 3 | 2026-10-19 | 47 hardware operands substituted, the data blocks named. | | Promotion | 2026-10-16 | Promoted out of `_wip/`. | ### Lode Runner (U) — `NROM/Lode Runner (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 11,057 lines, 627 label definitions, 52.6 % code, canonical audit comments. | | Pass 2 | 2026-10-20 | All 627 labels renamed: 196 semantic routine entries plus 431 derived internal branch targets. | | Pass 3 | 2026-10-20 | 85 operands substituted, 2 semantic zero-page aliases, the data tables named. | | Promotion | 2026-10-20 | The whole-ROM build was added and the `_tools` paths moved. | ### Lunar Pool (U) — `NROM/Lunar Pool (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | The split was re-derived from a Mesen CDL capture (10,028 bytes of code) after a static trace alone had left most of the game unclassified: now 5,300 instructions / 11,077 bytes (67.6 %). | | Pass 2 | 2026-10-18 | 705 labels named, zero `Label_` residue. | | Pass 3 | 2026-10-18 | 33 data tables named, 38 semantic zero-page aliases; 45 operands substituted on 2026-10-19. | | Promotion | 2026-10-18 | `git mv` out of `_wip/`; the power-on flow and the print-inline-string helper documented. | ### Mario Bros (JU) — `NROM/Mario Bros (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The committed split was a strict subset of the correct one - only 72.0 % was classified as code. A Mesen CDL capture (10,915 code bytes) showed 1,010 bytes the tracer had left as data were code, and two branch targets the capture never reached were forced by hand, so 1,067 bytes moved from data to code: 78.4 %. The engine's handler tables sit inline, right behind the `jsr` that reads them. | | Pass 2 | 2026-10-20 | 206 routine entries and 519 internal branch targets, 931 `Label_XXXX` definitions gone, 51 raw call operands resolved. | | Pass 3 | 2026-10-20 | 25 semantic zero-page aliases, each applied only where its role is proven. | | Promotion | 2026-10-20 | Top level. | ### Millipede (U) — `NROM/Millipede (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 14,744 lines, canonical audit comments, 74.6 % code. | | Pass 2 | 2026-10-20 | All 839 address-derived labels renamed: 12 semantic routine names, 264 `SubC` entries and 577 internal branch targets. | | Pass 3 | 2026-10-20 | 8 semantic zero-page aliases; the data tables named - `LevelData`, `ObjectInitData`, `AudioData`, `VectorPageData`. | | Promotion | 2026-10-20 | The whole-ROM build was added and the `_tools` paths moved. | ### MUSCLE (U) — `NROM/MUSCLE (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | The bytecode VM layer was decoded: 40 opcodes handled through a table at $9237, so the "data" under the bytecode is code. 5,253 instructions. | | Pass 2 | 2026-10-18 | The Pass 1/2/3 output written with the 42 bytecode listings above the `.byte` data. | | Pass 3 | 2026-10-16 | The data names and the structures written. 2026-10-19: 220 operands substituted, zero residue. | | Promotion | 2026-10-16 | Promoted out of `_wip/`. The one ROM in the set mapped low (PRG at $8000), and the only project with a `make regen` target that reproduces `prg.s` byte for byte. | ### NES PowerPad Test Card (U) — `NROM/NES PowerPad Test Card (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | A minimal test cartridge: the NMI reads the PowerPad over $4016 and updates the score and the lives, 7.2 % code. | | Pass 2 | 2026-10-19 | Labels renamed with the PowerPad group. | | Pass 3 | 2026-10-16 | The data tables and the `VramWrite` streams named. 2026-10-19: 89 hardware operands substituted, zero residue. | | Promotion | 2026-10-16 | Promoted out of `_wip/`. | ### Pac-Man (Tengen) (U) — `NROM/Pac-Man (Tengen) (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | Six command handlers recovered - 120 bytes the tracer had left as data, two of them mis-split across the `jmp $EF61` at $F01F and $F04F. 1,637 instruction lines covering 3,273 code bytes (20.0 %). The boot sequence is inline at $C033: this build has no boot code at $FF00. | | Pass 2 | 2026-10-20 | A fresh map from this dump's own committed split - no name, map or file was carried over from the other Pac-Man dump. 227 labels (199 routine entries + 28 branch targets), zero `Label_` residue. | | Pass 3 | 2026-10-20 | 30 semantic zero-page aliases, each applied only at the sites where the role is proven. | | Promotion | 2026-10-20 | `git mv` out of `_wip/NROM/`. | ### Pac-Man (U) — `NROM/Pac-Man (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | 19.6 % code; the boot code sits at $FF00, unlike the Tengen build. | | Pass 2 | 2026-10-20 | 194 `Label_XXXX` definitions renamed to semantic routine entries plus derived internal branch targets, and all 49 `jsr`/`jmp` call sites resolved. | | Pass 3 | 2026-10-20 | 119 operands substituted (101 hardware + 11 single-name + 7 multi-alias resolved per site). | | Promotion | 2026-10-20 | `git mv` out of `_wip/NROM/`. | ### Pac-Man (USA) (Namco) — `NROM/Pac-Man (USA) (Namco)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | 524 single-name operands substituted; 727 multi-alias residues left numeric and recorded (an address with two or more aliases, e.g. `$00` = `workPtr0`/`Zp00`). | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | ### Pinball (JU) — `NROM/Pinball (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 4,734 instructions / 9,756 code bytes (59.5 %) - a real disassembly, not a skeleton. | | Pass 2 | 2026-10-18 | Substantial: 180 of 681 labels carry semantic names. The "Pass 2 complete" claim of that day was corrected on 2026-10-19 after an audit of the map. | | Pass 3 | — | Drafted only: the zero-page aliases are not in `locals.inc` yet. | | Promotion | 2026-10-20 | The whole-ROM build was added; the pass work is still open. | ### Popeye (JU) (PRG 0) — `NROM/Popeye (JU) (PRG 0)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 14,875 lines, canonical audit comments, 72.3 % code, six routine entries seeded from table references. | | Pass 2 | 2026-10-20 | All 878 labels renamed: 2 semantic entry names, 315 `SubC` entries and 561 internal branch targets. | | Pass 3 | 2026-10-20 | 4 semantic zero-page aliases, 79 operands substituted, the data tables named - `LevelData`, `AudioData`, `VectorPageData`. | | Promotion | 2026-10-15 | Top-level wave: the build was standardised and the project got its power-on README. | ### Popeye (JU) (PRG 1) — `NROM/Popeye (JU) (PRG 1)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | As the PRG 0 dump, but a different PRG binary. | | Pass 2 | 2026-10-20 | All 878 labels renamed. | | Pass 3 | 2026-10-20 | 4 semantic zero-page aliases and the data tables named. | | Promotion | 2026-10-20 | Promoted out of `_wip/`. | ### Pyramid (U) (Sachen) — `NROM/Pyramid (U) (Sachen)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The committed split was a strict subset of the correct one: 828 bytes of code were still marked as data, because the tracer cannot follow a `jmp` that sits inside a data region. Re-tracing with the sixteen `jmp`-table targets, the $C800 boot mainline and six further hand-seeded starts recovered them, and lost no committed code byte. 39.6 % code. | | Pass 2 | 2026-10-20 | 431 routine entries and the 32 data blocks named, 428 `Label_XXXX` definitions gone, 320 raw call operands resolved. | | Pass 3 | 2026-10-20 | 24 semantic zero-page aliases, each applied only where its role is proven. | | Promotion | 2026-10-20 | Top level; renamed to `Pyramid (U) (Sachen)` on 2026-10-21. The unlicensed boot pokes a mapper unlock at $4100 and dispatches through two queued-event tables. | ### Raid on Bungeling Bay (U) — `NROM/Raid on Bungeling Bay (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | 64.6 % code. | | Pass 2 | 2026-10-20 | Labels renamed in the same wave as the other 2026-10-20 promotions. | | Pass 3 | 2026-10-20 | 11 semantic zero-page aliases added to `locals.inc`. | | Promotion | 2026-10-20 | Top level. | ### Raid on Bungeling Bay (U) [a2] — `NROM/Raid on Bungeling Bay (U) [a2]/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | 64.3 % code. A different PRG from the other dump - the two differ in mirroring only per the header, but the PRGs are separate builds. | | Pass 2 | 2026-10-20 | Labels renamed. | | Pass 3 | 2026-10-20 | 15 semantic zero-page aliases added to `locals.inc`. | | Promotion | 2026-10-20 | Top level. | ### Tennis (JU) — `NROM/Tennis (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-09 | First disassembly; byte identical out of the box. | | Pass 2 | 2026-10-19 | Labels renamed. | | Pass 3 | 2026-10-19 | 18 single-name operands substituted, zero residue. | | Promotion | 2026-10-09 | In the top level from the start - there was no `_wip/` tier yet. The build and the per-project power-on README were standardised on 2026-10-15. | ### Urban Champion (JU) — `NROM/Urban Champion (JU)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-18 | 2,716 instructions, 33.9 % code. | | Pass 2 | 2026-10-18 | 436 labels named. | | Pass 3 | 2026-10-19 | 10 semantic zero-page aliases, 70 operands substituted. | | Promotion | 2026-10-18 | Promoted with the full-ROM build. | ### Volleyball (U) — `NROM/Volleyball (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-21 | Every byte of the 32 KB PRG image accounted for as an instruction or an explicit `.byte` block: 11,395 instructions / 23,926 code bytes (73.0 %). | | Pass 2 | 2026-10-21 | 1,989 labels named. | | Pass 3 | 2026-10-21 | 37 named data blocks, zero residue. | | Promotion | 2026-10-21 | Promoted out of `_wip/NROM/`. | ### Wild Gunman (U) — `NROM/Wild Gunman (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-16 | Redone: the original tracer followed only the vectors and the direct calls and knew just the 151 documented opcodes, so it missed the runtime dispatch - the `pla` based computed goto puts the jump table inline right after the `jsr` - and filed about 4.9 KB of real code as data. 1,597 instructions / 3,352 bytes (20.5 %) became 3,956 / 8,085 (49.3 %). | | Pass 2 | 2026-10-19 | All 733 labels carry names; zero `Label_Cxxxx` or `SubCxxxx` remain, and every entry point has a routine header. | | Pass 3 | 2026-10-19 | The data tables named - `SpriteAttributeTable`, `NoteVolumeTable`, `VolumeEnvelopeTable`, `SpritePositionTable`; 76 operands substituted, zero residue. | | Promotion | 2026-10-16 | Promoted out of `_wip/`. | ### Xevious (U) — `NROM/Xevious (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The static trace from the vectors reached only 13.6 % of the PRG. Declaring both jump tables with `--table` plus 92 hand-seeded entry points recovered the code behind the computed dispatch, reaching 34.5 %. Re-split on 2026-10-21 from the Mesen CDL (`_tools/cdl_reclass.py`): 1,158 data→code bytes, 38.0 % code, 6,002 instructions. | | Pass 2 | 2026-10-20 | Every routine entry point has a semantic name and every raw `$XXXX` call operand was replaced. Re-checked after the 2026-10-21 re-split: the 45 recovered entries are named `Sub_XXXX`; six CDL-silent raw operands remain and are documented in the project README. | | Pass 3 | 2026-10-20 | 32 named data tables (the $A74B-$B8AA block split into six proven tables on 2026-10-21) and about 20 semantic zero-page aliases. | | Promotion | 2026-10-20 | Promoted out of `_wip/`. | ### Zanac (U) — `UNROM/Zanac (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-20 | The static trace from the vectors reached only 37.9 % of bank 7; banks 0-6 are still raw `.byte` data, pending the bank-switch analysis. | | Pass 2 | 2026-10-20 | 133 semantic entry points. | | Pass 3 | 2026-10-20 | 26 named data tables and 14 semantic zero-page aliases. | | Promotion | 2026-10-20 | Promoted out of `_wip/`; the only promoted UxROM project, with the fixed bank in its own segment. | ### Mermaids of Atlantis (U) — `MAPPER8/Mermaids of Atlantis (U)/` | Milestone | Date | Note | |-----------|------|------| | Pass 1 | 2026-10-21 | Redone, bank-aware: the 64 KB PRG is eight 8 KB MMC3 banks, and each is traced separately with the CPU window it is visible at, seeded from a Mesen CDL capture. 8,075 bytes of code (12.3 %) in 494 routines, up from about 200 bytes. The image carries the reset stub and the vector table twice, and the fixed bank is the fourth 8 KB bank rather than the last. | | Pass 2 | 2026-10-21 | Every label named, no `Label_XXXX` left and no raw `$XXXX` branch or jump operand left; the banks that share a CPU window carry a `Bank4_` .. `Bank7_` prefix. | | Pass 3 | 2026-10-21 | 13 semantic zero-page aliases, the routine headers written, the data tables listed in the project README. | | Promotion | 2026-10-20 | Promoted - but the build was broken: a half-applied Pass 2 rename left eight undefined symbols. Rebuilt and verified on 2026-10-21. | --- ### Adding an entry **For a change to any project**, add a line to **Pending** at the top of this file: name the project, and say what changed. When the commit is made, its message is written from those lines, and they move to **History**. **For a project that reaches a milestone**, the milestone also gets a row in the milestone index below, with the date and a sentence saying what the milestone produced. The numbers that go in the row are the ones the project README and the root `README.md` already carry: the instruction count, the code coverage, the number of named labels, the number of semantic zero-page aliases and the number of named data tables. A milestone that has not happened gets a `-` in the date column and a note saying what is still open - the point of the table is that a reader can see at a glance which projects are done and which are not.