The tri-world capstone — one clock, three native renderers
site/media/lessons/learn-triworld.lesson.ts (5 steps) This is the capstone of the multi-root arc: one game, all three built-in adapters.
Three.js owns a point-field sculpture and the clock state. PixiJS reads that state to
draw a precise dial. React reads the same state to render sparse annotations. They remain
native renderer trees, but the host mounts them as one Game and the editor presents
them through one hierarchy.
The shared state seam
Section titled “The shared state seam”The Three.js scene attaches SignalField to TemporalClockField. The component owns
the clock values as well as the point sculpture:
export class SignalField extends GameComponent { hours = 0; minutes = 0; seconds = 0; milliseconds = 0; timeLabel = '--:--:--';
update(dt: number, ctx: GameContext): void { const now = new Date(); this.hours = now.getHours(); this.minutes = now.getMinutes(); this.seconds = now.getSeconds(); this.milliseconds = now.getMilliseconds(); // Update the native Three.js point field here too. }}Both scene formats resolve behavior from their native registries:
export const componentRegistry = { SignalField } satisfies ComponentRegistry;export const pixiComponentRegistry = { TacticalMap } satisfies Component2DRegistry;PixiJS receives the shared Game in its world context and queries the authoritative
component. There is no mirrored clock model:
const field = ctx.game?.queryByComponent(SignalField, { worldId: 'main' })[0];if (!field) return;
const secondAngle = ((field.seconds + field.milliseconds / 1000) / 60) * Math.PI * 2 - Math.PI / 2;React uses the same query through useGameState, which subscribes to selected-value
changes without a polling timer:
const state = useGameState((game) => { const field = game.queryByComponent(SignalField, { worldId: 'main' })[0]; return { timeLabel: field?.timeLabel ?? '--:--:--', signalStrength: field?.signalStrength ?? 0, };}, shallow);Semantic hierarchy names
Section titled “Semantic hierarchy names”Hierarchy identity comes from each renderer’s ordinary conventions:
- Three.js:
Object3D.name - PixiJS:
Container.label/ display-object labels - React: accessible HTML —
aria-labelledby,aria-label, headings, direct text,alt,title, orid, with the tag name only as a fallback
For example, the HUD’s section is regular semantic HTML:
<section aria-labelledby="projection-state-title"> <h2 id="projection-state-title">PROJECTION STATE</h2> <p>PixiJS reads the live component directly—no mirrored model.</p></section>The hierarchy calls that node PROJECTION STATE. No vgai-only naming attribute is
required. An explicit data-vgai-name remains supported only as an escape hatch when a
native tree genuinely has no useful semantic identity.
-
Press Play. One
Gamemounts the Three.js canvas, the transparent PixiJS canvas, and the React DOM root. Together they form one restrained, geometric clock.
Three native renderers, one composition. -
Watch the clock move. The Pixi hands and React metrics change with the real local time because both query the
SignalFieldliving in the Three.js root.
Cross-world state through the shared Game root, with no mirror or polling loop. -
Read the hierarchy. Expand Three.js, PixiJS, and React. You see domain names such as
TemporalPointBloom,ClockDialConstruction, andPROJECTION STATE, not raw renderer primitives such asGraphicsorsection.
Native naming and accessibility semantics produce an intelligible tree. -
Select
TemporalProjection. The same hierarchy and inspector now route to the PixiJS root and expose its 2D transform. -
Edit Position-Y, then Stop and Play. The edit is visible immediately while you inspect the running game. A new Play session reconstructs the root from authored source, so the value returns to
0. Play mode is an ephemeral preview; durable edits belong in Edit mode or the source scene.
The inspector is shared; Play-mode mutation is intentionally temporary.
Recap
New functionality
- Declared a three-root game in vgai.game.json
- Shared one live clock component with PixiJS and React
- Inspected all three native trees through semantic hierarchy names
New concepts & skills
- one Game mounts every adapter root and stacks surfaces by zOrder
- queryByComponent spans roots without a mirrored state model
- native object names and accessible HTML are the hierarchy contract
- Play-mode edits are ephemeral previews
Next lesson → Multi-world games & play control (manual)
On Your Own!
Extend what you built:
- Add a date ring to the PixiJS projection using the same SignalField
- Create a second React world for an interactive clock settings panel
- Expose a time-zone property on SignalField and add it to Storybook design states