> Cinder 0.1.0 documentation: cinder.core API
> Canonical page: https://cinder.ash10.dev/docs/api/core/ · Source: `docs/api/core.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.core`


## `cinder.core`

Source: `cinder/core/__init__.mojo`

Core: math, logging, assertions, allocators, containers, handles, time, file system. No I/O to GPU, audio or window systems lives here.


## `cinder.core.arena`

Source: `cinder/core/arena.mojo`

LinearArena: a bump allocator for per-frame scratch memory. One block is allocated up front; `alloc_bytes` hands out aligned sub-ranges in O(1) and `reset` returns everything at once. There is no per-allocation free. After warm-up the engine's frame loop allocates only from arenas and pools, which is what makes the zero-allocation steady-state test possible.

### struct `LinearArena`

```mojo
struct LinearArena(Movable)
```

Bump allocator over one fixed block. Every address returned is aligned as requested.

Fields:


- **`__init__`** `__init__(out self, capacity: Int) raises`  
  Allocates `capacity` bytes once. Raises on a non-positive capacity.
- **`capacity`** `capacity(self) -> Int`  
  Usable bytes requested at construction; the 64-byte alignment slack is not counted.
- **`used`** `used(self) -> Int`  
  Bytes handed out since the last reset, including alignment padding.
- **`high_water`** `high_water(self) -> Int`  
  Largest `used()` ever observed; the number to size the arena by.
- **`alloc_bytes`** `alloc_bytes( mut self, size: Int, alignment: Int = 16 ) raises -> Pointer[UInt8, MutUntrackedOrigin]`  
  Returns `size` bytes aligned to `alignment` (a power of two <= 64). Raises when exhausted.
- **`alloc`** `alloc[ T: AnyType ](mut self, count: Int = 1) raises -> Pointer[T, MutUntrackedOrigin]`  
  Uninitialised storage for `count` values of `T`, aligned for `T`. The caller initialises it.
- **`reset`** `reset(mut self)`  
  Makes the whole block available again. Previously returned pointers become dangling.


## `cinder.core.assertions`

Source: `cinder/core/assertions.mojo`

Engine assertions: broken invariants abort with an explicit message. `cinder_assert` is compiled in unless the build defines `CINDER_ASSERTS=0` (`mojo build -D CINDER_ASSERTS=0`), which release builds of the shipped game set and nothing else does. `cinder_check` is never compiled out and is used where the cost is negligible and the invariant guards memory safety (handle validation, FFI preconditions). Both abort rather than raise: an invariant violation is a bug, not a recoverable condition (section 4 of the brief).

- `comptime ASSERTS_ENABLED: Bool = get_defined_bool["CINDER_ASSERTS", True]()` — Whether `cinder_assert` is compiled in. Read from -D CINDER_ASSERTS at build time.
### def `cinder_assert`

```mojo
def cinder_assert(condition: Bool, message: StaticString)
```

Aborts with `message` when `condition` is false, unless asserts are compiled out.


### def `cinder_check`

```mojo
def cinder_check(condition: Bool, message: StaticString)
```

Aborts with `message` when `condition` is false, in every build.



## `cinder.core.base64`

Source: `cinder/core/base64.mojo`

Base64 decoding (RFC 4648, with or without padding) for data URIs in glTF files.

### def `decode_base64`

```mojo
def decode_base64(text: StringSpan) raises -> List[UInt8]
```

Decodes base64 text; whitespace is skipped and '=' padding is optional.



## `cinder.core.boxed`

Source: `cinder/core/boxed.mojo`

Boxed: a value at a stable heap address. Subsystems that hand out `Pointer`s to themselves (Gfx, Resources, Renderer, JobSystem, ...) must never move once those pointers exist. `Boxed[T]` owns one `T` on the heap so the owner can move freely while the pointee stays put.

### struct `Boxed`

```mojo
struct Boxed[T: Deinitable & Movable](Movable)
```

Owns one `T` on the heap so pointers to it stay valid while the box itself moves. The value is moved in at construction and destroyed, then freed, when the box is destroyed. `ptr()` and `[]` give untracked mutable access; neither may outlive the box.

Fields:


- **`__init__`** `__init__(out self, var value: Self.T)`
- **`ptr`** `ptr(self) -> Pointer[Self.T, MutUntrackedOrigin]`  
  A raw pointer valid for the box's lifetime.
- **`__getitem__`** `__getitem__(ref self) -> ref[MutUntrackedOrigin] Self.T`


## `cinder.core.bytes`

Source: `cinder/core/bytes.mojo`

