Skip to content

Design philosophy

vgai’s design is shaped by one north star: it should be a great engine for an AI (or a human) to author games in. That yields three priorities.

There should be one obvious place and one obvious way to do a thing. The engine avoids configurable indirection in favor of a single, predictable path. The clearest expression of this is phase ordering.

Every fixed tick runs systems and GameComponent.update() methods in a fixed order:

#PhaseWhat runs
1inputSample keyboard / mouse / gamepad.
2prePhysicsApply forces/intents to rigid bodies.
3physicsStep the Rapier world.
4postPhysicsRead physics back; sync transforms to Object3D; dispatch triggers.
5gameLogicGeneral gameplay. Default phase for GameComponents.
6animationAdvance animation graphs / mixers.
7preRenderCamera/HUD fixups before drawing.
8renderDraw the frame.

A component declares its phase with a static field; data flows in one direction through the phases, so you never wonder “did this run before or after physics?”

A feature lives in one place. The scene format, the Zod schema that validates it, and the default values that the editor and runtime share are all single sources of truth in packages/engine/src/scene/. Change the schema once and the editor inspector, JSON-Schema autocomplete, and runtime validation all update together.

Bad data should fail loudly at load time, not silently at frame 10,000. Unknown component names throw when a scene loads; schema parsing rejects malformed fields. npm run validate-scenes checks every scene against the registry before you ship.

Because the editor edits the very Object3Ds the runtime uses, two things fall out for free:

  • Hot reload of behavior — swap a GameComponent’s code and keep its live state.
  • Editor-edits-truth — what you see in the editor is what the game runs.

Every game UI is declared in vgai.game.json as a root with "adapter": "react" and an entry. It mounts as a DOM layer in the same z-ordered host stack as Three.js and PixiJS canvases. JSX source is truth, so the editor hierarchy can inspect it and style/className edits write back to the JSX. There is no hidden HUD mount API or implicit UI container.

React reads live game state through the engine’s GameProvider and useGameState selectors (zero polling). React nodes do not host GameComponents.