Files
dusk/.claude/scene.md
T
2026-06-16 13:07:21 -05:00

3.8 KiB

Scene System

Source: src/dusk/scene/

Overview

The scene system is the top-level coordinator for a running game state. It manages one active scene at a time. Scenes are JS scripts -- each scene is a .js asset file that exports an object with lifecycle hooks. The scene system loads, ticks, and tears down these scripts, while the C side runs the ECS and render pipeline on each tick.

Scene lifecycle (C side)

extern scene_t SCENE;

errorret_t sceneInit(void);     // initialise the scene manager
errorret_t sceneUpdate(void);   // process pending transition, tick active scene
errorret_t sceneRender(void);   // render entities + render pipeline + UI
errorret_t sceneDispose(void);  // dispose the active scene immediately

sceneUpdate each tick:

  1. Checks for a pending scene transition and performs it (dispose old, load and init new).
  2. Calls the JS scene's update() hook.
  3. Calls entityManagerUpdate() to fire all entity update callbacks.

sceneRender each tick:

  1. Binds the screen.
  2. Calls sceneRenderPipeline() -- renders all entities with a COMPONENT_TYPE_RENDERABLE in priority order.
  3. Renders UI.
  4. Calls the JS scene's render() hook (for any custom drawing).
  5. Unbinds the screen.

Scene lifecycle (JS side)

A scene file exports a plain object with these optional hooks:

var scene = {};

scene.init = async function() {
  // Load assets, create entities, set up state.
  // May be async -- await asset loads here.
};

scene.update = function() {
  // Called each fixed-timestep tick.
};

scene.render = function() {
  // Called each render tick, after ECS renderables.
};

scene.dispose = function() {
  // Clean up entities and state.
};

module.exports = scene;

See CLAUDE.md -- "JavaScript (asset scripts)" for JS style rules.

Render pipeline (scenerenderpipeline.h)

sceneRenderPipeline(cameraEntityId) gathers all active COMPONENT_TYPE_RENDERABLE components, sorts them by effective priority, and draws each one using its shader.

Priority rules:

  • renderable.priority != 0 -- use that value directly.
  • renderable.priority == 0 -- auto-derive: opaque geometry sorts before transparent geometry; sprite batches sort before shader materials; etc.
  • Lower priority number = drawn first (behind); higher = drawn last (on top).

The shader used for each renderable:

  • ENTITY_RENDERABLE_TYPE_SPRITEBATCH and CUSTOM default to SHADER_LIST_SHADER_UNLIT.
  • ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL uses the shader indexed by renderable.data.material.shaderType in SHADER_LIST_DEFS.

Transitioning between scenes

Scene transitions are handled entirely in JS via the Scene global. The Scene object is a singleton with:

// Switch to a new scene. Calls dispose() on the current scene, then
// init() on the new one. Both happen synchronously this tick.
Scene.set(newSceneObject);

// The current scene object (may be null):
Scene.current

Typical scene-switch pattern:

// Inside a scene's update or event handler:
const nextScene = require("scenes/gameplay.js");
Scene.set(nextScene);

Scene.set is synchronous -- it calls dispose on the old scene and init on the new scene before returning. If init needs async work (loading assets), use an async function and await inside init:

nextScene.init = async function() {
  await batch.load();  // wait for assets before proceeding
};

The C side does not defer the transition; the switch happens inside the current sceneUpdate call.

Relationship to the engine loop

engineUpdate()
  timeUpdate()
  inputUpdate()
  physicsManagerUpdate()
  scriptUpdate()       <- runs JS microjobs
  sceneUpdate()        <- JS update + ECS entity updates

engineUpdate() -> sceneRender()
  screenBind()
  sceneRenderPipeline()  <- ECS renderables sorted by priority
  uiRender()
  sceneRender (JS hook)
  screenUnbind / screenRender