> Cinder 0.1.0 documentation: cinder.scene API
> Canonical page: https://cinder.ash10.dev/docs/api/scene/ · Source: `docs/api/scene.md` in the engine repository (engine commit 47c9352, imported 2026-11-09)
> Index of every page for agents: https://cinder.ash10.dev/llms.txt

# `cinder.scene`


## `cinder.scene`

Source: `cinder/scene/__init__.mojo`

Scene files: versioned text and binary formats with migrations, over the ECS world.


## `cinder.scene.format`

Source: `cinder/scene/format.mojo`

Scene serialisation: a human-diffable text format and a binary format, versioned, with migrations. Both formats carry the same intermediate representation (`SceneDoc`: entities with component records of typed fields), so a document can be read in either format, migrated version by version, and written in either. Component types take part by implementing `SceneComponent` and being registered in a `SceneRegistry`, which stores type-erased read/write functions. Text format (version 2): cinder-scene 2 entity 0 Position x=1.5 y=-2 Label text="hello \\"world\\"" entity 1 Parent entity=#0 Floats are written as the shortest decimal that round-trips, or as `0x...` IEEE bits when the decimal form does not reproduce the value exactly. Entity references (`#n`) are file-local indices remapped on load. Binary: magic `CSCN`, version, then the same records with type tags.

- `comptime SCENE_VERSION = 2` — Version this engine writes and instantiates; older documents are upgraded by `SceneRegistry.migrate`, newer ones are rejected.
- `comptime MAGIC = "CSCN"` — The four bytes that open a binary scene file.
- `comptime KIND_FLOAT = 1` — A single Float32 in `Field.floats[0]` (also mirrored in `Field.number`).
- `comptime KIND_INT = 2` — A signed 64-bit integer in `Field.integer`.
- `comptime KIND_BOOL = 3` — A boolean in `Field.integer` (0 or 1); `true`/`false` in the text format.
- `comptime KIND_STRING = 4` — Text in `Field.text`; written quoted and escaped in the text format.
- `comptime KIND_ENTITY = 5` — A file-local entity index in `Field.integer` (-1 = no entity); `#n` in the text format.
- `comptime KIND_FLOATS = 6` — A fixed list of floats (vec3, vec4, mat4 ...).
### struct `Field`

```mojo
struct Field(Copyable, Movable)
```

One named, typed value of a component record. `kind` is a `KIND_*` constant and selects which of `number`, `integer`, `text` or `floats` holds the value; the others keep their zero defaults.

Fields:

- `name`: String
- `kind`: Int
- `number`: Float64
- `integer`: Int64
- `text`: String
- `floats`: List[Float32]

- **`__init__`** `__init__(out self, name: String, kind: Int)`

### struct `ComponentRecord`

```mojo
struct ComponentRecord(Copyable, Movable)
```

A component as stored in a scene: its scene name and its fields in write order.

Fields:

- `name`: String
- `fields`: List[Field]

- **`__init__`** `__init__(out self, name: String)`
- **`find`** `find(self, name: String) -> Int`  
  Index of the field called `name`, or -1.
- **`rename_field`** `rename_field(mut self, old: String, new: String) -> Bool`  
  Renames field `old` to `new` (for migrations); returns False when there is no field `old`. An existing field called `new` is not checked for.

### struct `EntityRecord`

```mojo
struct EntityRecord(Copyable, Movable)
```

One entity of a scene: the records of the components it carries.

Fields:

- `components`: List[ComponentRecord]

- **`__init__`** `__init__(out self)`
- **`find`** `find(self, name: String) -> Int`  
  Index of the component record called `name`, or -1.

### struct `SceneDoc`

```mojo
struct SceneDoc(Movable)
```

The format-independent scene: a version and the entities in file order. Entity fields hold indices into `entities`. Produced by `SceneRegistry.to_doc`, `read_text` and `read_binary`; consumed by `SceneRegistry.migrate`, `SceneRegistry.instantiate`, `write_text` and `write_binary`.

Fields:

- `version`: Int
- `entities`: List[EntityRecord]

- **`__init__`** `__init__(out self, version: Int = SCENE_VERSION)`

### struct `FieldWriter`

```mojo
struct FieldWriter(Movable)
```

Collects a component's fields. Entities are written as file-local indices.

