RFDGameStudio
The Idea
Most game engines are built for one game. The engine I wanted to build is a studio — a set of shared primitives that compounds across games rather than getting rewritten from scratch every time.
RFDGameStudio is six complete games sharing one engine. Every mechanic that survives production in one game gets promoted to a primitive available to all games that come after.
The Six Games
ScrapCrawl — A room-based crawler with scrap-to-equipment crafting and win-only proficiency growth, plus companion breeding. Originally prototyped in Google AI Studio, then ported into the shared engine — the first full validation that an AI Studio demo could actually make the jump into this architecture.
Chimera Wilds — A six-part chimera encounter game, explored as a follow-on pivot from ScrapCrawl’s crafting systems.
Horse Racing Sim — Discrete event engine. Horses have genetics, form, and career stats. You bet, breed, and build a stable. Tests the Port-Engine pattern: pure Lua logic exposed as a clean API that both TypeScript and PyGame consume identically.
Slither Rogue — Real-time snake roguelike with evolution cards. Collision detection, real-time tick loop, emergent power systems. Tests the real-time physics primitive at speed.
Mutant Battle Ball — 2v2 possession-based combat sport. Mutants have parts (head, chest, arms, legs) that drive stats and visual appearance. Tests the possession system, AI role assignment, and composite figure rendering.
Slime Coin — Coin-pusher roguelike with a token economy. Three physics layers (shelf, floor, vat), pairwise chip synergies, a shot queue, and a mid-round exchange system. Tests economy loops and physics simulation.
The Engine
The stack is deliberately minimal:
data.yaml -- entity definitions, config, opponent data
logic.lua -- game simulation (runs identically everywhere)
TypeScript -- browser renderer (Vite + React)
PyGame -- desktop renderer (same logic.lua, different canvas)
Lua was chosen because it runs in both environments without modification. The same tick_game(dt, input) call that drives the browser also drives PyGame. The renderers are interchangeable. The logic is never duplicated.
Shared primitives in engine/primitives/:
movement.lua— vector math, move_toward, atan2physics.lua— grid collision, wall bounce, coin physicsaction.lua— input handling, collect() for Lua/Python bridgingresolution.lua— weighted outcomes, affinity scoringlifecycle.lua— entity lifecycle event constants
Each primitive was extracted when the same logic appeared in two or more games. Nothing was abstracted speculatively.
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.
The Methodology
Every feature starts as a directive:
- Scope defined — files listed, read-only files named explicitly
- Test anchors written — behavior specified before implementation
- Agent implements
- Floor verified — raw pytest/vitest output only, never agent summaries
- Phase certified — floor locked, next phase begins
Current certified floors: 194 passed, 0 failed (Python) and 76 passed, 0 failed (TypeScript).
No phase advances without a passing floor. No exceptions.
The MCP Server
RFDStudioMCP runs as an NSSM service with 11 tools for live game inspection:
studio_load_game— load any game by IDstudio_get_state— inspect live GAME_STATEstudio_call— call any Lua function directlystudio_screenshot— render a frame and return PNGstudio_run_tests— run the full test suitestudio_balance_report— simulate N races, return win distribution
The studio is designed to be interrogated, not just played.
GitHub: RFDGameStudio
Built with Lua, TypeScript, and Python. Six games, one engine. The engine is the point.