Hooks Transition Events - drizztdourden08/relic-of-the-past GitHub Wiki
Transition Events
window.__onTransitionSettled (see Callbacks) tells the renderer when a
module or dungeon submodule transition has just finished, replacing what used to be a
polling loop in the navigation widget's auto mode.
Source: core/game-hooks/transition_events.c (C side), apps/web/src/lib/game/events/
(TS bridge).
Why an event instead of polling a flag
Three earlier attempts tried to infer "has the transition finished" from game state
(is_standing_in_doorway, door_animation_step_indicator). Both are latched, not
per-frame: set when something begins, cleared much later by unrelated code, so they report
"still happening" indefinitely and either block or delay every trigger.
The game itself only knows one honest signal: submodule_index returning to 0 inside a
gameplay module. Every dungeon transition, whatever kind, ends by handing control back that
way, and the dungeon module's own dispatch (Module07_Dungeon) proves it treats 0 as
settled, since room tags and door processing only run then.
Behind a setting
Gated on the Developer Tools master flag (kFeatures0_DeveloperTools), off by default,
under Profile → Developer. Off, GameHook_ModuleFrameEnd returns immediately, before doing
anything else, exactly like the haptics gate. No EM_ASM, no host-call, no cost beyond the
flag check.
Classification
C reports raw numbers; classify-transition.ts turns (module, fromSubmodule) into a
TransitionKind:
| Kind | Dungeon submodule(s) |
|---|---|
room |
2 |
quadrant |
1 |
doors |
4, 5, 9 |
stairs |
6, 8, 0x0E, 0x10 to 0x13 |
entered |
fromSubmodule is 0 and the module just became a gameplay module |
other |
any remaining submodule returning to 0 |
Subscribing
import { subscribeTransitionSettled } from '@app/lib/game';
const unsubscribe = subscribeTransitionSettled((event) => {
// event.kind, event.isIndoors, event.roomIndex, event.owScreenIndex
});
The bridge installs window.__onTransitionSettled once per game session (wired into
lifecycle.ts alongside the tracker and haptic bridges), and fans out to every subscriber.
Subscribing when the setting is off is harmless: the callback is never invoked.
Known gap
A room tag that rewrites collision without passing through a submodule produces no
event. RoomTag_OperateChestReveal is the clearest case: it stamps attrs directly while the
game sits at submodule 0. If a revealed chest turns out to need a refresh, the fix is one
more explicit GameHook_* call at that site, not a return to polling.