How games get in
Written July 2026, when the engine was Lua-first; the studio is TypeScript-native now (ADR-013).
How New Games Get In: Google AI Studio → Shared Engine
New games start as standalone demos in Google AI Studio — a fast way to prototype a real mechanic without committing to the studio’s four-file contract before knowing if the idea holds up. The ones worth keeping get ported in.
ScrapCrawl is the proof this works. It shipped as a Google AI Studio TypeScript demo first, then got ported into the Lua contract as a standalone game — rooms, crafting, companion breeding, all of it, running through the same shared engine as everything else.
Not every port is clean, and that’s worth saying honestly. A separate demo — a lane-based wave-defense concept with a hex ring map — remains unported, because it’s a plain React/TypeScript/Vite SPA with no Lua, no PyGame, no data.yaml, and none of the shared hooks the rest of the studio depends on. Getting it in isn’t a deploy step; it’s either a genuine rebuild against the existing engine architecture, or the studio’s publishing pipeline would need a second, parallel path for arbitrary Vite SPAs that don’t speak the shared contract at all. That’s real, unresolved work, not a footnote — three of six games ported clean, one still sitting outside the fence.
A Real Bug, Found by Playing, Not by Tests
The test suites didn’t catch this one — live playtesting did. A studio-wide bug lived in the TypeScript rendering bridge: fengari (the Lua-in-JavaScript runtime powering the browser renderer) returns lua_toboolean as a native JavaScript boolean, not a C-style integer. Code written to check !== 0 against that return value was always true, regardless of the actual boolean state — silently corrupting every boolean flag in every browser-rendered game.
It was found via a Home Base fight exploit (a Fight button with no room-type validation, allowing infinite free wins), traced back to the shared bridge, and confirmed studio-wide by auditing all six games — the Python/PyGame bridge (lupa) handles booleans correctly natively, so this was specific to the browser rendering path only. Fixed with the same two-layer pattern used everywhere in this codebase: a backend Lua guard plus a matching frontend disabled state, applied consistently rather than patched once and hoped to hold elsewhere.