Little-endian byte packing for GPU uploads (std140/std430-style layouts are the caller's job).

### struct `ByteWriter`

```mojo
struct ByteWriter(Movable, Sized)
```

Appends plain values to a byte buffer. `pad_to` aligns the length for struct rules.

Fields:

- `bytes`: List[UInt8]

- **`__init__`** `__init__(out self, capacity: Int = 256)`
- **`__len__`** `__len__(self) -> Int`
- **`clear`** `clear(mut self)`  
  Empties the buffer, keeping its capacity (same as `reset`).
- **`u8`** `u8(mut self, v: UInt8)`  
  Appends one byte.
- **`u32`** `u32(mut self, v: UInt32)`  
  Appends `v` as four bytes, little-endian.
- **`i32`** `i32(mut self, v: Int32)`  
  Appends `v` as four bytes, little-endian two's complement.
- **`f32`** `f32(mut self, v: Float32)`  
  Appends the IEEE-754 bits of `v` as four bytes, little-endian.
- **`vec2`** `vec2(mut self, v: Vec2)`  
  Appends x then y as two `f32` (8 bytes, no padding).
- **`vec3`** `vec3(mut self, v: Vec3)`  
  Three floats, no padding (callers add `f32` or `pad_to(16)` for vec4 alignment).
- **`vec3_padded`** `vec3_padded(mut self, v: Vec3, w: Float32 = 0.0)`  
  Appends x, y, z, then `w` (default 0) as four `f32`: a vec3 in a 16-byte vec4 slot.
- **`vec4`** `vec4(mut self, v: Vec4)`  
  Appends x, y, z, w as four `f32` (16 bytes).
- **`mat4`** `mat4(mut self, m: Mat4)`  
  Column-major, matching WGSL `mat4x4<f32>`.
- **`pad_to`** `pad_to(mut self, alignment: Int)`  
  Zero-fills to the next multiple of `alignment`; no-op when already aligned. `alignment` must be positive: 0 or a negative value is a bug and `cinder_assert` aborts. With assertions compiled out it leaves the bytes unchanged.
- **`span`** `span(self) -> Span[UInt8, origin_of(self.bytes)]`  
  A view of the bytes written so far, borrowed from the writer; invalid after it changes.
- **`reset`** `reset(mut self)`  
  Empties the writer, keeping its capacity (frame scratch buffers).
- **`take`** `take(deinit self) -> List[UInt8]`  
  Consumes the writer and returns its bytes.


## `cinder.core.cstring`

Source: `cinder/core/cstring.mojo`

C strings to Mojo `String`s, decoding the bytes as UTF-8. C APIs (SDL, the shim, stb) hand back NUL-terminated UTF-8. Converting byte by byte with `chr` treats every byte as a code point and double-encodes anything outside ASCII, so every conversion goes through `string_from_c` instead.

### def `string_from_c`

```mojo
def string_from_c( p: Optional[Pointer[c_char, MutUntrackedOrigin]], max_len: Int = -1 ) -> String
```

The UTF-8 text at `p` up to its NUL terminator, or up to `max_len` bytes when `max_len` is not negative (fixed-size C buffers). A null pointer gives the empty string. Invalid UTF-8 sequences become U+FFFD instead of failing: C text is diagnostic data here and must never make the caller raise. The caller guarantees `p` points at a terminated string or at least `max_len` readable bytes.



## `cinder.core.fs`

Source: `cinder/core/fs.mojo`

Filesystem helpers with typed errors. Thin on purpose: the asset database layers on top.

### struct `FileError`

```mojo
struct FileError(Copyable, Movable, Writable)
```

A failed file operation, with the path and the underlying message.

Fields:

- `path`: String
- `message`: String

- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### def `file_exists`

```mojo
def file_exists(path: String) -> Bool
```

True when something exists at `path` (a file or a directory). Never raises.


### def `read_bytes`

```mojo
def read_bytes(path: String) raises FileError -> List[UInt8]
```

Reads a whole file into memory.


### def `read_text`

```mojo
def read_text(path: String) raises FileError -> String
```

Reads a whole UTF-8 text file.


### def `write_bytes`

```mojo
def write_bytes(path: String, data: Span[UInt8, _]) raises FileError
```

Writes (truncating) `data` to `path`.


### def `write_text`

```mojo
def write_text(path: String, text: String) raises FileError
```

Writes (truncating) `text` to `path`. Raises `FileError` carrying the path on failure.



## `cinder.core.handles`

Source: `cinder/core/handles.mojo`

Generational handles and the slot pool that issues them. A `Handle[Tag]` names a slot in a `Pool[T, Tag]`. The generation counter makes a stale handle (one whose slot was freed and reused) fail validation instead of aliasing a new object. `Tag` is a phantom type so that, for example, a texture handle cannot be passed where a buffer handle is expected.

### struct `Handle`

```mojo
struct Handle[Tag: AnyType]( Boolable, Equatable, Hashable, ImplicitlyCopyable, RegisterPassable, Writable, )
```

A 64-bit generational handle: 32-bit slot index, 32-bit generation. Zero is the null handle.

Fields:

- `index`: UInt32
- `generation`: UInt32

- **`null`** `null() -> Handle[Self.Tag]`  
  The null handle (index 0, generation 0); never valid in any `Pool`.
- **`is_null`** `is_null(self) -> Bool`  
  True when the generation is 0, whatever the index; pools never issue generation 0.
- **`packed`** `packed(self) -> UInt64`  
  The handle as one integer, for serialization and GPU-side ids.
- **`from_packed`** `from_packed(v: UInt64) -> Handle[Self.Tag]`  
  Inverse of `packed()`: low 32 bits are the index, high 32 bits the generation.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### struct `Pool`

```mojo
struct Pool[T: Copyable & Deinitable, Tag: AnyType](Movable)
```

A slot map: O(1) insert, remove and lookup by `Handle[Self.Tag]`, with generation checking. Slots are never reallocated once created, so a `Pool` holds at most `capacity` live values; `insert` raises when full, which keeps steady-state frames free of allocation (section 4 of the brief). Freed slots are recycled through an intrusive free list. Generations start at 1 and skip 0 so the zero handle is never valid.

Fields:


- **`__init__`** `__init__(out self, capacity: Int) raises`  
  Creates a pool with room for `capacity` values (1 <= capacity < 2^32).
- **`capacity`** `capacity(self) -> Int`  
  Number of slots, fixed at construction; the pool never grows past it.
- **`live`** `live(self) -> Int`  
  Number of values currently stored.
- **`insert`** `insert(mut self, var value: Self.T) raises -> Handle[Self.Tag]`  
  Stores `value` and returns its handle. Raises when the pool is full.
- **`is_valid`** `is_valid(self, h: Handle[Self.Tag]) -> Bool`  
  True when `h` names a live value (right slot, current generation).
- **`get`** `get(self, h: Handle[Self.Tag]) raises -> Self.T`  
  A copy of the value behind `h`; raises on a null, stale or foreign handle. Pooled values are small records (handles, descriptors), so copying is the intended access pattern; `update` writes a value back.
- **`update`** `update(mut self, h: Handle[Self.Tag], var value: Self.T) raises`  
  Replaces the value behind `h`; raises on an invalid handle.
- **`remove`** `remove(mut self, h: Handle[Self.Tag]) raises -> Self.T`  
  Removes and returns the value behind `h`, retiring the handle's generation.
- **`handles`** `handles(self) -> List[Handle[Self.Tag]]`  
  Handles of every live value, in slot order (allocates; for tools and tests).


## `cinder.core.hash`

Source: `cinder/core/hash.mojo`

Content hashing: 64-bit FNV-1a over bytes and strings (stable across runs and platforms).

- `comptime FNV_OFFSET: UInt64 = 14695981039346656037` — FNV-1a 64-bit offset basis; also the hash of empty input.
- `comptime FNV_PRIME: UInt64 = 1099511628211` — FNV-1a 64-bit prime.
### def `fnv1a64`

```mojo
def fnv1a64(data: Span[UInt8, _]) -> UInt64
```

64-bit FNV-1a of `data`. Stable across runs and platforms, which is why the asset cooker uses it as a content hash. Not cryptographic: do not use it where collisions could be engineered.


### def `fnv1a64_string`

```mojo
def fnv1a64_string(text: String) -> UInt64
```

`fnv1a64` of the string's UTF-8 bytes, without a terminator.


### def `hex64`

```mojo
def hex64(v: UInt64) -> String
```

Sixteen lowercase hex digits.



## `cinder.core.image`

Source: `cinder/core/image.mojo`

CPU images: RGBA8 load/decode/save through the shim (stb) and perceptual comparison (SSIM). `Image` owns its pixels in a `List[UInt8]` (tightly packed RGBA8). `ssim` is the structural similarity index on luma with an 8x8 box window; golden-image tests compare against it.

### struct `Image`

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

A tightly packed RGBA8 image.

Fields:

- `width`: Int
- `height`: Int
- `pixels`: List[UInt8]

- **`__init__`** `__init__(out self, width: Int, height: Int)`
- **`__init__`** `__init__( out self, width: Int, height: Int, var pixels: List[UInt8] ) raises`
- **`from_rows`** `from_rows( width: Int, height: Int, rows: List[UInt8], stride: Int ) raises -> Image`  
  Repacks rows of `stride` bytes (GPU readback with 256-byte alignment) into a tight image.
- **`pixel`** `pixel(self, x: Int, y: Int) -> SIMD[DType.uint8, 4]`  
  The RGBA8 value at column `x`, row `y`; (0, 0) is the first pixel in memory. `x` must be in [0, width) and `y` in [0, height). Anything else is a bug and aborts through `cinder_check` in every build (an unchecked index would read outside the pixel list, or silently wrap into the next row).
- **`set_pixel`** `set_pixel(mut self, x: Int, y: Int, value: SIMD[DType.uint8, 4])`  
  Writes RGBA8 `value` at column `x`, row `y`; addressed and range-checked like `pixel`.
- **`swap_rb`** `swap_rb(mut self)`  
  BGRA <-> RGBA in place (swapchain readbacks are BGRA).
- **`luma`** `luma(self, x: Int, y: Int) -> Float64`  
  Rec. 709 luma of the pixel at (`x`, `y`) in [0, 255], from the stored (gamma-encoded) values; alpha is ignored. `compare` computes SSIM on this value.

### def `load_image`

```mojo
def load_image(lib: NativePtr, path: String) raises -> Image
```

Loads the image file at `path` through the shim's stb_image and returns it as RGBA8. Any channel count in the file is expanded to four. `lib` must point at the loaded native shim. Raises with stb's failure reason when the file cannot be read or decoded. The stb buffer is copied into the `Image` and freed before returning.


### def `decode_image`

```mojo
def decode_image(lib: NativePtr, bytes: Span[UInt8, _]) raises -> Image
```

Decodes an encoded image held in memory (stb_image) to RGBA8. Raises with stb's failure reason on undecodable data; the shim also rejects inputs larger than 2^31 - 1 bytes. The stb buffer is copied into the `Image` and freed before returning.


### def `save_png`

```mojo
def save_png(lib: NativePtr, path: String, img: Image) raises
```

Writes `img` to `path` as an RGBA8 PNG through the shim's stb_image_write. Raises when the file cannot be written; the shim also refuses an image with zero width or height.


### struct `ImageDiff`

```mojo
struct ImageDiff(ImplicitlyCopyable, Movable, Writable)
```

Result of `compare`: SSIM on luma, plus the raw pixel statistics tests may also bound.

Fields:

- `ssim`: Float64
- `max_channel_delta`: Int
- `differing_pixels`: Int — Pixels where any channel differs by more than `tolerance` passed to `compare`.

- **`write_to`** `write_to(self, mut w: Some[Writer])`

### def `compare`

```mojo
def compare( a: Image, b: Image, tolerance: Int = 2, window: Int = 8 ) raises -> ImageDiff
```

SSIM (luma, non-overlapping `window` boxes, K1 = 0.01, K2 = 0.03, L = 255) and pixel deltas.



## `cinder.core.jobs`

Source: `cinder/core/jobs.mojo`

The job system: a work-stealing thread pool with dependency counters. Design (ADR-0008, docs/THREADING.md): - One worker per logical core minus one (the main thread is the remaining core), each with a fixed-capacity Chase-Lev deque of job indices. A worker pushes and pops at the bottom of its own deque; idle workers steal from the top of others'. - Jobs are `JobFn` thin function pointers plus an untyped data pointer; the caller owns the data for the job's lifetime. Each job decrements a `Counter` when it finishes; `wait` on a counter helps run jobs until it reaches zero, so waiting never idles a core. - OS threads and semaphores come from the native shim (`cn_thread_*`, `cn_semaphore_*`); worker entry points are Mojo `abi("C")` functions (verified by spike 4). Everything else is Mojo with `Atomic` operations. - No allocation after construction: the job table, deques and counters are fixed-capacity. `submit` returns False when the system is saturated; callers then run the job inline.

- `comptime JobFn = def(Pointer[NoneType, MutUntrackedOrigin]) thin -> None` — Signature of a job: receives the untyped data pointer it was submitted with.
- `comptime INVALID_JOB: Int32 = -1` — Job index meaning "no job": `Deque.pop` and `Deque.steal` return it when nothing was taken.
- `comptime MAX_WORKERS = 64` — Largest `worker_count` that `JobSystem` accepts.
### struct `Counter`

```mojo
struct Counter(Movable)
```

Completion counter: incremented per submitted job, decremented when each finishes.

Fields:

- `remaining`: Atomic[Int32]

- **`__init__`** `__init__(out self)`
- **`load`** `load(self) -> Int32`  
  Jobs still outstanding on this counter (acquire load); 0 when all have finished.

### struct `Job`

```mojo
struct Job(ImplicitlyCopyable, Movable)
```

One unit of work in the job table.

Fields:

- `run`: JobFn
- `data`: Pointer[NoneType, MutUntrackedOrigin]
- `counter`: Optional[Pointer[Counter, MutUntrackedOrigin]]

- **`__init__`** `__init__( out self, run: JobFn, data: Pointer[NoneType, MutUntrackedOrigin], counter: Optional[Pointer[Counter, MutUntrackedOrigin]], )`

### struct `Deque`

```mojo
struct Deque(Movable)
```

Chase-Lev work-stealing deque of job indices with a fixed power-of-two capacity. The owner thread calls `push` and `pop`; any thread calls `steal`. Indices wrap through the mask; `push` fails when the deque is full instead of growing.

Fields:


- **`__init__`** `__init__(out self, capacity: Int)`
- **`push`** `push(mut self, job: Int32) -> Bool`  
  Owner only. Returns False when full.
- **`pop`** `pop(mut self) -> Int32`  
  Owner only. Returns INVALID_JOB when empty.
- **`steal`** `steal(mut self) -> Int32`  
  Any thread. Returns INVALID_JOB when empty or when it lost a race (caller retries elsewhere).

### struct `Shared`

```mojo
struct Shared(Movable)
```

State shared by all workers; lives at a stable heap address for the system's lifetime.

Fields:

- `jobs`: Allocation[Job]
- `job_capacity`: Int
- `next_job`: Atomic[Int64]
- `deques`: List[Deque]
- `worker_count`: Int
- `stop`: Atomic[Int32]
- `idle`: Atomic[Int32]
- `semaphore`: Optional[Pointer[CnSemaphore, MutUntrackedOrigin]]
- `executed`: Atomic[Int64]
- `lib`: Pointer[CinderNative, MutUntrackedOrigin]

- **`__init__`** `__init__( out self, worker_count: Int, job_capacity: Int, semaphore: Optional[Pointer[CnSemaphore, MutUntrackedOrigin]], lib: Pointer[CinderNative, MutUntrackedOrigin], )`

### struct `WorkerContext`

```mojo
struct WorkerContext(Movable)
```

Passed to each worker thread entry: which deque is mine and where the shared state lives.

Fields:

- `shared`: Pointer[Shared, MutUntrackedOrigin]
- `index`: Int

- **`__init__`** `__init__( out self, shared: Pointer[Shared, MutUntrackedOrigin], index: Int )`

### struct `JobSystem`

```mojo
struct JobSystem(Movable)
```

Owns the worker threads, the shared state and the job table. Deque 0 belongs to the main thread. Hold one instance for the life of the program (section 4: the job system runs everything that is not SDL or presentation). `__deinit__` stops and joins every worker.

Fields:


- **`__init__`** `__init__( out self, lib: Pointer[CinderNative, MutUntrackedOrigin], worker_count: Int, job_capacity: Int = 1 << 14, ) raises`  
  Starts `worker_count` workers (0 runs everything on the calling thread). `job_capacity` must be a power of two; it bounds the number of jobs in flight between `reset` calls.
- **`worker_count`** `worker_count(self) -> Int`  
  Number of worker threads started, not counting the main thread (which owns deque 0).
- **`executed`** `executed(self) -> Int`  
  Jobs completed since construction (diagnostics).
- **`submit`** `submit( mut self, run: JobFn, data: Pointer[NoneType, MutUntrackedOrigin], counter: Optional[Pointer[Counter, MutUntrackedOrigin]], ) -> Bool`  
  Queues a job from the main thread. Increments `counter` before the job can run. Returns False when the job table or the main deque is full; the caller must then run it inline.
- **`wait`** `wait(mut self, counter: Pointer[Counter, MutUntrackedOrigin])`  
  Runs jobs on the calling thread until `counter` reaches zero.
- **`reset`** `reset(mut self)`  
  Recycles the job table. Only valid when no job is queued or running (e.g. at frame start).


## `cinder.core.log`

Source: `cinder/core/log.mojo`

Structured, levelled logging. A `Logger` owns a minimum level and a sink. Records carry a level, a category and a message plus optional key=value fields, and are formatted as one line: `[LEVEL] category: message key=value ...`. Sinks are a trait so tests capture records in memory and the engine routes them to stderr, a file or the debug console. Logging never raises; a failing sink drops the record.

### struct `LogLevel`

```mojo
struct LogLevel( Comparable, Equatable, ImplicitlyCopyable, RegisterPassable, Writable )
```

Severity, ordered. Compare with the usual operators.

Fields:

- `value`: UInt8

- **`trace`** `trace() -> LogLevel`  
  Severity 0, the lowest: a logger at this level passes everything.
- **`debug`** `debug() -> LogLevel`  
  Severity 1, between `trace` and `info`.
- **`info`** `info() -> LogLevel`  
  Severity 2; the default `Logger` minimum level.
- **`warn`** `warn() -> LogLevel`  
  Severity 3, between `info` and `error`.
- **`error`** `error() -> LogLevel`  
  Severity 4, the highest named level.
- **`name`** `name(self) -> StaticString`  
  Label printed in records: TRACE, DEBUG, INFO, WARN, or ERROR for 4 and above.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### struct `LogField`

```mojo
struct LogField(Copyable, Movable, Writable)
```

One key=value pair attached to a record.

Fields:

- `key`: String
- `value`: String

- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### struct `LogRecord`

```mojo
struct LogRecord(Copyable, Movable, Writable)
```

A formatted log entry.

Fields:

- `level`: LogLevel
- `category`: String
- `message`: String
- `fields`: List[LogField]

- **`__init__`** `__init__( out self, level: LogLevel, category: String, message: String, var fields: List[LogField], )`
- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### trait `LogSink`

```mojo
trait LogSink
```

Receives formatted records. Implementations must not raise.

- **`emit`** `emit(mut self, record: LogRecord)`  
  Handles one record that passed the filter. Must not raise: drop the record instead.

### struct `PrintSink`

```mojo
struct PrintSink(LogSink, Movable)
```

Writes each record as one line to standard output.

Fields:

- `emitted`: Int — Number of records written so far.

- **`__init__`** `__init__(out self)`
- **`emit`** `emit(mut self, record: LogRecord)`  
  Prints the record as one line to standard output and increments `emitted`.

### struct `MemorySink`

```mojo
struct MemorySink(LogSink, Movable)
```

Keeps every record; used by tests and the in-engine console.

Fields:

- `records`: List[LogRecord]

- **`__init__`** `__init__(out self)`
- **`emit`** `emit(mut self, record: LogRecord)`  
  Appends a copy of the record to `records`, which grows without bound.

### struct `Logger`

```mojo
struct Logger[S: LogSink & Movable & Deinitable](Movable)
```

Filters by minimum level and forwards to its sink.

Fields:

- `min_level`: LogLevel
- `sink`: Self.S

- **`__init__`** `__init__( out self, var sink: Self.S, min_level: LogLevel = LogLevel.info() )`
- **`enabled`** `enabled(self, level: LogLevel) -> Bool`  
  True when `level` >= `min_level`; check it before building costly messages.
- **`log`** `log( mut self, level: LogLevel, category: String, message: String, var fields: List[LogField], )`  
  Emits a record when `level` passes the filter. Field lists are consumed.
- **`log`** `log(mut self, level: LogLevel, category: String, message: String)`  
  Emits a record without fields when `level` passes the filter.
- **`trace`** `trace(mut self, category: String, message: String)`  
  Logs `message` under `category` at TRACE, subject to `min_level`.
- **`debug`** `debug(mut self, category: String, message: String)`  
  Logs `message` under `category` at DEBUG, subject to `min_level`.
- **`info`** `info(mut self, category: String, message: String)`  
  Logs `message` under `category` at INFO, subject to `min_level`.
- **`warn`** `warn(mut self, category: String, message: String)`  
  Logs `message` under `category` at WARN, subject to `min_level`.
- **`error`** `error(mut self, category: String, message: String)`  
  Logs `message` under `category` at ERROR, subject to `min_level`.


## `cinder.core.math`

Source: `cinder/core/math/__init__.mojo`

SIMD math for the engine. Conventions (docs/ARCHITECTURE.md, fixed): right-handed, Y up, -Z forward, metres, column-major matrices, reverse-Z depth with an infinite far plane. All types are plain 32-bit float values backed by `SIMD[DType.float32, 4]`; a `Vec3` keeps its fourth lane at zero.


## `cinder.core.math.mat4`

Source: `cinder/core/math/mat4.mojo`

Mat4: a column-major 4x4 float matrix, as WGSL and glTF store it.

### struct `Mat4`

```mojo
struct Mat4(Equatable, ImplicitlyCopyable, RegisterPassable, Writable)
```

Column-major 4x4 matrix. `c0..c3` are the columns; element (row r, column c) is `cC[r]`.

Fields:

- `c0`: F4
- `c1`: F4
- `c2`: F4
- `c3`: F4

- **`identity`** `identity() -> Mat4`  
  The identity matrix.
- **`zero`** `zero() -> Mat4`  
  All sixteen elements zero; also what `inverse` returns for a singular matrix.
- **`from_rows`** `from_rows(r0: Vec4, r1: Vec4, r2: Vec4, r3: Vec4) -> Mat4`  
  Builds a matrix from its rows (handy for literals written the way maths books print them).
- **`translation`** `translation(t: Vec3) -> Mat4`  
  Translation by `t` (metres): the identity with `t` in the last column.
- **`scaling`** `scaling(s: Vec3) -> Mat4`  
  Scale by `s.x()`, `s.y()`, `s.z()` along the x, y and z axes.
- **`rotation`** `rotation(q: Quat) -> Mat4`  
  Rotation matrix of a unit quaternion.
- **`trs`** `trs(t: Vec3, r: Quat, s: Vec3) -> Mat4`  
  Translation * Rotation * Scale, the glTF node transform order.
- **`look_at`** `look_at(eye: Vec3, target: Vec3, up: Vec3) -> Mat4`  
  Right-handed view matrix: the camera looks down -Z, with +Y up and +X right.
- **`perspective_reverse_z_infinite`** `perspective_reverse_z_infinite( fov_y: Float32, aspect: Float32, near: Float32 ) -> Mat4`  
  Right-handed perspective projection, reverse-Z (near maps to 1) with an infinite far plane, clip depth in [0, 1] as wgpu expects.
- **`orthographic_reverse_z`** `orthographic_reverse_z( left: Float32, right: Float32, bottom: Float32, top: Float32, near: Float32, far: Float32, ) -> Mat4`  
  Right-handed orthographic projection with reverse-Z (near maps to 1, far to 0).
- **`column`** `column(self, c: Int) -> Vec4`  
  Column `c` (0 to 3) as a Vec4. Any other index is a bug: `cinder_assert` aborts. With assertions compiled out (`-D CINDER_ASSERTS=0`) the index wraps to `c & 3`.
- **`__getitem__`** `__getitem__(self, row: Int, col: Int) -> Float32`  
  Element (`row`, `col`); both indices are checked like `row` and `column`.
- **`row`** `row(self, r: Int) -> Vec4`  
  Row `r` (0 to 3), gathered from the four columns. Any other index is a bug: `cinder_assert` aborts. With assertions compiled out the index wraps to `r & 3`.
- **`to_quat`** `to_quat(self) -> Quat`  
  Rotation of an orthonormal (possibly scaled) matrix's upper 3x3 (Shepperd's method, choosing the largest diagonal term for stability). Scale is removed first.
