Skip to content
hikari
RenderingEditorAIToolchainDocumentation
GitHub
hikari

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsLicenseBack to top
Documentation / Systems
Browse docs
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Systems12 min read

Architecture

On this page
On this pagePurpose and boundariesNative host domainsRepository mapNaming glossary (easy to confuse)Public and private seamsGame SDK seam (hikari_game)Driver seam (backend modules)Editor authoring seamGraphics boundaryOwnership rulesUseful entry points Back to top

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:

Diagram
Diagram source
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. Audio threading is documented in Audio.

Native host domains

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

DomainOwns
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).

Repository map

AreaResponsibility
src/hikariZig engine, public API, native frontends, platform adapters, built-in shaders
src/games/exampleSample game package, scenes, source assets, input actions, game shaders
src/games/craftVoxel dig/place sample game
src/games/metropolisCrowd / city streaming sample
src/games/thomasNarrative / level sample
src/kaji.NET build conductor and typed product/build-unit plans
src/kawaC scripting VM, compiler, archive support, examples, editor integrations
src/shinraRust asset compiler, bundle builder, validators, watch mode
src/akariRust shader language + end-to-end Metal/DXC compile
src/tenkai3d3D physics middleware (wired into the engine)
src/tenkai2d2D physics middleware (not in the engine 3D path; not product-registered)

Naming glossary (easy to confuse)

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

NameMeansNot 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.hnative_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 + storescene/world/ (world domain modules)
src/hikari/src/editor/panels/Inspector, hierarchy, residency, …editor/dialogs/, editor/viewport/
src/games/example/scenes/Authoring scene JSON documents (placeable actors)src/games/example/assets/scenes/ (per-scene asset bags: models, materials, scripts for demos)
src/games/example/assets/Cookable art tree (textures, models, materials, shaders, scripts)src/games/example/resources/ (loose content dirs from content_dirs; often not Shinra-cooked the same way)
src/games/example/resources/Project content roots (hikari.project.json → content_dirs) for ContentRef / staged loose dataassets/ (pipeline art)
src/hikari/sdk/Thin game ABI pack (hikari_game); what games importsrc/hikari/src/game_api/ (host-only bind; never packed into the SDK)
src/hikari/src/backend/Driver contracts + registry.zigBodies 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
src/games/example (repo sample)Full sample product projectsrc/hikari/standalone_game/ (editor stub when kaji editor has no --project)
bin/hikari/Engine/tool staging from Zig buildbin/game/ (packaged product under Kaji)

Human-facing change seams and validation: Development guide.

World space is right-handed, Y-up, −Z-forward with reverse-Z clip depth. See Coordinate space.

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.host_api.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.

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.

ChangeAction
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 fieldsBump host_api_version_current. Extract a new subtable only when a domain is its own subsystem (animation / particle / audio); do not split WorldApi to shrink the vtable — see src/hikari/AGENTS.md WorldApi policy
Semantic change to GameModule loading/lifecycleBump abi_version_current (game_module_def.zig)
editor_contrib.Api layout / semanticsBump editor_contrib.api_version_current; update host game_contrib.zig
Facade methods / helper types over existing table entriesNo 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.

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.

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.

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

NeedStart at
Game-facing API (thin ABI)src/hikari/sdk/src/hikari_game.zig
Game editor chrome SDKsdk/src/editor_contrib.zig (hi.editor, opt-in) → host editor/game_contrib.zig; docs
Engine/host APIsrc/hikari/src/hikari_api.zig
Runtime compositionsrc/hikari/src/runtime_session.zig
Project contractsrc/hikari/sdk/src/project_config.zig and game_contract.zig
Backend selectionsrc/hikari/src/backend/registry.zig
Graphics front doorsrc/hikari/src/graphics/api.zig
Scene/actor modelsrc/hikari/src/scene/
Editor state/eventssrc/hikari/src/editor/state.zig and docs/systems/ui-and-editor.md
Host editor contributionssrc/hikari/src/editor/contrib.zig
Build product graphsrc/kaji/src/HikariBuildPlans.cs and HikariGameConductor.cs
PreviousSystemsNext Application lifecycle

Documentation follows the current engine checkout.

Snapshot cc148c75Source docs/systems/architecture.md
On this pagePurpose and boundariesNative host domainsRepository mapNaming glossary (easy to confuse)Public and private seamsGame SDK seam (hikari_game)Driver seam (backend modules)Editor authoring seamGraphics boundaryOwnership rulesUseful entry points Back to top