Skip to content

Add portable world.talk dispatch and cross-engine NPC contracts - #149

Closed
Roxas2712 wants to merge 24 commits into
UNDERdecoded:mainfrom
Roxas2712:codex/portable-world-talk
Closed

Roxas2712 wants to merge 24 commits into
UNDERdecoded:mainfrom
Roxas2712:codex/portable-world-talk

Conversation

@Roxas2712

@Roxas2712 Roxas2712 commented Oct 23, 2026 •

Copy link
Copy Markdown

NPC mods can register world.talk in Gen2Recomped's API-3 catalog, but native A-button interaction currently calls talkTo directly. A mod therefore cannot claim its runtime NPC's conversation using the same public hook as Gen1Recomp.

Dispatch world.talk(next, owner, target) at the existing native NPC boundary, before the normal freeze/facing/dialogue flow. Omitting next claims the conversation; calling it retains the native flow. The continuation matches Gen1's classic void return contract. Register the producer in capability queries and unified API-2 callbacks, retaining opaque owner/actor identities.

Behavior and boundaries

  • The common Gen2 path covers all 15 hosts. Counter lookup, moving/hidden/blocker checks, GBA elevation, special follower dispatch and world.interacted ownership retain their existing behavior.
  • Real runtime-NPC tests exercise native public spawning, NPC construction, conversation interception, TextBox confirmation, facing, freeze release, unsubscribe and removal.
  • A single unchanged example mod runs through both real Loaders and native NPC interaction methods for API 1, native/unified API 2 and API 3.
  • Gold reference boundary: pinned Gen1's separate native Gold World:interactBody does not produce world.talk, despite catalog recognition. The checker tests that native absence explicitly; Gen2 Gold executes the shared contract. It never substitutes the classic controller or modifies reference sources.
  • Native handles are intentionally opaque: Gen1's GBA owner is Game, while classic Gen1 and Gen2 supply their overworld. Redirecting the actual native target via replacement arguments and retaining next asynchronously are not portable promises. These boundaries are documented.

Validation

  • 5,385/5,385 focused checks: shared dispatch/lifecycle 74, all-host/four-mode routing 5,116, native runtime-NPC dialogue 195.
  • Capability suite: 361/361.
  • Unmodified Gen1 world_talk and unified_npcs suites: 9/9 + 1,555/1,555.
  • Final feature and integration commits: 80 native worker runs, 224 matched observations across Red/Yellow/FireRed/Emerald, plus 8 explicit Gold producer-boundary checks, spanning all four callback modes.
  • Feature quick suite: 97/107 engine suites, SDK 3/3, B30 pass. Parent Add portable battle.status_hud_visible for GB/GBA and shared contracts #148 is 94/104; the same 10 known baseline suites and all 26 failure lines remain unchanged, with no new failures.
  • Combined with the existing portability and baseline-fix PRs: 151/151 engine suites, SDK 3/3, B30 pass.
  • StyLua, actionlint and git diff --check pass. Validation is ROM-free; imported-ROM playthroughs are not claimed.

The workflow pins clean Gen1 commit 79ea62f45fb2ea8f65d081a109431e4eb1bbdb36, runs the unmodified reference suites and publishes the cross-engine reports. Documentation includes reproduction commands and corrects an obsolete API-3 availability example.

Stack

Depends on #148 and its prerequisites. This PR targets main to follow the existing series. Review the feature-only comparison against #148; please merge in stack order.

Hosted CI

All ten fork workflows currently require maintainer approval (action_required), including the new world.talk contract run. Its job list contains 0 jobs; hosted tests have not run yet. The passing results above are local, reproducible ROM-free runs.

# Conflicts:
#	src/mods/Loader.lua
# Conflicts:
#	docs/modding.md
#	src/mods/Loader.lua
#	src/world/WorldAPI.lua
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant