Posted December 12, 2025 by meapps
Posting a bit more info here in case it saves someone else some time.
Got the web build (-target:js_wasm32) stable after hitting two totally separate crashes. Here’s what broke and what I changed.
core:os not supported on JS/WASMWhat happened
The game would start booting, then immediately die with an Odin runtime error pointing at core:os being “not yet implemented” for the JS target. The browser console would then spiral into loader failures and eventually a WASM trap.
Why it happened
settings_save.odin was calling core:os directly (os.read_entire_file, os.write_entire_file). Those procedures simply aren’t implemented for JS/WASM in Odin, so the build was guaranteed to abort as soon as settings I/O ran.
The annoying part: the project already had the right abstraction in place. utils.odin exposes read_entire_file / write_entire_file with per platform implementations:
Native: delegates to core:os
Web: uses fopen/fread/fwrite against Emscripten’s virtual FS
…but settings_save.odin bypassed it.
Fix
settings_save.odin now uses the existing wrappers instead of importing core:os:
removed import "core:os"
swapped os.read_entire_file(...) → read_entire_file(...)
swapped os.write_entire_file(...) → write_entire_file(...)
Result
Web build can get past init again. This fixes the crash; persistence still depends on how Emscripten FS is mounted/synced in the actual deployment.
Aborted(alignment fault) from RNGWhat happened
After settings stopped killing the build, gameplay would crash shortly after starting with:
Uncaught RuntimeError: Aborted(alignment fault)
Stack trace pointed straight into RNG usage (7 bag shuffle / refill_and_shuffle) via rand.int_max(...).
Why it happened
WebAssembly traps on unaligned 64 bit memory access. Odin’s default RNG (core:math/rand) keeps 64 bit state internally, and in this configuration the RNG state ended up being touched in a way that triggered an unaligned 64 bit read/write instant WASM abort.
Fix approach
Stopped calling rand.* from gameplay code and added a tiny platform RNG layer:
Native builds: keep using core:math/rand
WASM builds: use a simple 32 bit xorshift (u32 state only) to avoid any 64 bit alignment issues
Code changes
Added two platform files with the same API:
source/random_web.odin (#+build wasm32, wasm64p32)
xorshift32-backed
web_rand_init(), game_rand_int_max, game_rand_int31_max, game_rand_float, game_rand_float_range
source/random_default.odin (#+build !wasm32 and !wasm64p32)
wrapper around core:math/rand with the same functions
Then in game.odin:
call web_rand_init() from game_init() (no-op on native)
replaced all gameplay RNG calls rand.* → game_rand_*
Result
No more alignment fault during gameplay. 7 bag shuffle and other RNG heavy bits run cleanly on the web build now.