Split glemy/game.gleam into glemy/game_tiers.gleam plus a shared game_ffi.mjs, in preparation for a second game runner
Context
Starting work on glemy's second reference game (a Breakout/Arkanoid-style paddle-and-brick game, chosen specifically to stress-test the Core API's genericity claims established across decisions 0047-0050 with a genuinely genre-distinct second consumer). glemy/game.gleam's own doc comment already anticipated this moment explicitly ('a second game would get its own runner shaped like this one, gluing io/render to its own tick function'), but the module and its FFI file were both named and shaped for exactly one game. game_ffi.mjs itself was a mix of genuinely generic runner infrastructure (requestFrame, its error-reporting wrapper, canvas-layout conversion for input) and Tiers-specific DOM/audio writes (setScoreText, setGameOver, setDangerLinePosition, setNextTierColor, playDropSound, playMergeSound, triggerMergeFlash) that a second game's runner has no use for and shouldn't import.
Options considered
Decision
Renamed glemy/game.gleam to glemy/game_tiers.gleam (mechanical, git mv, mirrors the pe->physics rename precedent) and split glemy/game_ffi.mjs: requestFrame, its error-reporting wrapper, and canvasBoundingRectLeftAndWidth stay in game_ffi.mjs as genuinely generic runner infrastructure every game's runner needs; setScoreText/setGameOver/setDangerLinePosition/setNextTierColor/playDropSound/playMergeSound/triggerMergeFlash (all Tiers-specific DOM/audio writes) moved to a new glemy/game_tiers_ffi.mjs. Test files/helpers renamed to match (game_test.gleam -> game_tiers_test.gleam, game_test_ffi.mjs -> game_tiers_test_ffi.mjs). index.html's module import updated to the renamed compiled output. Established the standing convention (documented in ARCHITECTURE.md): each game gets its own game_<name>.gleam + game_<name>_ffi.mjs, mirroring games/<name>/'s existing one-tree-per-game convention one layer up at the Shell boundary, with game_ffi.mjs remaining the one shared file for genuinely generic runner concerns. While touching these files, also fixed several pre-existing stale doc-comment references found along the way: a duplicated '(now glemy/physics)' artifact left over from the pe->physics rename's own bulk sed (in io.gleam), a stale glemy/pe/bounds reference in tools/browser_check.ts, and a stale clamp_preview_x citation in ARCHITECTURE.md (that function was replaced by physics/bounds.clamp_x in decision 0050).
Verification
gleam test and gleam test --target javascript both pass with identical counts to before this change (195 Erlang / 218 JavaScript) -- confirming the rename and FFI split changed no behavior, only structure. deno task check-warnings passes after updating the baseline (51/51, same count, only paths renamed) -- every diff confirmed as the already-documented @target(javascript) target-gating artifact under its new filename, not a new issue. deno task browser-check passes for real against the renamed runner (182 real requestAnimationFrame frames, a real click-and-hold grew the entity count 4 -> 5, a real entity spawned near the expected world-x, zero page errors), confirming Tiers' actual running game is unaffected end-to-end.
Consequences
glemy/game_ffi.mjs's exports are now unambiguously safe for any future game's runner to import; a second game's own game_<name>_ffi.mjs starts from a clean slate with no risk of accidentally reaching for a Tiers-specific DOM write. This is the last piece of foundational restructuring before the Breakout game's own logic and rendering land in subsequent, separately-verified steps (this project's roadmap tracks it as a multi-phase initiative, not one change).