Multi-root games & play control
One Game, many adapter roots
Section titled “One Game, many adapter roots”A game is a manifest-backed root list. The manifest composer mounts every
entry onto one Game — any number of threejs, pixijs, and react adapters,
stacked as layers in a single host container (per-root zOrder),
with one shared loop and a delegating input router. Each first-party world carries
its own ComponentManager; the Game spans them at query time (below) rather
than merging them into one:
{ "manifestVersion": 2, "name": "my-game", "roots": [ { "id": "main", "adapter": "threejs", "scene": "scenes/level.vscn.json" }, { "id": "overlay", "adapter": "pixijs", "scene": "hud.scn2d.json", "zOrder": 10 } ]}- The native node is the entity for gameplay adapters:
Object3Din Three.js roots,PIXI.Containerin pixijs worlds; react worlds render from state and host no components. - Cross-world queries:
game.queryByComponent(Cls)spans every first-party world in one call (narrow with{worldId}or{kind}). - In the editor, the hierarchy presents native Three.js, PixiJS, and React roots; selection and inspection work in every root (the 3D transform gizmo operates in threejs worlds only).
Play control: pause, resume, step
Section titled “Play control: pause, resume, step”Game.play is the one game-level control surface (D10/T7.6), with per-world
pausable semantics — a menu or HUD world declaring "pausable": false keeps
ticking while everything else freezes:
pause()— freezes everypausableworld’s simulation. The loop itself never stops: a frozen world still runs itsrenderphase every frame withdt = 0(the screen doesn’t go black; time-based render effects don’t creep), and worlds that can’t be gated (self-driven mounts with nosetPausedcapability) are reported loudly, once, instead of silently ignored. Audio is muted per-world the same honest way.resume()— the inverse; both are idempotent.step(dt?)— advances exactly the frozen worlds by one fixed substep (realdt). Non-pausable worlds are untouched — the still-running loop already ticks them — and while the game isn’t paused,step()is a no-op.- React HUDs stay honest while paused:
Game.stateonly notifies subscribers on frames where something actually advanced, so a fully-frozen game produces no notifications, and astep()that advances a host-driven world produces exactly one. (One recorded edge: a paused game whose only frozen worlds are self-driven — mounts the host doesn’t tick — advances them onstep()without a notification; such worlds report state through their ownobservebridge, notGame.state.)
The editor’s play bar (pause/step buttons) and the CLI (vgai pause /
vgai resume / vgai step) both drive this same surface.
See also
Section titled “See also”- React roots — DOM adapter roots.
- 2D worlds (world2d) — PixiJS adapter roots.
- The GameContext & runtime — session lifecycle.