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

16 KiB

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.