- **`transform_point`** `transform_point(self, p: Vec3) -> Vec3`  
  Applies the affine part to a point (w = 1), ignoring any projective row.
- **`transform_vector`** `transform_vector(self, v: Vec3) -> Vec3`  
  Applies the linear part to a direction (w = 0).
- **`project_point`** `project_point(self, p: Vec3) -> Vec3`  
  Full projective transform with perspective divide.
- **`transpose`** `transpose(self) -> Mat4`  
  Rows and columns swapped; for a pure rotation this is also its inverse.
- **`determinant`** `determinant(self) -> Float32`  
  Determinant of the full 4x4 matrix, by expansion in 2x2 minors. Exactly 0 is what makes `inverse` return the zero matrix; a negative value means the matrix mirrors space (`Transform.from_mat4` relies on this).
- **`inverse`** `inverse(self) -> Mat4`  
  General inverse by cofactors. A singular matrix returns the zero matrix; callers that need to detect that check `determinant()` first.
- **`approx_equal`** `approx_equal(self, o: Mat4, eps: Float32 = EPSILON) -> Bool`  
  True when every element matches within `eps` under `approx_eq`.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`


## `cinder.core.math.quat`

Source: `cinder/core/math/quat.mojo`

Quat: a unit quaternion (x, y, z, w) representing a rotation.

### struct `Quat`

```mojo
struct Quat(Equatable, ImplicitlyCopyable, RegisterPassable, Writable)
```

Rotation quaternion. Lanes are (x, y, z, w); `w` is the scalar part.

Fields:

- `data`: F4

- **`__init__`** `__init__(out self, x: Float32, y: Float32, z: Float32, w: Float32)`
- **`identity`** `identity() -> Quat`  
  The no-rotation quaternion (0, 0, 0, 1).
- **`from_axis_angle`** `from_axis_angle(axis: Vec3, angle: Float32) -> Quat`  
  Rotation of `angle` radians about the unit vector `axis` (right-handed).
- **`from_euler`** `from_euler(yaw: Float32, pitch: Float32, roll: Float32) -> Quat`  
  Yaw about +Y, then pitch about +X, then roll about +Z (applied in that order to a body).
- **`x`** `x(self) -> Float32`  
  Lane 0, the x term of the vector part.
- **`y`** `y(self) -> Float32`  
  Lane 1, the y term of the vector part.
- **`z`** `z(self) -> Float32`  
  Lane 2, the z term of the vector part.
- **`w`** `w(self) -> Float32`  
  Lane 3, the scalar part: cos(angle / 2) for a unit quaternion.
- **`vector_part`** `vector_part(self) -> Vec3`  
  (x, y, z) as a Vec3: the rotation axis times sin(angle / 2) for a unit quaternion.
- **`dot`** `dot(self, o: Quat) -> Float32`  
  4D dot product; negative when the two are on opposite hemispheres (see `slerp`).
- **`length`** `length(self) -> Float32`  
  Euclidean norm over all four lanes; 1 for a unit quaternion.
- **`normalized`** `normalized(self) -> Quat`  
  Unit quaternion; the zero quaternion becomes the identity.
- **`conjugate`** `conjugate(self) -> Quat`  
  The vector part negated; for a unit quaternion this is the inverse rotation.
- **`inverse`** `inverse(self) -> Quat`  
  Inverse for any non-zero quaternion (equals the conjugate for unit quaternions). The zero quaternion has no inverse and returns the zero quaternion, as `Mat4.inverse` returns the zero matrix for a singular matrix.
- **`rotate`** `rotate(self, v: Vec3) -> Vec3`  
  Rotates `v` by this unit quaternion: v' = v + 2w(u x v) + 2u x (u x v).
- **`nlerp`** `nlerp(self, o: Quat, t: Float32) -> Quat`  
  Normalised linear interpolation along the shorter arc.
- **`slerp`** `slerp(self, o: Quat, t: Float32) -> Quat`  
  Spherical interpolation along the shorter arc; falls back to nlerp when nearly parallel.
- **`approx_equal`** `approx_equal(self, o: Quat, eps: Float32 = EPSILON) -> Bool`  
  True when both represent the same rotation (q and -q are equal rotations).
- **`write_to`** `write_to(self, mut writer: Some[Writer])`


## `cinder.core.math.scalar`

Source: `cinder/core/math/scalar.mojo`

Scalar helpers shared by the vector, quaternion and matrix types.

- `comptime EPSILON: Float32 = 1e-6` — Default tolerance for approximate comparisons of unit-scale quantities.
### def `radians`

```mojo
def radians(deg: Float32) -> Float32
```

Converts degrees to radians.


### def `degrees`

```mojo
def degrees(rad: Float32) -> Float32
```

Converts radians to degrees.


### def `clamp`

```mojo
def clamp(x: Float32, lo: Float32, hi: Float32) -> Float32
```

Clamps `x` to [lo, hi].


### def `saturate`

```mojo
def saturate(x: Float32) -> Float32
```

Clamps `x` to [0, 1].


### def `lerp`

```mojo
def lerp(a: Float32, b: Float32, t: Float32) -> Float32
```

Linear interpolation; exact at t = 0 and t = 1.


### def `approx_eq`

```mojo
def approx_eq(a: Float32, b: Float32, eps: Float32 = EPSILON) -> Bool
```

True when |a - b| <= eps * max(1, |a|, |b|): absolute near zero, relative elsewhere.



## `cinder.core.math.transform`

Source: `cinder/core/math/transform.mojo`

Transform: translation, rotation and scale as separate terms (poses, animation keys).

### struct `Transform`

```mojo
struct Transform(Equatable, ImplicitlyCopyable, Movable, Writable)
```

A TRS transform. Composition order is scale, then rotation, then translation.

Fields:

- `translation`: Vec3
- `rotation`: Quat
- `scale`: Vec3

- **`identity`** `identity() -> Transform`  
  Zero translation, identity rotation and unit scale.
- **`from_mat4`** `from_mat4(m: Mat4) -> Transform`  
  Decomposes a matrix without shear: translation from the last column, scale from the column lengths, rotation from the normalised columns. A mirrored matrix (negative determinant) has no proper rotation; the reflection is folded into a negative x scale and the rotation is taken from the matrix with its first column negated, so `to_mat4()` rebuilds the input.
- **`to_mat4`** `to_mat4(self) -> Mat4`  
  The equivalent matrix, `Mat4.trs`: scale, then rotation, then translation.
- **`lerp`** `lerp(self, o: Transform, t: Float32) -> Transform`  
  Linear translation and scale, normalised-lerp rotation along the shorter arc.
- **`approx_equal`** `approx_equal(self, o: Transform, eps: Float32 = EPSILON) -> Bool`  
  True when translation, rotation and scale match within `eps` (q and -q are equal).
- **`write_to`** `write_to(self, mut writer: Some[Writer])`


## `cinder.core.math.vec`

Source: `cinder/core/math/vec.mojo`

Vec2, Vec3 and Vec4: 32-bit float vectors backed by SIMD registers.

- `comptime F2 = SIMD[DType.float32, 2]` — Storage of `Vec2`: two float32 lanes.
- `comptime F4 = SIMD[DType.float32, 4]` — Storage of `Vec3`, `Vec4`, `Quat` and each `Mat4` column: four float32 lanes.
### struct `Vec2`

```mojo
struct Vec2(Equatable, ImplicitlyCopyable, RegisterPassable, Writable)
```

A 2D vector.

Fields:

- `data`: F2

- **`__init__`** `__init__(out self, x: Float32, y: Float32)`
- **`zero`** `zero() -> Vec2`  
  (0, 0).
- **`x`** `x(self) -> Float32`  
  Lane 0.
- **`y`** `y(self) -> Float32`  
  Lane 1.
- **`dot`** `dot(self, o: Vec2) -> Float32`  
  Sum of the component-wise products.
- **`length_squared`** `length_squared(self) -> Float32`  
  Squared Euclidean length; avoids the square root when only comparing lengths.
- **`length`** `length(self) -> Float32`  
  Euclidean length.
- **`approx_equal`** `approx_equal(self, o: Vec2, eps: Float32 = EPSILON) -> Bool`  
  True when x and y each match within `eps` under `approx_eq`.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### struct `Vec3`

```mojo
struct Vec3(Equatable, ImplicitlyCopyable, RegisterPassable, Writable)
```

A 3D vector. The fourth SIMD lane is always zero so 4-wide operations stay exact.

Fields:

- `data`: F4

- **`__init__`** `__init__(out self, x: Float32, y: Float32, z: Float32)`
- **`zero`** `zero() -> Vec3`  
  (0, 0, 0).
- **`one`** `one() -> Vec3`  
  (1, 1, 1), the unit scale.
- **`unit_x`** `unit_x() -> Vec3`  
  +X, the engine's right axis.
- **`unit_y`** `unit_y() -> Vec3`  
  +Y, the engine's up axis (right-handed, Y-up).
- **`unit_z`** `unit_z() -> Vec3`  
  +Z, pointing backwards: the engine's forward is -Z.
- **`forward`** `forward() -> Vec3`  
  The engine's forward direction, -Z.
- **`splat`** `splat(v: Float32) -> Vec3`  
  All three components set to `v` (the fourth lane stays zero).
- **`x`** `x(self) -> Float32`  
  Lane 0.
- **`y`** `y(self) -> Float32`  
  Lane 1.
- **`z`** `z(self) -> Float32`  
  Lane 2.
- **`__getitem__`** `__getitem__(self, i: Int) -> Float32`
- **`with_x`** `with_x(self, v: Float32) -> Vec3`  
  A copy with x replaced by `v`.
- **`with_y`** `with_y(self, v: Float32) -> Vec3`  
  A copy with y replaced by `v`.
- **`with_z`** `with_z(self, v: Float32) -> Vec3`  
  A copy with z replaced by `v`.
- **`dot`** `dot(self, o: Vec3) -> Float32`  
  Sum of the component-wise products of x, y and z.
- **`cross`** `cross(self, o: Vec3) -> Vec3`  
  Right-handed cross product `self x o` (unit_x cross unit_y is unit_z).
- **`length_squared`** `length_squared(self) -> Float32`  
  Squared Euclidean length; avoids the square root when only comparing lengths.
- **`length`** `length(self) -> Float32`  
  Euclidean length.
- **`distance`** `distance(self, o: Vec3) -> Float32`  
  Euclidean distance to `o`.
- **`normalized`** `normalized(self) -> Vec3`  
  Unit vector in the same direction; zero stays zero.
- **`lerp`** `lerp(self, o: Vec3, t: Float32) -> Vec3`  
  `self + (o - self) * t` per component; `t` is unclamped, so it extrapolates.
- **`min`** `min(self, o: Vec3) -> Vec3`  
  Component-wise minimum.
- **`max`** `max(self, o: Vec3) -> Vec3`  
  Component-wise maximum.
- **`abs`** `abs(self) -> Vec3`  
  Component-wise absolute value.
- **`max_component`** `max_component(self) -> Float32`  
  Largest of x, y and z by signed value (not by magnitude; use `abs()` first for that).
- **`approx_equal`** `approx_equal(self, o: Vec3, eps: Float32 = EPSILON) -> Bool`  
  True when x, y and z each match within `eps` under `approx_eq`.
- **`to_vec4`** `to_vec4(self, w: Float32) -> Vec4`  
  (x, y, z, `w`): pass 1 for a point and 0 for a direction.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`

