ADR-0013: Tools and the application runtime (Phase 7)
Status: accepted, 2026-11-09.
Context #
Phase 7 asks for an ImGui debug overlay (entity inspector with editing, asset browser, render
graph view, frame-time graph, log console), Tracy integration throughout, and a --headless
mode for tests and benchmarks. The engine had no single owner of its subsystems: every test
assembled Gfx, Resources, Renderer, the ECS and the rest by hand.
Decisions #
Appowns the subsystems and the loop (cinder/app). It builds the job system, the platform/window/swapchain (or nothing in headless mode),Gfx/Resources/Renderer, the physics world, the audio engine (device-less when headless), the animation system, the input map, the ECS world, a scene registry and an in-memory log.run_frameis the canonical frame: poll, fixed steps (ECS tick, kinematics in, physics step, dynamics out,Game.fixed_update), interpolated transforms,Game.update, animation, transform propagation, audio sync, asset streaming, render, overlay, present. Gameplay implements theGametrait, so the sample game, tests and benchmarks run the same code.- Headless is the same loop without a window. Frames render into an RGBA8 offscreen
texture that
read_framereads back. Every Cinder executable accepts--headlessthroughparse_app_args, together with--width/--height/--title/--profile/--no-audio/--no-debug-ui. - Subsystems with inbound pointers live in
Boxed[T](cinder/core/boxed.mojo): a heap cell with a stable address, so anAppvalue can be moved and returned whileResourceskeeps pointing at the sameGfx. Fields are declared in destruction order (overlay and gameplay first, GPU last, the native library at the end). - Tracy with a manual lifetime. The client is compiled with
TRACY_MANUAL_LIFETIMEandTRACY_DELAYED_INIT; nothing runs (no socket, no thread) untilProfiler.start()(the--profileflag). Zones are RAIIZonevalues; the app wraps fixed steps, animation, audio and rendering, andRenderer.render/AssetServer.updateopen their own zones. Zone contexts cross the FFI as a packeduint64, so Mojo never sees Tracy's struct. In Tracy 0.15 messages are___tracy_emit_logString. - The overlay is data-driven.
DebugUirenders through the shim's SDL3 + WebGPU ImGui backends into the swapchain frame after the scene. The inspector is built from the scene registry's document snapshot (SceneRegistry.to_doc), so any component that is scene-serialisable is also inspectable; numeric fields are editable and written back through the registry's reader (components with entity references are read-only, since a single-entity edit cannot resolve them). Strings handed to ImGui are copied into per-frame byte buffers, becauseStringhas inline storage and moves when a list grows. - The swapchain frame is imported, not owned.
Resources.import_texture_viewwraps the acquired view in a handle for the renderer andforget_texture_viewdrops it without releasing, keeping the renderer's typed-handle API. - Event routing.
Platform.forward_to_imguihands every raw SDL event to ImGui before translation; the app skips mouse/keyboard events for the game'sInputMapwhile ImGui wants them.
Consequences #
- Phase 8's sample game is a
GameoverApp; its benchmarks run with--headlessand readApp.read_frame/ renderer stats. - Engine components become inspectable as they gain
SceneComponentconformance (Phase 8 registers the sample's components; built-in components follow when the scene format grows past v1). - dcimgui passes
float v[3]arrays by value in the generated bindings, so the overlay uses oneInputFloatper component instead ofDragFloat3.