Fields:

- `record`: ComponentRecord
- `index_of`: Pointer[List[Int], MutUntrackedOrigin] — Entity slot index -> file-local index (-1 when not in the file).

- **`__init__`** `__init__( out self, name: String, index_of: Pointer[List[Int], MutUntrackedOrigin] )`
- **`float`** `float(mut self, name: String, v: Float32)`  
  Appends a `KIND_FLOAT` field.
- **`int`** `int(mut self, name: String, v: Int)`  
  Appends a `KIND_INT` field.
- **`bool`** `bool(mut self, name: String, v: Bool)`  
  Appends a `KIND_BOOL` field (stored as 0 or 1).
- **`string`** `string(mut self, name: String, v: String)`  
  Appends a `KIND_STRING` field.
- **`entity`** `entity(mut self, name: String, e: Entity)`  
  Appends a `KIND_ENTITY` field with `e`'s file-local index: -1 when `e` is null, beyond the index table or not part of the snapshot. Only the slot index is looked up; the generation is not checked.
- **`floats`** `floats(mut self, name: String, values: List[Float32])`  
  Appends a `KIND_FLOATS` field holding a copy of `values`.
- **`vec3`** `vec3(mut self, name: String, v: Vec3)`  
  Appends `v` as a three-float `KIND_FLOATS` field.
- **`mat4`** `mat4(mut self, name: String, m: Mat4)`  
  Appends `m` as a 16-float `KIND_FLOATS` field in column-major order.

### struct `FieldReader`

```mojo
struct FieldReader(Movable)
```

Reads a component's fields by name; missing fields take the given default.

Fields:

- `record`: ComponentRecord
- `entities`: Pointer[List[Entity], MutUntrackedOrigin] — File-local index -> spawned entity.
- `error`: String

- **`__init__`** `__init__( out self, var record: ComponentRecord, entities: Pointer[List[Entity], MutUntrackedOrigin], )`
- **`float`** `float(mut self, name: String, default: Float32) -> Float32`  
  Field `name` as a float, or `default` when absent. An integer field is converted; any other kind sets `error` and returns `default`.
- **`int`** `int(mut self, name: String, default: Int) -> Int`  
  Field `name` as an integer, or `default` when absent; another kind sets `error` and returns `default`.
- **`bool`** `bool(mut self, name: String, default: Bool) -> Bool`  
  Field `name` as a boolean, or `default` when absent; another kind sets `error` and returns `default`.
- **`string`** `string(mut self, name: String, default: String) -> String`  
  Field `name` as a string, or `default` when absent; another kind sets `error` and returns `default`.
- **`entity`** `entity(mut self, name: String) -> Entity`  
  Field `name` remapped to the spawned entity. Returns the null entity when the field is absent, not an entity field, -1 or out of range; never sets `error`.
- **`floats`** `floats(mut self, name: String, count: Int) -> List[Float32]`  
  The `count` floats of field `name`. Returns `count` zeros when the field is absent, and also when it is not a `KIND_FLOATS` field of exactly `count` values, in which case `error` is set.
- **`vec3`** `vec3(mut self, name: String, default: Vec3) -> Vec3`  
  Field `name` as a Vec3, or `default` when absent; a field of another shape sets `error` and yields zero.
- **`mat4`** `mat4(mut self, name: String) -> Mat4`  
  Field `name` as a column-major Mat4, or identity when absent; a field of another shape sets `error` and yields an all-zero matrix.

### trait `SceneComponent`

```mojo
trait SceneComponent(Component)
```

A component that can be saved: name, field writer and reader.

- **`scene_name`** `scene_name() -> String`  
  The component's name in scene files, matched against component records on load.
- **`write_scene`** `write_scene(self, mut w: FieldWriter)`  
  Writes the component's fields into `w`.
- **`read_scene`** `read_scene(mut r: FieldReader) -> Self`  
  Builds the component from `r`, with defaults for missing fields. When `r.error` is set afterwards (the typed getters set it on a kind mismatch) the component is not added and `SceneRegistry.instantiate` raises.

