# Architecture

## Purpose and boundaries

This repository is a polyglot desktop game-engine workspace. The game owns its project configuration, entities, scene data, source assets, and optional global subsystem. The engine owns runtime orchestration, scene execution, graphics policy, input/action resolution, asset access, UI composition, and editor chrome.

The Zig product graph has a strict one-way boundary. `src/hikari/src/hikari_api.zig`
is the runtime/game root and cannot reach `src/hikari/src/editor/`. The editor uses the
separate `src/hikari/src/hikari_editor_api.zig` root, which composes the runtime
surface with editor-only code. Runtime and editor tests are built from those
separate roots as well; editor test coverage is never obtained by importing the
editor into the runtime module.

The design keeps stable authoring contracts above replaceable or platform-specific implementation:

```mermaid
flowchart TB
    native["Native host"] -->|C ABI| app["Game / editor application"]
    app --> session["SessionCore"]
    session --> host["GameHost"]
    session --> world["World"]
    session --> services["Runtime services"]
    host --> global["Cross-scene state"]
    world --> scene["Actors · scripts · UI"]
    services --> runtime["Drivers · threads · assets"]
    class session accent
```


`SessionCore` is the main live-runtime seam. It owns the world, dispatch system, physics subsystem, audio system (driver + command worker), renderer and render thread, input/action state, game host, and content paths. `App` and `EditorApp` are hosts around it; the native frontends retain process/event-loop ownership. Within `World`, domain-owned state is embedded by value (`PhysicsState` and the event dispatcher state in `World.events`) so ownership is visible without adding allocations or pointer indirection to hot paths. Cross-scene state and session scripts are documented in [Session services](/docs/systems/session-services.md). Audio threading is documented in [Audio](/docs/systems/audio.md#threading-model).

### Native host domains

The macOS and Windows process layers use the same ownership shape under `src/hikari/src/native/<platform>/src/`:

| Domain | Owns |
|---|---|
| `application/` | Boot and ordered shutdown orchestration only |
| `host/` | Minimal process-lifetime state shared by native domains |
| `windowing/` | Primary/tool windows, registry/controllers, chrome, window events, and window exports |
| `frame/` | Display-driven scheduling, pacing policy application, and frame exports |
| `startup/` | Startup presentation and its completion export |
| `system/` | Appearance and power-state observation |
| `services/` | Dialog, clipboard, and file-manager integrations |

The dependency direction is inward toward `host/`; `host/` does not import the feature domains. Native frontends use the public platform headers and provide renderer callbacks, but do not reach into HWND/NSWindow registries or own native-bridge globals. Tool-window creation is an atomic platform operation over the shared `native/include/NativeWindowing.h` contract: validation, OS-window creation, renderer-surface initialization, registration, input adoption, geometry publication, and rollback stay inside `windowing/`.

Kaji compiles each native domain into its own object directory and links only the object patterns for the currently declared domain set. Per-domain compile steps prune orphan objects, and static archives are recreated. A removed or renamed domain can therefore leave an inert cache directory, but it cannot leak stale code into a later link.

**CPU jobs vs file streams vs audio:** `dispatch` runs short non-blocking work on a worker pool. Long-lived append IO uses `io_stream` (one writer thread, per-stream SPSC slots, drop-if-busy) so the game thread never blocks on disk — profiler file traces and the engine log file tee are clients. Audio is a third pattern: game posts fixed-size commands; a dedicated audio worker owns decode and voices; the OS device callback only mixes (see [Audio](/docs/systems/audio.md)).

## Repository map

Sample and reference games are a sibling checkout (`../hikari-games`), not this tree. `--workspace` is this engine repo; `--project` is a game folder. Packaged editors open a game by folder and resolve the SDK from `<exe>/sdk` or a nearby `hikari` checkout.

| Area | Responsibility |
|---|---|
| `src/hikari` | Zig engine, public API, native frontends, platform adapters, built-in shaders |
| `hikari-games/example` | Sample game package, scenes, source assets, input actions, game shaders (sibling repo) |
| `hikari-games/craft` | Voxel dig/place sample game |
| `hikari-games/metropolis` | Crowd / city streaming sample |
| `hikari-games/thomas` | Narrative / level sample |
| `src/kaji` | .NET build conductor and typed product/build-unit plans |
| `src/kawa` | C scripting VM, compiler, archive support, examples, editor integrations |
| `src/shinra` | Rust asset compiler, bundle builder, validators, watch mode |
| `src/akari` | Rust shader language + end-to-end Metal/DXC compile |
| `src/tenkai3d` | 3D physics middleware (wired into the engine) |

### Naming glossary (easy to confuse)

These paths are **intentionally different**; do not merge them mentally or in PRs.

| Name | Means | Not to confuse with |
|---|---|---|
| `src/hikari/src/native_frontends/` | Native process entry (ObjC++/C++ `main`, event loop) | `src/hikari/src/application/` (Zig application ABI/lifecycle) |
| `src/hikari/src/application/` | Zig game/editor application libraries over `hikari_frontend.h` | `native_frontends/` (OS entrypoints) |
| `src/hikari/src/graphics/` | Shared renderer policy by feature (`renderer/`, `rendergraph/`, `rhi/`, `shadow/`, `raytracing/`, `material/`, `residency/`, `effects/`, `gpu/`, `debug/`) | Platform encode in `platform/*/graphics/` |
| `src/hikari/src/scene/components/` | Actor component types + store | `scene/world/` (world domain modules) |
| `src/hikari/src/editor/panels/` | Inspector, hierarchy, residency, … | `editor/dialogs/`, `editor/viewport/` |
| `hikari-games/example/assets/scenes/*.json` | Authoring scene JSON documents (placeable actors) — a scene is any `.json` with `kind: com.hikari.scene`, wherever it sits | `hikari-games/example/assets/scenes/<name>/` (per-scene **asset bags**: models, materials, scripts for demos, kept beside the scene by convention only) |
| `hikari-games/example/assets/` | Cookable art tree (textures, models, materials, shaders, scripts) | `hikari-games/example/resources/` (loose **content** dirs from `content_dirs`; often not Shinra-cooked the same way) |
| `hikari-games/example/resources/` | Project content roots (`hikari.project.json` → `content_dirs`) for `ContentRef` / staged loose data | `assets/` (pipeline art) |
| `src/hikari/sdk/` | Thin game ABI pack (`hikari_game`); what games import | `src/hikari/src/game_api/` (host-only bind; never packed into the SDK) |
| `src/hikari/src/backend/` | Driver contracts + `registry.zig` | Bodies in `backend/modules/`; thin build roots `src/hikari/src/driver_module_*.zig` (Zig package root must be `src/`) |
| `src/hikari/src/tools/` | Zig CLI / bench **bodies** (`entity_bench`, …) | Thin roots under `src/` |
| `src/kawa/tools/kawac/` | Offline bytecode compiler (`kawac`) | Built by Kawa Kaji unit; staged to `bin/game/tools/` for Shinra |
| `hikari-games/example` (repo sample) | Full sample product project | `src/hikari/standalone_game/` (editor stub when `kaji editor` has no `--project`) |
| `bin/hikari/` | Engine/tool staging from Zig build | `bin/game/` (packaged product under Kaji) |

Human-facing change seams and validation: [Development guide](/docs/guides/development.md).

World implementation files are grouped under `src/hikari/src/scene/world/`:
`render/` owns render publication and retained render state, `scene/` owns scene
loading and editing, and `ui/` owns World UI instances. Their sibling facades
(`render.zig`, `scene.zig`, `ui.zig`) keep the domain entry points visible; the
World state remains in `types.zig`.

The shared macOS/Windows audio and input C-ABI adapters live in
`src/hikari/src/platform/native/`. Native OS implementations remain under
`src/hikari/src/native/{macOS,Windows}/`. Common build linking lives in
`src/hikari/build/link.zig` and `kawa.zig`; platform linker details remain in the
platform build directories, including the planned Linux backend.

Kaji's `src/` root contains its program entry point and project file. Product
orchestration lives in `Conductors/`, built-in units in `Units/`, command-line
parsing in `Cli/`, workspace discovery in `Workspace/`, and shipping code in
`Packaging/`. Reusable build execution stays in `Build/`; console output stays
in `Frontend/`.

World space is right-handed, Y-up, −Z-forward with reverse-Z clip depth. See [Coordinate space](/docs/systems/coordinate-space.md).

## Public and private seams

Each seam answers the same four questions: **why it exists**, **what crosses it**, **what guards it**, and **how to change it** (including which version to bump).

### Game SDK seam (`hikari_game`)

**Why.** Game modules reload independently of the host; they must not share engine memory layout.

**What.** Games import only `hikari_game` (`src/hikari/sdk/src/`). Product Zig uses `const std = @import("hikari_std");` — only `sdk/src/std/root.zig` may import Zig `std`. The host installs a versioned `HostApi` (required subtables `world` / `render` / `ui` / `animation` / `particle`; optional `debug` / `paths` / `jobs` / `audio` / `settings` / `config`). Facades: `hi.world()`, `hi.render()`, `hi.ui()`, `hi.debug()`, `hi.audio()`, `hi.animation()`, `hi.particle()`, `hi.settings`, `hi.config`. Entities are generation-checked `ActorRef` handles (`EntityId` is an alias). The game exports `config` and `content_manifest`; each `defineActor` becomes one host archetype descriptor.

**Optional editor chrome.** With `-Deditor-contrib=true` (editor recompile always; standalone games default off), `hi.editor` and optional `GameModule.register_editor` register menus/panels/commands via C-ABI → host `editor/game_contrib.zig`. Not compiled into non-editor game modules — [Game editor SDK](/docs/systems/ui-and-editor.md#game-editor-sdk-hieditor).

**Guard rails.** C-ABI handshake (`hikari_game_abi_version`, `hikari_game_zig_version`) before reading `GameModule`; then `abi_version`, Zig/optimize/profiler gates; then per-table `api_version` at `bindHost`. Stale `EntityId` is a no-op. Editor contrib uses its own `api_version_current` and compile-time isolation.

**Changing it.**

| Change | Action |
|---|---|
| Add/change a fn or field in a subtable (`WorldApi`, `RenderApi`, `UiApi`, `DebugApi`, `AnimationApi`, `ParticleApi`, `AudioApi`, `JobsApi`, `PathsApi`, `SettingsApi`, `ConfigApi`) | Bump that table's `*_api_version_current`; implement in `host_bind.zig`; add the facade method beside the table |
| Add/remove a subtable, touch root `HostApi` fields | Bump `host_api_version_current`. Extract a new subtable only when a domain is its own subsystem (`animation` / `particle` / `audio`); do not split `WorldApi` merely to shrink the vtable |
| Semantic change to `GameModule` loading/lifecycle | Bump `abi_version_current` (`game_module_def.zig`) |
| `editor_contrib.Api` layout / semantics | Bump `editor_contrib.api_version_current`; update host `game_contrib.zig` |
| Facade methods / helper types over *existing* table entries | No bump — they compile into the game module |
| Shared leaf types (`motion`, `render_config`, …) | Import the named build modules only, never by path — file identity must stay unique |

Session policy: Zig `GameModule.config` defaults + authored/staged solo `configs/*` overlays + writable per-user overlays (`Config.sessionConfig` / `project_config.composeLayers`). Load each layer once; compose in order without a second disk pass. Kind registry SSOT: `project_config.kinds`. `hikari.project.json` is identity only and its durable id scopes the platform user-data root. Editor chrome is engine-owned; `game_ui` is runtime UI / viewport only.

### Driver seam (backend modules)

**Why.** Platform and middleware implementations (windowing, input, rendering, physics, scene scripting, game UI, audio) must be replaceable per product and platform — dynamic on desktop/tools, statically registered on consoles — without platform switches leaking into shared engine code.

**What.** Shared runtime code asks `backend.Registry` for type-erased driver implementations; dynamic driver dylibs export a `DriverModule` descriptor of factories. This is an engine-internal Zig contract, deliberately not a third-party binary ABI: engine types cross it freely because drivers rebuild with the engine.

**Guard rails.** Versioned entry symbol (`hikari_driver_module_entry_v1` — an incompatible module simply fails symbol lookup), `format_version`, `engine_contract_version`, a `LayoutFingerprint` derived from every `DriverModule` field's name/offset/size/alignment (catches a forgotten version bump; derived rather than hand-listed so a new field cannot escape it), and Zig/optimize-mode checks. Process-wide singletons (log sink, geometry ledger) are installed into each loaded module through the single `install_host_state` hook — a driver dylib carries its own copy of every engine global, so adding one means adding a field to `HostProcessState` and a line to its `adopt`, never a per-driver edit. Drivers are destroyed before their module unloads. Shared code must not select platforms with `builtin.os.tag` or expose native graphics resources through the public graphics API.

**Changing it.** Factory, vtable, or cross-module type change → bump `engine_contract_version` (`backend/module.zig`). `DriverModule` descriptor layout change → bump `module_format_version` and the entry-symbol suffix. New driver kinds follow the checklist in [Frontends and drivers](/docs/systems/frontends-and-drivers.md).

### Editor authoring seam

**Why.** The editor links the full engine, so its risk is not ABI but *unmediated mutation*: edits that bypass history break undo and desynchronize the authored document from the live preview.

**What.** `SceneDocument` is the authored source of truth; the running `World` is a live preview. **Enter only via** `editor/authoring/scene_mutation.zig` (`Operation`, `max_operations`, `applyBatch` / `apply` / `applyJoin`, txn helpers) — host chrome, MCP `scene_edit` / plane tools, and plugin `hi.editor.scene()` all share that plane. Each batch becomes document history with one undo label; live apply uses `ActorMutation` payloads (`SceneEditAccess`) so document and preview stay in sync. Mutations carry enough to invert — undo/redo and live sync fall out of one channel.

**Guard rails.** No front-end may call ad-hoc `SceneDocument` mutators for authoring. `editor.Document` remains the history boundary under the plane (component fields via `ActorMutation.component_field` / `ComponentFieldEdit`, entity active via `ActorMutation.active`); round-trip serialization tests plus comptime field-count tripwires catch a scene field added in one spot but not the others; retained widgets must not keep pointers into document actor storage.

**Changing it.** No version numbers — editor and engine are one binary. New op kinds land in `scene_mutation` first (MCP wire + SceneApi bridge consume them). An authorable scene/component field touches every serialization spot *and* the mutation path; the comptime tripwires fail the build if one is missed. New mutation kinds must carry their inverse.

### Graphics boundary

Viewport output, active-render and allocation extents, plus the host presentation-list fallback, live in `graphics/renderer/surface_policy.zig`. Frame preparation and orchestration read this policy directly; dimensions are not backend hooks. Screenshot request handling and encoding live in `graphics/renderer/screenshot.zig`, with native pixel readback supplied by each backend. Ray-tracing residency rows come from the shared scene owner, and micromap arena growth follows the shared opacity-micromap limits.

Shared ownership extends to backend resource state. GPU culling and light binning use one slot definition each, parameterized by native buffer types, with backend command/staging/readback fields grouped separately. `opacity_micromap_storage.zig` owns arena growth, retirement and bake receipts. `surface_lifecycle.zig` owns secondary-surface admission, publication and teardown after a native GPU drain; `renderer_lifecycle.zig` owns the common renderer cleanup blocks. Native adapters retain pipeline creation, bindings, heap placement and synchronization. Particle density and reception bake sequences live in `graphics/particles/`.

On a macOS or Linux host the D3D12 half of `platform/` is never analysed by an ordinary build — Zig is lazy and the platform arm is selected by `switch (builtin.os.tag)` — so its comptime contracts do not run. `zig build check-windows` builds a second module graph pinned to `x86_64-windows-msvc` and type-checks it (no link). The ABI must be `msvc`, not `gnu`: Clang types an unfixed C enum as `int` under the MS ABI and `unsigned int` under gnu, and the gnu choice turns every runtime enum passed to a D3D12 entry point into a false "expected c_uint, found c_int".

Parity between the two backends is held by comptime contracts checked in **both** directions: `renderer_contract` for renderer fields, `pass_encoder.validate` for the encode verbs (`requiredVerbNames()` is the contract; `pass_encoder` asserts its count so the list and the validator cannot drift), and `rendergraph/backend.zig` for the graph adapter. Each requires the shared set and rejects anything else unless the backend names it in its own `platform_only_decls`, so a one-sided hook is a recorded decision rather than an unnoticed difference. D3D12 root-parameter ordinals derive from the named layouts in `graphics/gpu/binding_layout.zig`, and each slot's register is checked against `shaders/bindings.kaji.json` by a test that runs on any host. The frame entry/exit sequence lives once in `graphics/renderer/frame_entry.zig`; backend differences are named hooks both backends declare, Metal's as empty bodies that record why.

Shared render code does not ask what a backend has. `@hasDecl` / `@hasField` probes are banned in `src/graphics` (`zig build check-probes`, a dependency of `zig build test`, fails on a new one): a hook is required and no-opped where it has nothing to do, a real capability difference is a constant both sides give a value to, and the few unavoidable questions have one named predicate each. A probe used as a `@compileError` assertion is the replacement, not the problem.

Target resolution is policy and runs once in `pass_targets.Plan`, a fully resolved description of which attachments a pass writes, what happens to them on entry, and what state they are left in. Each backend implements a single `beginRenderPass` over it. Because the plan is a value, `fake_renderer.zig` can run the real graph (assemble, compile, schedule, execute) against a recording encoder for every driver in its `traced_kinds` list, so the verb sequence a pass emits is a fact a Mac can assert without a GPU. Kinds that need more than a null backend can provide are listed in `untraced_kinds` with a reason, and a comptime check requires every registry kind to appear in exactly one list. `call_trace` (Debug, `renderer.common.backend_trace_request`) records one frame of verbs so the two backends can be diffed.

Shared cores hold what would otherwise be written twice: `gpu/buffer_stream.zig`, `debug/pass_timing.zig`, `renderer/renderer_display.zig` (reconfigure ordering), `renderer/frame_entry.zig`, and `shadow/shadow_pass.zig`. What remains mirrored is parallel control flow over different APIs; reducing it further means a command-recording vocabulary in `rhi/`, not more extraction.

`src/hikari/src/graphics/rhi/` is the backend-neutral vocabulary for resources, state transitions, queues, capabilities, compute, and acceleration structures. Renderer policy (frame graph, PSO/resource tables, uniforms, residency/lifecycle) lives under `src/hikari/src/graphics/`; Metal/D3D12 implementations create native objects, encode commands, synchronize, and present. Backend identity (shader artifact format and reconstruction payload tag) and any render-thread scope are reported by the selected renderer driver; shared graphics never derives them from the host OS. See [Rendering](/docs/systems/rendering.md#renderer-model).

## Ownership rules

- Native frontends own process entry and the stable frontend ABI callbacks; the native bridge owns OS windows and event-loop integration.
- Zig application libraries translate the C ABI and arrange host-specific behavior.
- `SessionCore` owns the running engine subsystems and destroys them in lifecycle order.
- `GameSubsystem` owns session-scoped services; scene unload must not destroy it.
- `SceneDocument`, not a runtime `World`, is the editor's mutable authored source of truth.
- `World` owns live **scene** entities and retained runtime UI state (session HUD may draw into `world.ui` without owning an entity).
- The render thread owns render-thread-sensitive backend reconfiguration.
- Platform/native folders are implementation details below engine-facing contracts.

## Useful entry points

| Need | Start at |
|---|---|
| Game-facing API (thin ABI) | `src/hikari/sdk/src/hikari_game.zig` |
| Game editor chrome SDK | `sdk/src/editor_contrib.zig` (`hi.editor`, opt-in) → host `editor/game_contrib.zig`; [docs](/docs/systems/ui-and-editor.md#game-editor-sdk-hieditor) |
| Engine/host API | `src/hikari/src/hikari_api.zig` |
| Runtime composition | `src/hikari/src/runtime_session.zig` |
| Project contract | `src/hikari/sdk/src/project_config.zig` and `game_contract.zig` |
| Backend selection | `src/hikari/src/backend/registry.zig` |
| Graphics front door | `src/hikari/src/graphics/api.zig` |
| Scene/actor model | `src/hikari/src/scene/` |
| Editor state/events | `src/hikari/src/editor/state.zig` and `docs/systems/ui-and-editor.md` |
| Host editor contributions | `src/hikari/src/editor/contrib.zig` |
| Build product graph | `src/kaji/src/Conductors/HikariBuildPlans.cs` and `HikariGameConductor.cs` |
