Contributing Adding A WASM Function - drizztdourden08/relic-of-the-past GitHub Wiki
Crossing the C↔TS boundary touches two places. Miss one and it either fails silently, since the
dead code gets stripped, or throws at the ccall. Do both, then rebuild WASM.
Pick the direction first. The repo's add-wasm-function skill scaffolds this, and the
hooks reference covers the existing surface and buffer conventions.
For reading a game value/state or commanding the game.
1 · C implementation in core/game-hooks/<domain>.c (state_queries*.c for reads, cheats.c,
check_triggers.c, ui_state.c, item_overrides.c, ...):
#include "game_hooks_internal.h"
EMSCRIPTEN_KEEPALIVE
int WasmGetMyFlag(void) { return some_game_global ? 1 : 0; }
// Bulk data: fill a static buffer, return its address.
static uint8 g_my_buf[N];
EMSCRIPTEN_KEEPALIVE
int WasmGetMyData(void) { g_my_buf[0] = /* ... */; return (int)g_my_buf; }
EMSCRIPTEN_KEEPALIVE
void WasmSetMyThing(int value) { /* ... */ }EMSCRIPTEN_KEEPALIVE is mandatory. It both retains and exports the symbol, so there's no
EXPORTED_FUNCTIONS list to edit in build.bat/Makefile. Add a declaration to game_hooks.h only
when other C code calls it.
2 · TypeScript caller in apps/web/src/lib/game/ (the right bridge/* facade or domain module):
const flag = mod.ccall('WasmGetMyFlag', 'number', [], []) as number; // scalar
mod.ccall('WasmSetMyThing', null, ['number'], [value]); // args
const ptr = mod.ccall('WasmGetMyData', 'number', [], []) as number; // pointer
const bytes = mod.HEAPU8.subarray(ptr, ptr + N); // read the documented layoutGuard on module readiness like the existing helpers, document the buffer layout, and follow the coding standards (exports at end, destructure first line).
For notifying the UI of a game event.
1 · C side (core/game-hooks/<domain>.c + declare in game_hooks.h):
void GameHook_NotifyMyEvent(uint8 arg) {
EM_ASM({ if (typeof window !== 'undefined' && window.__onMyEvent) window.__onMyEvent($0); }, arg);
}2 · Call site in upstream core/zelda3/: insert the GameHook_NotifyMyEvent(...) call at the game
event. This is the one sanctioned reason to edit zelda3/: a single call line, with the logic staying in
game-hooks/.
3 · JS registers the handler in the renderer: window.__onMyEvent = (arg) => { ... }, mirroring how
window.__onItemReceived is wired. This direction needs no EXPORTED_FUNCTIONS entry.
- Rebuild WASM, since C changes only take effect after a rebuild.
- Restart
npm run dev. - Trigger the function/event and confirm. A "function not found" at
ccallmeans the C function is missing itsEMSCRIPTEN_KEEPALIVEtag, or the WASM wasn't rebuilt.