Files
gen1recomp/GOLD-OPEN-ITEMS.md
T
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

276 lines
16 KiB
Markdown

# Gold port: what is still open after the 2026-08-09 asm parity runs
Two multi-agent runs diffed the Gold port against the `pokegold` disassembly
(`../pokegold`) and fixed what diverged. Round 1 confirmed 51 defects across 14
paired clusters; round 2 confirmed 41 more across 13, including one crash. A
third round (2026-08-10) closed the mod-API boundary: see "The 2026-08-10
mod-API round" below for what that opened and closed.
Every tier is green at the end of all three: `run_engine` 131/131, `run_tests`
ALL TESTS PASSED, `run_modkit` 6/6, `run_save_editor_tests` 650/0,
`run_link_tests` ALL PASSED.
This file records only what is NOT done, so nobody re-derives it. It is a
working handoff doc in the shape of `KANTO-CONTINUE.md`, not user documentation.
## Do this first or none of it is visible
**Re-import the Gold cache.** Seven fixes live in the extractor and the manifest
tool, so they do nothing until the ROM is re-imported. This was verified still
outstanding on 2026-08-10: the default identity's
`gold/data/generated/menu_gfx.lua` has no `trainerPics` key at all, so a trainer
battle still opens on the mon rather than the trainer even though the Lua half
is fixed and correct.
- trainer frontpics (`TrainerPicPointers` walk in `extractMenuGfx`), which is
the missing trainer intro image at the start of a trainer battle. The Lua
side is now right and re-import is the ONLY thing left: `Trainers.lookup`
returns a numeric `class` (36) beside a `classId` (`BUG_CATCHER`), and both
`menu_gfx.trainerPics` and `palettes.trainers` are keyed by the constant, so
the lookup had to move to `classId`. On a cache that carries the pics the
frontpic renders in the class's own palette.
- egg assets: `battle/front/egg.png` and `menu/egg_hatch.png`, feeding the
hatch cutscene and the summary screen's egg page. A fallback landed in the
meantime (`ICON_EGG` frame 0, which resolves to a real 16x16 frame), so the
egg page draws SOMETHING on a stale cache; `tools/rom_manifest_gold.json`
carries `EggPic` at `[20, 31363]` and that stream decompresses to exactly 400
bytes (a 5x5-tile pic), so a fresh import takes the real path.
- the Pokegear phone icon. `_LoadFontsExtra` (`engine/gfx/load_font.asm:8-15`)
puts `FontsExtra_SolidBlackAndUpArrowGFX` at `$60`/`$61` and
`PokegearPhoneIconGFX` at `$62`, ON TOP of `FontExtra`'s BOLD_A/B/C.
`src/import/RomExtractorGen2.lua:342-356` reads `FontExtra` straight in and
then blits only `Frames` over `$79-$7E`, so `$62` is still a bold "C" -- which
is what the new caller box draws where the telephone glyph belongs. Fix is two
files: add the two GFX symbols to `tools/rom_manifest_gold.json`, then blit
them at `$60`/`$61`/`$62` in the loop that already handles `Frames`.
- `PREDEFPAL_UNOWN_PUZZLE = 76`, which is why the Ruins of Alph puzzle rendered
grayscale instead of brown
- the tilemap pad `0x7f -> 0x4f` in `readTilemapRLE` / `readFlatTilemap`
- held-item icons (`HeldItemIcons` in the manifest, `out.heldItem` in
`extractIcons`), which is the held-item marker in the party menu
- the NPC-trade rows' `item` field, which is what made the Violet City Onix's
Bitter Berry print as "83"
## Open: real unknowns, each needs a cache and a driver run
1. **Magnet train renders a blank field.** Pushing `World:magnetTrain` directly
shows nothing for 90+ frames (see shot `03-magnet-train.png`). The screen's
surround is correct now; what was never investigated is whether
`drawBackground` needs setup that the direct push skips. This cannot be
settled by reading source.
2. **The NPC that introduces Unown never appears.** Round 1 could not produce a
port ref that diverges from the asm, so it is filed `cannot_locate`, not
fixed. The cart chain is: solve a chamber puzzle, then
`setmapscene RUINS_OF_ALPH_INNER_CHAMBER, SCENE_RUINSOFALPHINNERCHAMBER_STRANGE_PRESENCE`
from `maps/RuinsOfAlphKabutoChamber.asm`. Walk that chain live rather than
statically: the static read came back clean twice.
3. **Two pic sites still resolve an Unown off the species.**
`src/ui/gen2/PhotoStudio.lua:102` and `src/ui/gen2/EvolutionAnim.lua:460` use
`def.spriteFront` without going through `Unown.formSprite`. For EvolutionAnim
this is almost certainly dead (Unown does not evolve). For PhotoStudio it is
genuinely unclear: the cart's `engine/events/print_photo.asm` does not call
`GetUnownLetter` at all, so the port may already match. Check before changing.
4. **Unown dex registration on obtain paths.** `engine/pokemon/evolve.asm:310`
runs `GetUnownLetter` then `callfar UpdateUnownDex` after
`SetSeenAndCaughtMon`, guarded by `cp UNOWN`. That is dex bookkeeping, not
rendering, and it was never audited across every path that gives the player a
mon. `GetUnownLetter` has 20 call sites on the cart; the port covers the pic
ones.
## The 2026-08-10 mod-API round
Six lanes closed the Gen 1 / Gen 2 mod-API boundary and a batch of reported
gameplay bugs. What that round **closed**, so nobody re-opens it:
- Fifteen seams that had a Gen 2 site but no entry in the parity gate are now
listed and asserted: the four `intro.oak_speech.*` events plus the
`intro.oak_speech.build` hook, `battle.overlay`,
`battle.low_health_alarm`, `battle.catch_exp`, `pokemon.sprite`,
`input.step`, `input.pointer`, `render.zones`, `render.compose`,
`render.letterbox`, `render.hud`. Five more (`intro.boot.*`) are listed as
Gen 2-only. `tests/engine/gate_gen2_mod_api.lua` is 893/893.
- Two registries un-gated on Gold after their consumer landed:
`battle_sprite_scales` (`src/ui/gen2/BattleState.lua:imageScale`) and
`render_pipelines` (`src/core/Game2.lua:load` installs
`src/render/Pipelines.lua` on the merged dataset after `mods:load`). Both
keep the SHARED Gen 1 target, so one mod record serves both games. 40 of the
46 registries are now available on Gold.
- `hook:render.zones` came off the `gate_meta_coverage.lua` DEBT ledger, which
is now four entries, all M7/M12 link and give-mon seams.
What that round **left open**, each verified on 2026-08-10:
1. **`render_pipelines`' `drawWorld` half is inert on Gold.** Gold's overworld
draws straight to the window rather than into a canvas the way
`src/world/OverworldController.lua` hands one to `Pipelines.drawWorld`, so a
drawWorld-only pipeline renders nothing. It is not left switched on
pretending: `Game2:load` retires a restored level for one and re-applies
Tilt from the option the exclusion just cleared, leaving
`options.pipelines` untouched so the mode returns the day Gold grows a world
canvas. Related: `Pipelines.rows` is read only from
`src/ui/OptionsMenu.lua`, so a pipeline on Gold has a hotkey and no OPTION
row.
2. **`transitions` stays gated and should.** `src/ui/gen2/BattleTransition.lua`
keys `STYLES` as a boolean SET of the four cart wipes (`spin`, `speckle`,
`zoom`, `sine`), not the `{ frames, draw, sound, flash }` record the
registry carries, and there is no styleDef lookup a mod id could reach --
a registered style would fail the membership test and fall back to vanilla.
Un-gating it before that changes would be the silent no-op the routing table
exists to prevent. Same for `rulesets`, `field`, `text_pointers`,
`link_fields` and `map_scripts`; `docs/mod-api-gen2-compat.md` carries the
per-registry reason.
3. **`src/core/Game2.lua:joystickremoved` is half of Gen 1's.**
`src/core/Game.lua:867` does `self:recoverInput("joystickremoved", joystick)`
AND `TouchControls:joystickremoved()`; Game2 does only the pad half, so a
controller unplugged mid-hold leaves Gold's Input state stranded and a held
direction walks forever. It was a total noop before, so this is not a
regression -- it is a seam finished halfway. One line.
4. **`Game2` noops five joystick callbacks.** `joystickpressed`,
`joystickreleased`, `joystickaxis`, `joystickhat` and `joystickadded` are
assigned `noop`, so a stick with no SDL game-controller-database entry
reaches neither Input nor `GamepadMap.RAW_BUTTON_BINDINGS` on Gold. Gen 1
handles all four (`src/core/Game.lua:764-797`). A DualSense is
SDL-recognized and takes the gamepad path, so this is not the reported
SELECT bug -- it is the raw-stick fallback.
5. **`Game2:gamepadpressed` has no SELECT-held guard.** Gen 1
(`src/core/Game.lua:686-694`) suppresses the shoulder GAME SPEED cycle while
SELECT is held, because Select+L is a display chord on NX. Harmless today
(Gold has no display chord) but the two paths have diverged.
6. **`hideCallerBox`'s fallback path skips the screen seams.**
`src/script/gen2/CallAsm.lua:217-231` pops through `stack:pop` when the
caller box is top (which it always is on a normal call, since `PhoneRing`
runs `closetext` before `InitCallReceiveDelay`) but falls back to a bare
`table.remove(states, index)` otherwise, which raises neither `exit` nor
`screen.popped`. Only a mod screen pushed over the box can reach it, and
then a listener that saw `screen.pushed` never sees the pop.
7. **`src/core/gen2/Roamers.lua:338` says the wrong thing.** Its comment says
"the shared `encounter.species` hook still runs downstream and is where a
mod changes what appears". It does not: `World:tryWildEncounter` calls
`startBattle` on a roamer hit and RETURNS before it reaches
`World:rollEncounter`. The doc's partial-coverage list is right and the
comment is wrong; correct the comment, not the doc.
8. **`src/world/gen2/World.lua:4318` still calls the pack's SEL row something
"this port has not built".** The pack submenu now offers SEL. Stale comment.
Three partial coverages that are documented and still true, repeated here
because "the hook exists" is not "the hook sees everything":
`encounter.roll` / `encounter.species` are not wired into `World:tryHeadbutt`,
`World:rockMonEncounter` or the roamer path; `src/ui/gen2/BattleState.lua`
builds a flat `opts` for `Catching.attempt` with no `data` in it, so a
mod-registered ball is readable through `Catching.recordFor` but is not
resolved at the real throw site; and no Gen 2 UI file reads
`Battle.statusRecordFor(data, status).hudLabel`, so a mod status shows no label
in the battle HUD, the party menu or the summary page.
## Open: cleanups declined on purpose
These were skipped with reasons during the wiring pass. They are listed so the
reasons survive, not because they are pending work.
1. **`Vm:showRaw(body, stay)`** was proposed and declined. `showRawHeld` in
`Specials.lua` already answers the text lookahead correctly, so this trades
one working shape for another.
2. **`tests/drivers/gold_egg_hatch.lua` summary push** was declined as
redundant: `gold_egg_hatch_shots.lua` already photographs both the cutscene
and the summary page.
3. **Two `Battle.lua` divergences** were filed as notes by the file's own owner
rather than fixed, because no reported symptom drives either: text printed
for an already-statused target, and Future Sight taking STAB, type
effectiveness and weather. Both are real divergences from the cart. Fix them
when something actually depends on them.
## Reported, investigated, and NOT defects: do not re-chase these
Each of these came in as a bug report and came back disproved against the asm.
Re-opening one costs another full investigation, so the reasoning is kept here.
- **SFX pointer table misalignment.** The table is aligned end to end. 188 `dba`
rows with no skips or padding, 188 constants, verified through the manifest
scraper, the extractor's `*3` stride, and 8 ids spot-checked against the live
cache. Two things that look like misalignment are how the cart is built:
`SFX_GET_EGG_UNUSED` and `SFX_GET_EGG` both point at `Sfx_GetEgg`, and
`Sfx_ReadText` / `Sfx_ReadText2` share one header address. **Do not repoint,
re-stride, or add an offset to the SFX, music, or cry tables.**
- **Bind letting you pick other moves.** This is Gen 1 behavior and does not
hold for Gen 2. `BattleCommand_TrapTarget` (`effect_commands.asm:5568-5605`)
writes only the TARGET's wrap count and trapping move. It touches nothing on
the user, so the user is free to switch moves. The port is correct. The real
Gen 2 move locks (Rollout, Thrash, Petal Dance) were genuinely missing and
have been added.
- **The Slowpoke Tail salesman not blocking progression.** He is Route 32's
`FISHER4` at (7,70), not an Azalea Town object, and the cart's refusal arm
only prints. `_OfferToSellSlowpokeTail` runs `setscene SCENE_ROUTE32_NOOP`
first, so the coord event never fires again. There is no pushback to port.
- **The rival being named BLUE.** Not reproducible. The whole chain is intact:
special 36 resolves to `H.NameRival`, `NamingScreen:accept` hands the typed
string through, and BLUE is the Gen 1 default that Gold never reaches. A
separate real defect on this seam (the rival being pre-named SILVER before the
officer asks, and the blank-entry fallback) was found and fixed.
- **Not Very Effective dealing 0 damage**, **status not shown in the battle
UI**, and **the Pokedex missing from the Start Menu** all came back
`already_fixed`: the damage floor is `MIN_DAMAGE` added after the cap and
before the type multiply, the HUD prints the status tag where the level goes,
and the menu row is gated on `save.engineFlags[11]` correctly.
## Standing risks, not breakage
- **Four Gold test files are wired into no runner**, so a regression in them
will not turn `run_tests.lua` red: `gold_flag_names_test`,
`gold_route_validate_test`, `gen2_pokegear_unlock_test`,
`gen2_save_export_test`. All four pass standalone. The first two are the
pre-run route validators documented in `KANTO-CONTINUE.md`; the last two need
a `GOLD_CACHE` and are excluded by an in-file comment. This is deliberate, but
it is a coverage hole and worth knowing.
- **`mods/` must stay clean.** The 2026-08-10 round left temporary probe mods
behind mid-run (`mods/tmp_menus`, `mods/zz_verify_seams`); both are gone now
and `mods/` holds only `example_mew_starter`, `example_silly_oak`, `examples`
and `nuzlocke`. A stray `mods/tmp_*` loads on every Gold boot and changes what
a driver measures. `mods/example_silly_oak` carries only a `.modkitignore` and
warns "manifest.json does not exist" on every boot; that is pre-existing
(27 Jul) and unrelated.
- **`tests/drivers/gold_opaque_surround.lua` is stale.** It reads
`game.stack._items`; `src/core/StateStack.lua` has only `.states`. That branch
is covered instead by `gold_center_pc` and the letterbox-per-frame count in
`gold_frame_seams`.
- **`luac` on this machine is Lua 5.5**, so `luac -p` is a weak proxy for the
LuaJIT/5.1 semantics this engine targets. Use `luajit -b <file> /dev/null` for
a real syntax gate.
- **`docs/rfcs/0001-surfing-pikachu-sprite.md` and `0002-screen-render-visible.md`
are deleted** in the working tree, and both are still referenced by name from
live modkit case files that pass. Those deletions predate both parity runs.
Worth resolving before a commit.
## The failure mode that actually bit, worth remembering
Round 1 reported the Yes/No dialogue fix as landed. It was not. Two of its three
lanes wrote their half, and the third left the hook closure at
`World.lua:828` declared as `function(body, onDone)`, so the third argument
`Vm:resume` passes was silently discarded and the entire `stay` implementation
in `World:showText` and `World:askYesNo` was dead code. Lua drops extra
arguments without complaint, so nothing failed and every suite stayed green.
Round 2 caught it only because an investigator probed the boundary empirically
instead of reading both sides and assuming they met. When work is split across
agents by file, **the seams between the files are where the bugs live**, and a
green suite does not prove a seam is connected.