Cinder: deliverable report
Living document. Every claim below was verified in this repository under the pinned toolchain on the date given; nothing is reported from memory or intention. Phases are those of the mission brief.
1. What was built (and what was not) #
| Area | State | Where |
|---|---|---|
| Toolchain pins, container, CI skeleton | Done | pixi.toml, docker/, .github/workflows/ci.yml, docs/TOOLCHAIN.md |
| Mojo 1.2.0 notes and banned legacy forms, with CI lint | Done | docs/MOJO_NOTES.md, tools/lint_legacy_mojo.sh |
| Architecture and ADRs 0001 to 0007 | Done | docs/ARCHITECTURE.md, docs/adr/ |
| Vendored dependencies (14 submodules, exact pins, licences) | Done | third_party/, THIRD_PARTY_NOTICES.md |
Native shim libcinder_native (C11): lock-free MPMC event queue, wgpu async/log callbacks, SDL surface + log capture, Jolt contact and activation listeners, miniaudio engine/sound ownership and callbacks, ImGui backends behind a C ABI |
Done for Phase 1 scope | native/ |
| Native build (CMake + Ninja): SDL3, wgpu-native via cargo, joltc + Jolt 5.6, miniaudio, cgltf, Dear ImGui + dcimgui, layout printer, CTest, ASan/UBSan configuration | Done | native/CMakeLists.txt |
| Bindings generator (libclang) producing Mojo 1.2 bindings, C layout printer and Mojo layout test from one parse | Done | tools/bindgen/cinder_bindgen.py, cinder/ffi/, native/layout/, tests/ffi/ |
| Phase 1 spikes 1 to 9 | Done on Linux | spikes/ |
| Phase 2 math library (Vec2/3/4, Quat, Mat4; RH, Y up, -Z forward, column-major, reverse-Z infinite) with reference-checked property tests | Done | cinder/core/math/, tests/core/test_math.mojo |
| Phase 2: logging, assertions, linear arena, generational pool and handles, ring buffer, clock and fixed step, fs, work-stealing job system (ADR-0008) | Done, tested (29 tests) | cinder/core/, tests/core/ |
| Phase 3 platform layer: SDL3 windows, pixel-size/resize, event translation (keyboard, mouse, wheel, window, gamepad), surface creation through the shim, SDL log capture | Done, tested on X11 and Wayland | cinder/platform/, tests/platform/ |
| Phase 3 gfx layer: device + diagnostics, pooled typed resources, shader manager with located errors and hot reload, typed pipelines/bind groups/vertex layouts, command encoding and passes, swapchain with resize/minimise/loss handling, debug draw (ADR-0009) | Done, tested (11 tests incl. pixel readback) | cinder/gfx/, tests/gfx/, docs/adr/0009-gfx-layer.md |
| Phase 4 renderer: frame graph, WGSL assembly with hot reload, forward+ clustered lighting, cascaded + atlas shadows, GPU-baked IBL, GTAO, bloom, AgX/ACES, MSAA, FXAA, instancing, meshoptimizer LODs, GPU skinning and morph targets, debug views (ADR-0010) | Done, tested on lavapipe (pixel tests + 16 golden images) | cinder/render/, tests/render/, tests/golden/, docs/RENDER_VALIDATION.md |
| glTF 2.0 import (meshes, tangents via MikkTSpace, materials with KHR_texture_transform and KHR_materials_emissive_strength, images from files/data URIs/GLB, nodes, skins, morph targets) | Done, tested | cinder/assets/gltf.mojo, tests/assets/ |
| Phase 5 ECS: archetype world with tight type-erased columns, compile-time typed queries (with/without filters), change ticks, command buffers, Parent/Local/World transform hierarchy, access-declared system schedule on the job system; benchmarked against flecs 4.2.0 through its iterator API and kept (ADR-0011) | Done, tested (5 tests + bench) | cinder/ecs/, bench/ecs_bench.mojo, docs/adr/0011-ecs-mojo-vs-flecs.md |
Phase 5 asset pipeline: cinder-cook CLI (meshes optimised + LODs, materials, animation clips, KTX2 textures via libktx: ETC1S/UASTC BasisU, sRGB and normal-map aware), incremental by FNV-1a content hash with stable logical IDs and a manifest; runtime AssetServer (async loads on the job system, refcounting, shared instances, hot reload on file change, materials resolve their textures, BC7 transcode when the adapter has BC) |
Done, tested (cook 4, textures 2, asset server 1, fuzz 3) | cinder/assets/{cook,cooked,textures,runtime}.mojo, tools/cinder_cook.mojo, tests/assets/ |
| Phase 5 scene: diffable text format (exact float round trip) and binary format, component registry with versioned migrations, ECS to RenderScene bridge (MeshRenderer, LightSource, CameraComponent) | Done, tested (5 + 1) | cinder/scene/, tests/scene/ |
| Phase 6 physics: Jolt world with bit layers, every main shape plus mesh/convex/scaled colliders, static/kinematic/dynamic bodies, sensors, continuous collision, queued contact and activation events, ray and shape casts with layer masks and body ignore, CharacterVirtual controller (steps, slopes, jumps, pushing, moving platforms), ECS sync with interpolation, deterministic replay (ADR-0012) | Done, tested (5 tests, also under ASan) | cinder/physics/, native/src/physics_shim.c, tests/physics/ |
Phase 6 animation: skeletons from glTF skins and cooked .cskel, step/linear/cubic-spline sampling, phase-synced 1D and 2D blend trees, crossfades, animation events, root motion, one-job-per-animator system feeding JointPalette to the render bridge |
Done, tested (4 tests incl. a skinned animated render) | cinder/anim/, tests/anim/ |
| Phase 6 audio: miniaudio buses, decoded and streamed sounds, one-shots, 3D attenuation and panning, ECS sources, listener following the camera entity, headless engine for tests | Done, tested (2 tests) | cinder/audio/, native/src/audio.c, tests/audio/ |
| Phase 6 input: action/axis mapping over keyboard, mouse and gamepad, dead zones, runtime rebinding by listening, exact text save/load | Done, tested (3 tests) | cinder/input/, tests/input/ |
Phase 7 runtime: App owning every subsystem (heap-boxed for stable pointers), fixed-step Game loop, windowed and --headless modes (offscreen render + readback), engine CLI options (ADR-0013) |
Done, tested (2 tests, also under ASan) | cinder/app/, cinder/core/boxed.mojo, tests/app/ |
| Phase 7 tools: ImGui debug overlay with frame-time graph and render stats, entity inspector (edits through the scene registry), asset browser, render-graph view, log console; Tracy client with manual lifetime and RAII zones | Done, tested (windowed overlay test on X11 and Wayland; profiler test) | cinder/tools/, cinder/core/profile.mojo, native/src/profile.c, tests/tools/, tests/core/test_profile.mojo |
Phase 8 sample game: third-person game built only on public engine APIs (skeletal KayKit character on CharacterController, idle/walk/run blend tree and jump state, procedural arena and Khronos Sponza with a Jolt mesh collider, 32 orbiting shadowed point lights plus a sun and IBL, 500+ dynamic crates, collectables, win and restart, 3D effects and music on buses, pause menu with settings saved to disk, scene-text save/load, autopilot), CC0 assets fetched with pinned hashes (ADR-0014) |
Done, tested (headless playthrough with save/load and restart; glTF spawn) | sample/, cinder/scene/gltf_spawn.mojo, cinder/assets/gltf.mojo (upload_gltf), tools/fetch_game_assets.py, tests/sample/, tests/scene/test_gltf_spawn.mojo |
Phase 8 performance: bench/stress (50k instances, 2,000 bodies, 256 lights) and bench/sample_bench writing JSON, tools/bench_check.py with adapter-scoped baselines and a 10% regression gate, allocation counter library and the zero-allocations-per-frame test, engine refactored to persistent scratch (ByteWriter reset, cached query specs and chunk lists, recycled frame-graph records, stack arrays for cascades and frustum corners, cached post-process and GTAO bind groups, lazy frame-graph DOT) |
Done; budgets measured only on lavapipe (section 4) | bench/, tools/bench_check.py, native/src/alloc_counter.c, tests/perf/test_zero_alloc.mojo, docs/BENCHMARKS.md |
| Phase 9 documentation: README quick start, sample and benchmark sections; tutorial built on a compiled example game; one guide per subsystem; API reference generated from docstrings with a coverage gate (every public symbol documented); benchmark methodology; changelog; doc-name checker | Done; release tag pending the defect sweep and final gate | README.md, docs/TUTORIAL.md, examples/first_game/, tests/examples/, docs/guides/, docs/api/, tools/gen_api_docs.py, tools/check_doc_names.py, docs/BENCHMARKS.md, CHANGELOG.md |
2. Pinned version matrix #
| Component | Version |
|---|---|
| Mojo | 1.2.0 (build 2016d0cd) |
| pixi | v0.63.2 |
| Rust (wgpu-native build only) | 1.93.0 |
| CMake / Ninja | >=3.28,<4 / >=1.11 (conda-forge) |
| wgpu-native | v29.0.1.1 (webgpu-headers 673658b) |
| SDL3 | release-3.4.20 |
| joltc / Jolt Physics | 2bbcf73 / v5.6.0 |
| cgltf | v1.15 |
| miniaudio | 0.11.26 |
| Dear ImGui / Dear Bindings | v1.93.0-docking / v0.27 |
| stb, KTX-Software, meshoptimizer, MikkTSpace, Tracy, flecs | 4e9b368, v4.4.2, v1.3, 3e895b4, v0.15.0, v4.2.0 (all built into the shim) |
| Linux toolchain image | Ubuntu 24.04, clang 18, Mesa lavapipe, Xvfb, Weston |
3. Test results (Linux x86-64, lavapipe, 2026-11-09) #
Three consecutive full runs of tools/run_tests.sh and spikes/window/run_window_spike.sh, identical
results each time (no flakes):
| Program | Tests | Result |
|---|---|---|
tests/ffi/test_layout_cinder_native.mojo |
892 (size, alignment, every field offset of every record incl. flecs and libktx; every enum and flag constant) | 892 pass |
spikes/ffi/test_ffi_spikes.mojo (spikes 3, 4, queue contract) |
5 | 5 pass |
spikes/wgpu_compute/test_compute.mojo (spike 2) |
1 | pass |
spikes/physics/test_physics.mojo (spike 5) |
1 | pass |
spikes/audio/test_audio.mojo (spike 6) |
1 | pass |
spikes/gltf/test_gltf.mojo (spike 7) |
2 | 2 pass |
spikes/window/test_window.mojo (spike 1) |
1 | pass on X11 (Xvfb) and on Wayland (Weston headless) |
spikes/imgui/test_imgui.mojo (spike 8) |
1 | pass on X11 and on Wayland |
tests/core/test_math.mojo |
12 (500 random cases per property) | 12 pass |
tests/core/test_core.mojo |
12 (handles, pool, arena, ring, time, fs, log, abort assertions) | 12 pass |
tests/core/test_jobs.mojo |
5 (parallel sum, 100k stolen jobs exactly once, saturation, zero workers) | 5 pass, also against the ASan+UBSan shim |
tests/gfx/test_device.mojo |
1 (device creation, validation error captured and counted) | pass |
tests/gfx/test_resources.mojo |
2 (typed handles, live counts, upload, stale handle rejection, leak accounting at shutdown) | 2 pass |
tests/gfx/test_shaders.mojo |
2 (compile error with path:line:col, hot reload keeps last good module) |
2 pass |
tests/gfx/test_render.mojo |
1 (compute pass doubles a uniform, triangle drawn offscreen, pixel readback exact) | pass |
tests/gfx/test_debug_draw.mojo |
2 (lines on expected pixels under identity and translated cameras; buffer growth bounded) | 2 pass |
tests/platform/test_platform.mojo |
2 (window create/resize/surface; injected SDL events translated) | 2 pass on X11 and on Wayland |
tests/platform/test_swapchain.mojo |
1 (configure, 3 presented frames, resize, minimise skip, restore, cleared frame read back) | pass on X11 and on Wayland |
tests/core/test_image.mojo |
3 (PNG round trip, decode from memory, SSIM behaviour) | 3 pass |
tests/render/test_render_core.mojo |
5 (camera/frustum incl. SIMD culling, primitives, MikkTSpace tangents, mips, material upload) | 5 pass |
tests/render/test_frame_graph.mojo |
2 (ordering independent of declaration order, culling, lifetimes, aliasing, pooling, resize, cycle detection) | 2 pass |
tests/render/test_shader_programs.mojo |
5 (all 8 WGSL programs compile; include errors map to file:line; cluster slicing; cascade splits and texel snapping; atlas allocation) | 5 pass |
tests/render/test_renderer.mojo |
3 (lit scene: sky, shadows proven by differencing, bloom peak, determinism; MSAA+FXAA+ACES, debug modes, LODs, wireframe, feature toggles; resize) | 3 pass |
tests/assets/test_gltf_loader.mojo |
3 (base64; skinned fixture incl. computed normals/tangents, joints, render; malformed files raise) | 3 pass |
tests/golden/test_golden_gltf.mojo |
16 Khronos models at 512x512 (SSIM >= 0.985, <= 0.5% pixels differ by > 8/255) | 16 pass against the reviewed Linux references |
tests/ecs/test_ecs.mojo |
5 (spawn/despawn/add/remove across archetypes, typed queries and filters, change ticks, command buffers with placeholders, hierarchy propagation, scheduled systems on jobs) | 5 pass |
tests/scene/test_scene_format.mojo |
5 (text and binary exact round trip, float formatting, migrations, malformed input rejected) | 5 pass |
tests/scene/test_render_sync.mojo |
1 (ECS components to RenderScene with world transforms) | pass |
tests/assets/test_cook.mojo |
4 (cooked mesh/material/clip formats round trip, glTF cook incl. KTX2 textures, incremental skip by content hash, manifest) | 4 pass |
tests/assets/test_textures.mojo |
2 (ETC1S and UASTC cook, load, full mip chain, colour within tolerance, BC7 transcode and upload validation clean; corrupt KTX2 rejected) | 2 pass |
tests/assets/test_fuzz.mojo |
3 (mutated glTF, cooked mesh and scene files never crash; every failure is a raised error) | 3 pass |
tests/assets/test_asset_server.mojo |
1 (async loads on jobs, shared refcounts, material pulls its textures and renders, release order, hot reload on file change) | pass |
tests/physics/test_physics_world.mojo |
5 (layers, sensors, kinematic platform, events; ray/shape casts, mesh and hull colliders; character controller over step, ramp, jump, wall, crate, moving platform; ECS sync with interpolation; bit-identical replay of 72 bodies) | 5 pass, also under ASan+UBSan |
tests/anim/test_animation.mojo |
4 (step/linear/cubic sampling and loops; Fox skeleton cooked round trip, 1D/2D blend weights, phase sync, crossfade; events and root motion incl. loop wraps; 16 animators on jobs bit-identical to serial, CesiumMan rendered through the ECS bridge) | 4 pass |
tests/audio/test_audio_engine.mojo |
2 (buses, left/right panning, distance attenuation, muted bus, streamed loop, one-shot end event and release, stale handles; ECS sources and camera listener) | 2 pass |
tests/input/test_input_map.mojo |
3 (edges per frame, digital and analog axes with dead zones, listening rebinds, exact save/load) | 3 pass |
tests/core/test_profile.mojo |
1 (Tracy no-ops while stopped, start/stop lifecycle, zones, plots, messages, frame marks) | pass, also under ASan |
tests/app/test_app_headless.mojo |
2 (headless game: 45 fixed steps, physics drives an entity, offscreen frame visible, no validation errors, zero live objects at shutdown; CLI option parsing) | 2 pass, also under ASan |
tests/tools/test_debug_ui.mojo |
1 (windowed app with the overlay: six frames, inspector snapshot through the registry, no validation errors) | pass on X11 and on Wayland |
tests/scene/test_gltf_spawn.mojo |
1 (KayKit character, BoxTextured and Avocado spawned into one world: an entity per glTF node, a primitive entity per mesh part, skeleton and 30 clips read, collision triangles extracted, six instances render validation clean) | pass |
tests/sample/test_sample_game.mojo |
2 (arena autopilot: crates, lights, collectables and controller spawned, wins after real travel time with zero validation errors and a visible frame, mid-game save then reload restores collected state, restart clears it, music and effects buses alive, no leaks; settings text round trip) | 2 pass |
tests/examples/test_first_game.mojo |
1 (the tutorial game headless: the ball pushes every crate off the ledge with the move-axis force, win detected, restart resets, frame read back, no validation errors, every body released) | see the final gate below |
tests/perf/test_zero_alloc.mojo |
1 (300 static instances, 100 bodies, 32 lights + sun, animated skinned character: 90 warm-up frames, then 60 frames with zero allocations from Mojo code, counted through the LD_PRELOADed interposer) | pass (0 Mojo allocations; third-party libraries still allocate) |
Native CTest event_queue (Release) |
1 | pass |
Native CTest event_queue (Debug + ASan + UBSan) |
1 | pass, no sanitizer reports |
Gate items also verified: tools/lint_legacy_mojo.sh clean; --Werror on every Mojo test build; zero
wgpu validation errors, device-lost or warning-level log events in spikes 1, 2 and 8 (the tests fail on
any such event).
macOS: none of the above has been run on macOS. No Apple Silicon machine or runner was available to this run (see Blockers). Every phase gate therefore remains half open.
4. Benchmark results #
The reference hardware (Linux RTX 3060-class, Apple M2) is not available; the only GPU here is Mesa
lavapipe (llvmpipe, LLVM 20.1.2) inside Docker on the Windows host's CPU. Every number below is from
that configuration and is a regression baseline for it (bench/baselines/*.json, checked by
tools/bench_check.py with a 10% tolerance), not a statement about the section 8 budgets. Method:
docs/BENCHMARKS.md.
Section 8 budgets #
| Budget | Measured here (lavapipe) | Status |
|---|---|---|
| Sample p99 <= 16.6 ms at 1080p high | see sample_arena below |
Unmeasured on reference hardware; far above on a CPU rasteriser |
bench/stress >= 60 fps (50k instances, 2,000 bodies, 256 lights) |
see stress below |
Unmeasured on reference hardware |
| Physics step <= 4 ms, 2,000 bodies | 0.73 ms median, 2.34 ms p99 over 120 frames (Jolt, 2 worker threads, bodies falling and settling) | Met on this CPU |
| Cold start <= 2 s | stress 0.97 s; sample arena 1.55 s (includes the KayKit character and the environment bake); sample Sponza above 2 s (source glTF textures decoded in process) | Met for arena and stress on this host; Sponza not met |
| Zero allocations per steady-state frame | tests/perf/test_zero_alloc: 0 Mojo allocations over 60 frames |
Met (asserted by the test) |
Results (pixi run bench-all, 1920x1080, high preset, headless) #
| Benchmark | Frames | Frame ms (median / p99) | Physics ms (median / p99) | Render submit ms (median / p99) | Start-up ms | Notes |
|---|---|---|---|---|---|---|
stress (50,000 instances, 2,000 bodies, 256 lights) |
120 | 721.3 / 785.0 | 0.728 / 2.343 | 715.0 / 778.5 | 972 | 24257 instances drawn after culling in 17 draw calls; 0 GPU errors |
sample_arena (high preset) |
300 | 294.6 / 356.9 | 0.007 / 0.011 | 294.2 / 356.4 | 1548 | autopilot collected 4; 0 GPU errors |
Adapter for both: llvmpipe (LLVM 20.1.2, 256 bits). Render time is almost all of frame time in both runs, and
on this adapter the GPU work is software rasterisation of 1080p on the same CPU, so these numbers
say nothing about the GPU budgets. The engine's CPU-only share of a frame was not isolated here
(a Tracy capture on reference hardware would). The stress scene's physics p99 includes the frames in which the
falling boxes first touch the floor and each other. The sample's bodies are mostly asleep, so its
physics time is microseconds.
ECS vs flecs (pixi run bench-ecs: 100,000 entities, two archetypes, 100 frames of
position += velocity * dt through a two-component query; flecs 4.2.0 through
ecs_query_iter/ecs_query_next/ecs_field_w_size), container on the Windows host:
| Run | Mojo ns / entity-iteration | flecs ns / entity-iteration | ratio |
|---|---|---|---|
| 1 | 0.758 | 0.829 | 0.91 |
| 2 | 0.746 | 0.750 | 1.00 |
| 3 | 0.728 | 0.744 | 0.98 |
Within the 10% band required to keep the Mojo ECS (ADR-0011).
5. Known limitations and Mojo workarounds #
- ASan
alloc_dealloc_mismatchdisabled intools/run_tests_asan.sh. KTX-Software at the pinned commit allocates Basis supercompression global data withnew[](lib/basis_encode.cpp,ktxTexture2_CompressBasisEx) and frees it withfree(lib/texture2.c,ktxTexture2_destruct), an alloc-dealloc mismatch inside the vendored library that only the in-process encoder path (the cooker) hits. ASan cannot scope that check with a suppression file, so that single check is off for the sanitizer runs; heap overflow, use-after-free, LeakSanitizer and UBSan stay on for every library and all Mojo code. The submodule is not patched; revisit when the pin moves.
| Item | Reproducer | Workaround |
|---|---|---|
A comptime Array[StaticString, N] literal with N in the low thousands exhausts the Mojo 1.2.0 compiler (OOM at 13 GB) |
bugs/comptime_array_literal_oom/ |
Symbol names are one newline-joined string literal split at load time. |
| Untracked-origin pointers do not keep their pointee alive; ASAP destruction frees trivially destructible stack values before C reads them | documented in docs/MOJO_NOTES.md (language semantics, not a defect) |
std.benchmark.keep(value) after the last C call; the safe layer owns descriptors in engine storage. |
OwnedDLHandle is closed at its last syntactic use |
docs/MOJO_NOTES.md |
Function pointers live only inside the struct that owns the handle. |
Optional[def(...) thin abi("C")] is 16 bytes |
docs/MOJO_NOTES.md |
Nullable C callback fields are bound as nullable opaque pointers. |
wgpu-native 29.0.1.1 does not implement wgpuInstanceWaitAny |
third_party/wgpu-native/src/unimplemented.rs |
cn_wgpu_pump + event queue polling with a deadline (ADR-0003 amendment). |
wgpu-native built from the git tag reports version 0 from wgpuGetVersion |
third_party/wgpu-native/Cargo.toml (version = "0.0.0") |
Not relied upon. |
| Jolt 5.6 builds hair compute shaders with glslangValidator when Vulkan is on | third_party/JoltPhysics/Build/CMakeLists.txt |
JPH_USE_VK=OFF (Jolt is CPU-only in Cinder, ADR-0002). |
wgpu-native 29 aborts (Rust panic) on a validation error inside wgpuQueueSubmit instead of reporting it through the error callback |
documented in docs/MOJO_NOTES.md |
Copy strides are explicit and 256-aligned; resource-creation errors still arrive through the callback and are tested. |
wgpuShaderModuleGetCompilationInfo is unimplemented in wgpu-native 29 (aborts) |
third_party/wgpu-native/src/unimplemented.rs |
Shader errors captured with a validation error scope and naga's wgsl:line:col report parsed (ADR-0009). |
mojo format has no check mode |
pixi run mojo format --help |
Format in place, then git diff --exit-code. |
Defect sweep before v0.1.0 (Phase 9) #
Writing a docstring for every public symbol meant reading every public function, and that review found real defects. Each was fixed at its root with a test that exercises it. Where the agent or operator ran the new test against the old code, it failed there. The fixes, by area:
| Area | Fixed |
|---|---|
| gfx | Encoder labels reached no descriptor; passes and encoders dropped without end/submit were never released; rejected pipelines leaked and reported the wrong diagnostic; adopt_*/create_* leaked on a full pool and accepted null; imported views could be released by Resources (also at shutdown); unchecked buffer offsets; read_buffer left a buffer mapped on error; the shutdown leak count was unreadable (now a diagnostic record); destroy_texture left its views alive (now refused); replace_*_pipeline leaked the new object on a stale handle |
| render | Frame graph did not order early readers before a resource's second writer; GpuMesh.index_count disagreed between upload paths; more than 32 morphed instances reused slot 0's weights; only the first morphed mesh's delta buffer was bound per frame (one cached object group per morph buffer now; the MorphPrimitivesTest golden reference had captured this bug and was regenerated and re-reviewed, docs/RENDER_VALIDATION.md); overdraw and LOD debug views showed the wrong data; dropped lights kept shadow tiles; batch keys collided at 4,096 materials or 8 LODs; skin weights were not validated; Renderer.replace_material/remove_material added |
| core, platform, input, app | Mirrored Transform.from_mat4 produced a wrong rotation; Tracy plot names dangled; out-of-range Mat4/Image indices, pad_to(0) and the zero quaternion's inverse are now checked or defined; NativeLibrary leaked on an ABI mismatch; UTF-8 text from C was double-encoded (shared string_from_c); negative binding codes indexed out of range; clearing a held action's bindings left it held; key and button releases were swallowed while the debug overlay had focus |
| ecs, scene, assets | Chunk access for a missing component read the wrong column (now a hard check); two types could share a component id; a failing CommandBuffer.apply double-destroyed payloads; FieldReader.bool skipped kind checks; non-ASCII names were mangled in scene and cooked formats; spawn_gltf duplicated nodes reachable twice and could recurse forever; AssetServer miscounted in-flight loads on early release, leaked renderer materials and texture references across reloads, returned records of the wrong kind and never retried failed loads; texture hot reload left materials binding a destroyed view; cooked sampler selection was order-dependent; the cook manifest kept stale entries |
| physics, anim, audio | Double or invalid destroy_body/destroy_shape corrupted leak counts; leaks at teardown were silent (now an error record and leak_report()); AnimationSystem.update behaved differently with and without jobs; a finished state could not be replayed (play(..., restart=True)); audio buses could not be destroyed (destroy_bus); blend trees accepted cycles; write_transforms did not skip invalid bodies |
| sample | No shutdown path: the game now releases its level, music, effects and both buses, and its test checks that nothing it made outlives shutdown; the jump animation could not replay |
Remaining limitations found in the same review, documented in the guides:
- Only the first directional light is used (
docs/guides/render.md). - Physics
destroy_shapecannot tell a stale copy of a shape handle from a new shape that Jolt allocated at the same address (docs/guides/physics.md). NativeLibrary's leak fix has no automated test: the allocation counter does not export a free count through the shim yet.
6. Deviations from the brief, each with its ADR #
| Deviation | ADR |
|---|---|
| Own bindings generator instead of mojo-bindgen (its 0.3.4 output does not parse under 1.2.0; it needs Python 3.14 but no nightly Mojo, so that part of the concern was unfounded) | ADR-0005 |
| wgpu-native is built by cargo driven from CMake, not by CMake itself (it is Rust) | ADR-0004 |
| Development host is a Linux container on Windows; macOS verification deferred | ADR-0007 |
| stargine not forked | ADR-0006 |
| Mojo ECS kept over flecs after the mandated benchmark (parity, within the band) | ADR-0011 |
| Jolt steps on its own 2-thread pool rather than the engine job system (joltc cannot be driven without callbacks into Mojo) | ADR-0012 |
Tracy is started on demand (--profile) rather than at process start, so tests and headless runs open no sockets |
ADR-0013 |
7. Blockers escalated #
- No macOS runner or machine. Exact problem: every gate requires both platforms; no Apple Silicon
host was reachable from this session. Tried: none possible (Docker runs Linux only). Options:
(a) enable the
macos-14job in.github/workflows/ci.ymlby pushing to GitHub, (b) a local Apple Silicon machine running the same pixi tasks, (c) accept Linux-only gates for now and batch macOS verification later. Recommendation: (a), it is already written. - No reference GPU hardware for section 8. Same options; benchmarks will run on CI hardware for regression tracking, but budget compliance cannot be claimed from lavapipe.
8. Exact commands #
docker build -t cinder-toolchain:1 -f docker/Dockerfile.toolchain docker
tools/in_container.sh pixi install
tools/in_container.sh pixi run gen-dcimgui # Dear Bindings C API (committed)
tools/in_container.sh pixi run -e bindgen bindgen # cinder/ffi, layout printer, layout test (committed)
tools/in_container.sh pixi run native # Release shim + CTest + layout facts
tools/in_container.sh pixi run native-sanitize # Debug shim with ASan+UBSan + CTest
tools/in_container.sh pixi run test # lint, every Mojo test program, X11 + Wayland spikes
tools/in_container.sh pixi run format-checkCooking assets and running the sample game: not available yet (Phases 5 and 8).