Skip to content

Entities & Behavior

There is no separate entity layer in vgai — no ECS, no scene-node wrapper, no mirror tree. The rendering library’s own node is the entity: a Three.js Object3D in a three world, a PIXI.Container in a canvas world, a DOM element in a dom root. Anything you can do to an entity, you do with the library’s own API on the library’s own object.

Engine services — input, physics, audio, RNG — flowing into world code through the world context
World code reaches engine services through the world context; the nodes themselves are plain library objects.

Behavior is code in the world’s own idiom

Section titled “Behavior is code in the world’s own idiom”

There is deliberately no engine component framework. Each root kind carries behavior the way its own ecosystem does:

  • Three (R3F) worlds — behavior is a React component calling useFrame. Its props are its configuration (the inspector types them straight from the JSX literal), its hooks are its state, and the engine services it needs — input, physics, audio, deterministic RNG — come from the world context (packages/engine/src/world3d-react/world-context.ts). A subsystem the world did not declare is a compile error, not a runtime surprise.
function Spinner({ speed = 1 }: { speed?: number }) {
const ref = useRef<Mesh>(null);
useFrame((_, dt) => {
if (ref.current) ref.current.rotation.y += dt * speed;
});
return (
<mesh ref={ref}>
<boxGeometry />
<meshStandardMaterial color="tomato" />
</mesh>
);
}
  • Canvas (Pixi TSX) worlds — the same shape through @pixi/react: behavior is a component calling useTick, and the host drives the tick (deltaMS is game time — a paused world receives no ticks, so Edit and Play stay distinct without any behavior-side code).
  • Ingest and module roots — the game’s own source owns its loop; the host hands it update(dt) and layer/lifecycle, nothing more.
  • Dom roots — React renders from game state via useWorldState; DOM entities host no per-frame logic.

A world’s live component state is React state, so the usual HMR semantics apply: an edited component re-renders in place and hooks keep their values where React can preserve them. There is no engine-side instance registry to keep in sync — the world’s source is the only authority.

Gameplay code can query a loaded graph without bookkeeping its own indexes:

  • queryByTag(root, tag) / queryByName(root, name) (packages/engine/src/ecs/scene-query.ts) — one-shot depth-first walks reading userData['tags'] and Object3D.name.
  • ctx.sceneIndex — the live index over the same storage: answers without traversing and dispatches entityadded / entityremoved / tagadded / tagremoved events when the answer changes.