- `comptime WriteFn = def( Pointer[World, MutUntrackedOrigin], Entity, Pointer[FieldWriter, MutUntrackedOrigin], ) thin -> Bool` — Type-erased writer of one component type: fills the `FieldWriter` and returns False when the entity lacks the component or writing it raised.
- `comptime ReadFn = def( Pointer[World, MutUntrackedOrigin], Entity, Pointer[FieldReader, MutUntrackedOrigin], ) thin -> Bool` — Type-erased reader of one component type: builds the component from the `FieldReader`, adds it to the entity and returns False when the reader recorded an error or the add failed.
- `comptime MigrationFn = def(Pointer[SceneDoc, MutUntrackedOrigin]) thin -> None` — Upgrades a `SceneDoc` in place from one version to the next; `SceneRegistry.migrate` then increments `version` itself.
### struct `SceneRegistry`

```mojo
struct SceneRegistry(Movable)
```

Known component types and version migrations.

Fields:

- `types`: List[_Registered]
- `migrations`: List[Tuple[Int, MigrationFn]] — (from_version, migration) pairs; each converts a document from `from_version` to +1.

- **`__init__`** `__init__(out self)`
- **`register`** `register[T: SceneComponent](mut self)`  
  Makes `T` saveable under `T.scene_name()`. Duplicate names are not rejected; lookups on load use the first registration.
- **`add_migration`** `add_migration(mut self, from_version: Int, migration: MigrationFn)`  
  Registers `migration` to upgrade documents at `from_version` to `from_version + 1`.
- **`to_doc`** `to_doc(self, mut world: World) raises -> SceneDoc`  
  Snapshots every live entity (in archetype order) with its registered components.
- **`migrate`** `migrate(self, mut doc: SceneDoc) raises`  
  Applies registered migrations until the document is at SCENE_VERSION.
- **`instantiate`** `instantiate( self, mut world: World, doc: SceneDoc ) raises -> List[Entity]`  
  Spawns the document's entities (after migration) and returns them in file order.

### def `format_float`

```mojo
def format_float(v: Float32) -> String
```

Shortest decimal that round-trips, else the IEEE bits as 0x....


### def `parse_float`

```mojo
def parse_float(text: String) raises -> Float32
```

Parses a float written by `format_float`: a decimal, or `0x` followed by up to 8 hex digits of IEEE bits. Raises on malformed text.


### def `bitcast_u32_to_f32`

```mojo
def bitcast_u32_to_f32(bits: UInt32) -> Float32
```

Reinterprets IEEE-754 single-precision bits as a Float32.


### def `write_text`

```mojo
def write_text(doc: SceneDoc) -> String
```

Serialises `doc` in the text format described in the module docstring: a `cinder-scene N` header, one `entity i` line per entity and one indented `Name key=value ...` line per component.


### def `read_text`

```mojo
def read_text(text: String) raises -> SceneDoc
```

Parses the text format into a `SceneDoc` at the file's version (not migrated). Blank lines and lines starting with `#` are skipped. Raises on a missing header, entities out of order, a component line before any entity, an unterminated quote or bracket, a token without `=`, or a value that does not parse.


### def `write_binary`

```mojo
def write_binary(doc: SceneDoc) -> List[UInt8]
```

Serialises `doc` in the binary format: `MAGIC`, then little-endian u32 version and entity count; per entity a component count, per component its name and field count, per field its name, a u8 kind and the value. Strings are a u32 byte length plus bytes; integers, bools and entities are 64-bit (low word first); float lists carry a u32 count.


### def `read_binary`

```mojo
def read_binary(var data: List[UInt8]) raises -> SceneDoc
```

Parses the binary format written by `write_binary` into a `SceneDoc` at the file's version (not migrated). Raises on a bad magic, truncated data, an unknown field kind, or implausible counts (over 10 million entities, 10 000 components per entity or fields per component, or 1 million floats per field). Trailing bytes are ignored.



## `cinder.scene.gltf_spawn`

Source: `cinder/scene/gltf_spawn.mojo`

Spawning a glTF asset into the ECS: one entity per node (transform hierarchy) and one child entity per mesh primitive (`MeshRenderer`), plus static collision triangles for level geometry.

### struct `SpawnedGltf`

```mojo
struct SpawnedGltf(Movable)
```

Result of `spawn_gltf`: the root entity, the entity of each glTF node and of each mesh primitive, and the renderer ids of the upload. The entities stay in the world after this value is dropped.

Fields:

