The game owns the world.
The application draws the ship, cargo, beacons and debris with Canvas 2D. A transparent WebGL 1 canvas sits above it. Ludic Canvas owns the HUD, manifest, cargo tabs and pause menu.
PLAYABLE EXAMPLES
Two small games. Two rendering engines. Every in-game interface is built with Ludic Canvas.
Canvas 2D world + WebGL UI
Orbital Relay selected.
INSIDE THE EXAMPLE / 2D
The application draws the ship, cargo, beacons and debris with Canvas 2D. A transparent WebGL 1 canvas sits above it. Ludic Canvas owns the HUD, manifest, cargo tabs and pause menu.
React reads immutable simulation snapshots at 10 Hz. Components call game actions to select a destination, boost or pause. Oxanium and Space Mono come from separate interface and mission modules; the UI root combines their generated MSDF atlases.
The loop advances the simulation, draws the world and calls ui.frame(now). ResizeObserver keeps both canvases aligned. Modal scopes isolate pause input, and scene teardown destroys the UI root and removes observers and listeners.
const ui = await createUI({
canvas: overlayCanvas,
autoStart: false,
fonts: [interfaceFont, telemetryFont],
});
ui.render(<Hud game={game} width={width} height={height} />);
function frame(now: number) {
game.tick(deltaSeconds);
world.draw(game, width, height, reducedMotion); // Application-owned Canvas 2D
// The game model publishes immutable snapshots at 10 Hz.
ui.frame(now);
requestAnimationFrame(frame);
}INSIDE THE EXAMPLE / 3D
Three.js owns the WebGL 2 scene, rover, terrain, camera and shadows. Ludic Canvas draws the complete custom interface in a transparent WebGL 1 overlay. Both renderers receive the same CSS dimensions; they use separate graphics contexts.
Three.js projects site positions into CSS-pixel coordinates for canvas labels. Terrain clicks travel through the UI input surface to a Three.js raycast. Blocking panels keep their clicks out of world navigation. Camera sliders and modal menus use the library's pointer and keyboard semantics. Collection → Regional archive opens a 4,000-record VirtualList with logical focus, application-owned selection and responsive rows; the three live samples remain a short ordinary list.
A plain game model owns navigation and sampling. React reads snapshots at approximately 14 Hz. Feature modules define two MSDF font families, composed at createUI. One loop advances bounded simulation steps, draws Three.js and frames the UI. Each renderer handles its context recovery and disposal.
const world = createSurveyWorld(worldCanvas); // Three.js / WebGL 2
const ui = await createUI({
canvas: overlayCanvas,
autoStart: false,
fonts: [displayFont, telemetryFont],
});
ui.render(<SurveyHud game={game} width={width} height={height}
onGround={(x, y) => {
const hit = world.pick(x, y); // CSS pixels -> terrain raycast
if (hit) game.driveTo(hit.x, hit.z);
}} />);
function frame(now: number) {
advanceSurvey(game, deltaSeconds);
world.draw(game.read(), width, height, deltaSeconds);
game.publish(world.project()); // 3D positions -> CSS-pixel labels
ui.frame(now);
requestAnimationFrame(frame);
}The gallery runs one example at a time in an iframe so each preview owns its game session. The iframe is a website preview choice. In your game, mount Ludic Canvas directly beside your renderer, as the example source shows.