### struct `Vec4`

```mojo
struct Vec4(Equatable, ImplicitlyCopyable, RegisterPassable, Writable)
```

A 4D vector, also used for homogeneous points and colours.

Fields:

- `data`: F4

- **`__init__`** `__init__(out self, x: Float32, y: Float32, z: Float32, w: Float32)`
- **`zero`** `zero() -> Vec4`  
  (0, 0, 0, 0).
- **`x`** `x(self) -> Float32`  
  Lane 0.
- **`y`** `y(self) -> Float32`  
  Lane 1.
- **`z`** `z(self) -> Float32`  
  Lane 2.
- **`w`** `w(self) -> Float32`  
  Lane 3: 1 for a point, 0 for a direction, alpha for a colour.
- **`__getitem__`** `__getitem__(self, i: Int) -> Float32`
- **`xyz`** `xyz(self) -> Vec3`  
  (x, y, z) as a Vec3, dropping w without a perspective divide.
- **`dot`** `dot(self, o: Vec4) -> Float32`  
  Sum of the component-wise products over all four lanes.
- **`length`** `length(self) -> Float32`  
  Euclidean length over all four lanes.
- **`approx_equal`** `approx_equal(self, o: Vec4, eps: Float32 = EPSILON) -> Bool`  
  True when all four components match within `eps` under `approx_eq`.