- `root`: Entity — Carries `root_transform`; every glTF root node is its child.
- `node_entities`: List[Entity] — Entity per glTF node (nodes outside the scene graph get the null entity).
- `primitive_entities`: List[Entity] — Every primitive entity, in node then primitive order.
- `uploaded`: UploadedGltf

- **`__init__`** `__init__(out self, root: Entity, var uploaded: UploadedGltf)`
- **`entity_of_node`** `entity_of_node(self, node: Int) -> Entity`  
  Entity spawned for glTF node `node`; the null entity for an out-of-range index or a node outside the scene graph.

### def `spawn_gltf`

```mojo
def spawn_gltf( mut renderer: Renderer, mut res: Resources, asset: GltfAsset, mut world: World, root_transform: Mat4 = Mat4.identity(), lods: Bool = True, cast_shadows: Bool = True, skinned_meshes: Bool = True, ) raises -> SpawnedGltf
```

Uploads the asset and spawns its node hierarchy. Skinned mesh nodes get a `MeshRenderer` too (the caller attaches an `AnimatorRef` so the animation system publishes their palette) unless `skinned_meshes` is False. Every node is spawned at most once, even when the file reaches it twice (a root that is also a child, a node shared by two parents, or a cycle); a node whose glTF parent has not been spawned yet hangs under `root`, so the `Parent` links never form a cycle.


### def `collision_triangles`

```mojo
def collision_triangles( asset: GltfAsset, root_transform: Mat4 = Mat4.identity() ) -> Tuple[List[Vec3], List[UInt32]]
```

World-space triangles of every non-skinned mesh node, for a static mesh collider.



## `cinder.scene.render_sync`

Source: `cinder/scene/render_sync.mojo`

Bridge from the ECS world to a `RenderScene`: render components and their collection. Components: `MeshRenderer` (renderer mesh and material indices, shadow flag), `LightSource`, `CameraComponent`. `build_render_scene` reads `WorldTransform` (propagated beforehand) and fills the renderer's scene lists in one pass; it allocates nothing in steady state because the scene's lists are cleared and refilled in place.

### struct `MeshRenderer`

```mojo
struct MeshRenderer(Component)
```

Draws renderer mesh `mesh` with material `material` at the entity's `WorldTransform`; `cast_shadows` is passed to the render instance.

Fields:

- `mesh`: Int
- `material`: Int
- `cast_shadows`: Bool

- **`component_id`** `component_id() -> Int`

### struct `LightSource`

```mojo
struct LightSource(Component)
```

A light positioned by the entity's world transform (direction = transform's -Z).

Fields:

- `kind`: LightKind
- `color`: Vec3
- `intensity`: Float32
- `range`: Float32
- `inner_angle`: Float32
- `outer_angle`: Float32
- `cast_shadows`: Bool

- **`component_id`** `component_id() -> Int`

### struct `CameraComponent`

```mojo
struct CameraComponent(Component)
```

A camera at the entity's world transform, looking down its -Z with its +Y up. `RenderBridge.build` uses the first `active` one it finds, keeping the scene camera's current aspect ratio.

Fields:

- `fov_y`: Float32
- `near`: Float32
- `active`: Bool

- **`component_id`** `component_id() -> Int`

### struct `JointPalette`

```mojo
struct JointPalette(Component)
```

Skinning matrices and morph weights for an entity's mesh, written by the animation system each frame and consumed by `build_render_scene`.

Fields:

- `joints`: List[Mat4]
- `morph_weights`: List[Float32]

- **`component_id`** `component_id() -> Int`

### struct `RenderBridge`

```mojo
struct RenderBridge(Movable)
```

ECS -> RenderScene with persistent query specs and chunk scratch (no steady-state allocations). `build_render_scene` is a convenience wrapper around a temporary instance.

Fields:


- **`__init__`** `__init__(out self)`
- **`build`** `build(mut self, world: World, mut scene: RenderScene) raises -> Int`  
  Fills `scene.instances`, `scene.joints` and `scene.lights` from the world and sets the camera from the first active `CameraComponent`. Returns the number of instances.

### def `build_render_scene`

```mojo
def build_render_scene(world: World, mut scene: RenderScene) raises -> Int
```

One-off bridge build (tests); the App keeps a `RenderBridge`.
