mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
276 lines
16 KiB
Markdown
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.
|