- **`write_to`** `write_to(self, mut writer: Some[Writer])`


## `cinder.core.profile`

Source: `cinder/core/profile.mojo`

Tracy profiling for the engine: zones, frame marks, plots and messages. Profiling is off until `Profiler.start()` (nothing listens on the network before that) and every call is a cheap no-op while it is off. `Zone` opens on construction and closes when it is destroyed. Mojo destroys a value right after its last use, so end the zone explicitly where it should close: var z = profiler.zone("render") ...  # the instrumented work _ = z^ String lifetimes: Tracy copies zone names, zone text and messages during the call, but keys plots by the name *pointer* and reads it for the rest of the program. `Profiler.plot` therefore interns each plot name once in a table that is never freed (one small allocation per distinct name, the first time it is plotted while profiling runs).

- `comptime COLOR_RENDER: UInt32 = 0x3F7FBF` — Zone colour (0xRRGGBB) for rendering.
- `comptime COLOR_PHYSICS: UInt32 = 0xBF7F3F` — Zone colour (0xRRGGBB) for physics and fixed steps.
- `comptime COLOR_ANIM: UInt32 = 0x7FBF3F` — Zone colour (0xRRGGBB) for animation.
- `comptime COLOR_AUDIO: UInt32 = 0xBF3F7F` — Zone colour (0xRRGGBB) for audio.
- `comptime COLOR_ASSETS: UInt32 = 0x7F7F7F` — Zone colour (0xRRGGBB) for asset loading.
### struct `Profiler`

```mojo
struct Profiler(ImplicitlyCopyable, Movable)
```

A handle to the shim's Tracy client; copies share the process-wide profiler and its plot name table.

Fields:

- `lib`: NativePtr

- **`__init__`** `__init__(out self, lib: NativePtr)`
- **`available`** `available(self) -> Bool`  
  Whether the shim was built with Tracy.
- **`start`** `start(self) -> Bool`  
  Starts the Tracy client (idempotent). Returns False when Tracy is not built in.
- **`stop`** `stop(self)`  
  Shuts the Tracy client down if it is running; a no-op otherwise or without Tracy.
- **`is_started`** `is_started(self) -> Bool`  
  Whether the Tracy client is running (`start` without `stop`).
- **`is_connected`** `is_connected(self) -> Bool`  
  Whether a Tracy viewer is connected; always False while stopped or without Tracy.
- **`zone`** `zone(self, name: String, color: UInt32 = 0) -> Zone`  
  Opens a zone named `name` that ends when the returned `Zone` is destroyed. `color` is 0xRRGGBB (0 keeps Tracy's default; see the `COLOR_*` constants). Mojo destroys a value after its last use, so end the zone explicitly with `_ = z^` where it should close. While profiling is off the zone is inert and its `id` is 0.
- **`frame_mark`** `frame_mark(self)`  
  Marks the end of a frame.
- **`plot`** `plot(self, name: String, value: Float64)`  
  Adds `value` to the Tracy plot called `name`. Does nothing while profiling is off. Tracy keeps the name pointer for the program's life, so the name is interned (copied once into a never-freed table); later calls with the same name reuse that copy and do not allocate.
- **`plot_name_count`** `plot_name_count(self) -> Int`  
  How many distinct plot names have been interned (shared by every copy).
- **`message`** `message(self, text: String)`  
  Sends `text` to Tracy's message log (info severity); a no-op while profiling is off.

### struct `Zone`

```mojo
struct Zone(Movable)
```

An open profiling zone; ends when destroyed. `id` is 0 while profiling is off.

Fields:

- `lib`: NativePtr
- `id`: UInt64

- **`__init__`** `__init__(out self, lib: NativePtr, name: String, color: UInt32)`
- **`text`** `text(self, note: String)`  
  Attaches text to the open zone.


## `cinder.core.ring`

Source: `cinder/core/ring.mojo`

RingBuffer: a fixed-capacity FIFO queue with no allocation after construction.

### struct `RingBuffer`

```mojo
struct RingBuffer[T: Copyable & Deinitable](Movable, Sized)
```

Single-threaded bounded FIFO. `push` returns False when full; `pop` returns None when empty. Capacity must be a power of two so index wrapping is a mask. Cross-thread queues live in the native shim (CnEventQueue); this type is for in-engine command and event buffering.

Fields:


- **`__init__`** `__init__(out self, capacity: Int) raises`
- **`capacity`** `capacity(self) -> Int`  
  Number of slots, a power of two fixed at construction.
- **`__len__`** `__len__(self) -> Int`
- **`is_empty`** `is_empty(self) -> Bool`  
  True when nothing is queued (`pop` would return None).
- **`is_full`** `is_full(self) -> Bool`  
  True when every slot is taken (`push` would return False).
- **`push`** `push(mut self, var value: Self.T) -> Bool`  
  Appends `value`; returns False (and drops nothing already queued) when full.
- **`pop`** `pop(mut self) -> Optional[Self.T]`  
  Removes and returns the oldest value, or None when empty.
- **`peek`** `peek(self) raises -> Self.T`  
  A copy of the oldest value; raises when empty.
- **`clear`** `clear(mut self)`  
  Pops and destroys every queued value, oldest first.


## `cinder.core.time`

Source: `cinder/core/time.mojo`

Time: a monotonic clock and the fixed-timestep accumulator that drives the simulation. The frame loop (section 4 of the brief) simulates at a fixed 60 Hz and renders at whatever rate the display allows, interpolating between the last two simulation states with `alpha`.

- `comptime NS_PER_SECOND: Int = 1_000_000_000` — Nanoseconds per second.
### struct `Clock`

```mojo
struct Clock(Movable)
```

Monotonic wall clock in nanoseconds, with per-frame deltas.

Fields:


- **`__init__`** `__init__(out self)`
- **`now_ns`** `now_ns(self) -> Int`  
  Current `perf_counter_ns()` reading. Monotonic, but its origin is arbitrary (not the clock's creation), so only differences between readings are meaningful.
- **`elapsed_seconds`** `elapsed_seconds(self) -> Float64`  
  Seconds since the clock was created.
- **`tick`** `tick(mut self) -> Float64`  
  Seconds since the previous `tick` (or creation). Never negative.

### struct `FixedStep`

```mojo
struct FixedStep(ImplicitlyCopyable, Movable)
```

Fixed-timestep accumulator. Feed real frame times with `advance`; it returns how many simulation steps of `step_seconds` to run (capped at `max_steps_per_frame` to avoid the spiral of death) and leaves `alpha()` as the interpolation factor for rendering between the previous and current simulation state.

Fields:

- `step_seconds`: Float64
- `max_steps_per_frame`: Int

- **`__init__`** `__init__(out self, hz: Float64 = 60.0, max_steps_per_frame: Int = 8)`
- **`advance`** `advance(mut self, frame_seconds: Float64) -> Int`  
  Accounts for `frame_seconds` of real time and returns the number of steps to simulate.
- **`alpha`** `alpha(self) -> Float64`  
  Fraction of a step accumulated but not yet simulated, in [0, 1).
- **`total_steps`** `total_steps(self) -> Int`  
  Sum of every step count `advance` has returned.
