Read before changing
Read the closest AGENTS.md before changing a subsystem. The root guide describes repository-level build/platform rules; engine, Kaji, Kawa, Shinra, and physics packages have local constraints.
New to game packages? Start with Tutorials (entity → session → UI → input → messages → Play/Edit), then return here for change seams.
Paths that look alike: see the naming glossary (frontend vs frontends, scenes/ vs assets/scenes/, assets/ vs resources/, SDK vs game_api, sample game vs standalone_game).
Editor syntax highlighting
VS Code-compatible grammars for Akari (.akari) and Kawa (.kawa) live in tools/vscode-extensions/. They also load in Cursor, VSCodium, Windsurf, and other editors that use the VS Code extension format.
tools/vscode-extensions/install.sh # macOS / Linux
tools/vscode-extensions/uninstall.sh
# tools/vscode-extensions/install.ps1 # Windows
# tools/vscode-extensions/uninstall.ps1
tools/nvim-plugins/install.sh # Neovim / Vim packpath
tools/nvim-plugins/uninstall.sh
# tools/nvim-plugins/install.ps1
# tools/nvim-plugins/uninstall.ps1Reload the editor window (or restart Neovim) afterward.
Change by seam
| Change | Primary seam |
|---|---|
| Game-facing API | src/hikari/sdk/src/hikari_game.zig |
Product Zig std (threads, heap, Io, …) | src/hikari/sdk/src/std/root.zig — always const std = @import("hikari_std") (see below) |
| Game editor chrome extensions | sdk/src/editor_contrib.zig (hi.editor, -Deditor-contrib) + host editor/game_contrib.zig / editor/contrib.zig (docs) |
| Source-composed plugins | Project hikari.plugins.json, package hikari.plugin.json, SDK build glue src/hikari/sdk/build/plugins/, generated hikari-plugin-artifacts.json (guide, design) |
| UI / gameplay motion | src/hikari/sdk/src/motion/ (Motion Kit) |
| Timers / delays | src/hikari/sdk/src/temporal/ (Temporal Kit) |
| Game configuration | sdk/src/project_config.zig, engine src/config.zig (configs/), game root.zig |
| Frame/runtime behavior | runtime_session.zig |
| CPU profiler / GPU markers | profiler.zig; Kaji --profiler-timing / --profiler-timing:modules=; runtime Recording (default off) + optional editor/entity/message timing via editor Profiler tab / World.profiler_overlay (UI and editor) |
| Graphics validation / DRED / GPU-based validation | Kaji --d3d12-* / --metal-* (aliases): -debug-layer defaults on in debug, -trace and -gbv off — all build-time, all usable in release (build and packaging) |
| CPU/GPU residency diagnostics | assets/asset_store/diagnostics.zig, graphics/residency/gpu_residency.zig; compile with Kaji --profiler-residency=cpu, gpu, or cpu+gpu (default off). Core lifetime management is unconditional (asset residency) |
| Backend replacement | backend/registry.zig and type-erased contract; dynamic dylib roots in backend/modules/ |
| Offline tools / benches | Engine: src/hikari/src/tools/ (entity_bench). Script cook: src/kawa/tools/kawac → bin/kawa/<host-arch>/kawac |
| Audio (cmds → worker → device mix) | src/hikari/src/audio/ (audio.zig queue+worker, mixer.zig), native AudioDevice, audio.md |
| Renderer policy | shared graphics/ and RHI |
| Shared frame-ring GPU buffer | graphics/renderer/dynamic_buffer_manifest.zig + both platform/*/graphics/dynamic_buffer_types.zig adapters |
| Engine scene component | scene/components/<name>.zig (descriptor, authoring rows, presentation, normalization, live topology) + scene/components/builtin_components.zig catalog; keep publish/spawn policy in its owning subsystem |
| Engine builtin capability/storage | sdk/src/builtin_component_manifest.zig + scene/world/world_components.zig; keep publish/spawn policy in its owning subsystem |
| API implementation | platform/native driver folders |
| Authoring behavior | Shared plane editor/authoring/scene_mutation.zig → editor.Document / SceneDocument (architecture, ui-and-editor) |
| Scene transactions (plugins) | sdk/src/editor_scene.zig (hi.editor.scene()) → host game_contrib → scene_mutation (Plugin API) |
| Package settings | sdk/src/settings.zig (hi.settings) → .engine/plugin-settings/<id>.json |
| Post-process pass registration | sdk/src/post_process_pass.zig + hi.render().registerPostProcessPass (fixed assemble slots; do not extend pass_registry.Kind) |
| Host editor panels / menus | editor/contrib.zig + bindFill (docs) |
| Asset processing | Shinra plugin/pipeline + runtime reader |
| Product build | typed Kaji plan/build unit |
Prefer a narrow, explicit boundary over cross-layer switches. In particular, do not make shared code select platforms, expose native handles, or make the live World the editor's authoring database.
Engine extension manifests
Mechanical, compile-time obligations are centralized without introducing runtime registration or indirect calls:
sdk/src/builtin_component_manifest.zigowns stable builtin names, reserved authoring identities, ids, and capability flags.builtin_components.zigsupplies the concrete SDK declarations and is order-checked against it; entity queries and scripting filters walk the generated capability fields.scene/components/<name>.zigis the public contract for one engine-authored component: itsDesc, optionalauthoring_rows, editor presentation/placement, document canonicalization, runtime target, and structural/picker field declarations. Component-specific owned seeding may live there too.scene/components/builtin_components.zigonly maps the actor tag to that module.scene_asset.zig,editor/component_catalog.zig,editor/component_schema.zig, andscene/world/live_fields.zigare adapters/facades, not second definitions.scene/world/world_components.zigowns typed store shape, capacity, reservation policy, lifecycle walks, and ownership binding. Resource-specific teardown remains explicit inscene/entity.zig; a new store without a teardown policy fails compilation.scene/actor_fields.zigderives builtin actor block names from the JSON wire type. That identity also drives prefab equality/refresh and editor presence/schema lookup.scene/scene_actor_json.zigis the canonical actor writer for both scene documents and prefabs; JSON→descriptor conversion must go through the checkedcopyMatchingpath.graphics/renderer/dynamic_buffer_manifest.zigowns shared frame-ring buffer membership and lifecycle. Each backend maps every logical name to a concrete buffer indynamic_buffer_types.zig; creation/destruction is compile-time unrolled, and render code readsrenderer.dynamic_buffers.<name>directly.
These manifests cover repeated identity, pairing, storage, and lifecycle work. Keep feature decisions—GPU record packing, scene spawn/materialization, pass scheduling, and publish hot paths—explicit in the subsystem that owns them. Component inspector labels/ranges belong to the component contract. This preserves compiler-visible direct calls and makes a new feature's remaining edits meaningful rather than registration ceremony.
Product Zig std (hikari_std)
Engine, game, and editor Zig modules do not import Zig's std directly (toolchain: Zig 0.16.0, pinned in 3rd-party/zig/VERSION). Use:
const std = @import("hikari_std");
// then std.mem, std.Thread, std.heap, …- Source:
src/hikari/sdk/src/std/root.zig(module namehikari_std; alsohi.std). - Only that file may
@import("std")(Zig). Today it re-exports Zig std; platform-specific heap/time/threads/etc. branch there later. - Exempt:
build.zigandbuild/**(host toolchain).
Validation
Use the smallest check that proves the path changed, then a product-level check when integration moved:
- Engine Zig unit work: from
src/hikari,zig build test -Dgame-src=../games/example/src(or another gamesrc/). - Entity performance changes:
zig build entity-bench -Dgame-src=../games/example/src(ReleaseFast; split metrics + composition tile sweep; baselines insrc/hikari/src/tools/entity_bench_baselines.md). - Kawa changes: its local build/examples plus the project build path.
- Renderer policy changes: prefer
src/hikari/src/graphics/(shared). Touch Metal and D3D12 only for native encoding/sync; then run the relevant product build on each host you changed. After shared graphics:zig build check-windows -Dgame-src=<abs>(Windows/D3D12 type-check only — no link). - Native C / ObjC / C++ (Metal, D3D12, frontends):
zig run src/hikari/tools/native_style.zig -- format|tidy|compile-db(wrappersnative-style.sh/.ps1). Format is host-agnostic; clang-tidy needs the matching SDK (see below). Not a Kaji product step. - Product/package changes: Kaji game/editor command with the exact link mode you changed.
- Shipping macOS matrix:
build/test-build-macos.sh. - Windows matrix:
build/test-build-windows.ps1(same grid intent;gamecells omit--project=today — see Build and packaging).
Dynamic and monolithic modes validate different ownership and loading paths. A monolithic success is not evidence that a dynamic module lifecycle change works.
Native C / ObjC / C++ style (Metal + D3D12)
The engine's native trees are a C ABI with two implementations: macOS ObjC/ARC + Metal and Windows C++20 + D3D12 (WRL::ComPtr). Format is one house style; lint overlays are per platform.
| File | Role |
|---|---|
src/hikari/.clang-format | C / C++ / ObjC / ObjC++ format (4-space, left pointers, Allman on functions, attached control braces) |
src/hikari/.clang-tidy | Shared bug/analyzer/readability baseline. No modernize-* here — this is a handle/C ABI layer |
src/hikari/src/native/macOS/.clang-tidy + .clangd | Metal / AppKit / ARC (objc-*, clang-analyzer-osx.*) |
src/hikari/src/native/Windows/.clang-tidy + .clangd | D3D12 / Win32 (microsoft-*, nullptr/override, Agility + PIX includes) |
src/hikari/src/native_frontends/{macOS,Windows}/ | Process frontends (ObjC++ vs C++20); both include shared/frontend_core.inl |
# Zig 0.16 (same helper pattern as unused_pub.zig). clang-tidy: brew install llvm@22 — unversioned `brew install llvm` is a stub on this repo's Tenkai path.
zig run src/hikari/tools/native_style.zig -- format # all native trees
zig run src/hikari/tools/native_style.zig -- format --check
zig run src/hikari/tools/native_style.zig -- tidy # host SDK only
zig run src/hikari/tools/native_style.zig -- tidy --platform macos
zig run src/hikari/tools/native_style.zig -- compile-db # build/cache/native-lint/compile_commands.json
src/hikari/tools/native-style.sh format # finds zig, cds to repo rootsrc/hikari/tools/native-style.ps1 format
src/hikari/tools/native-style.ps1 tidyclangd picks up the nearest .clangd. Warnings are advisory: product compiles already use -Wall -Wextra -Werror / /W4 /WX /analyze. Do not mass-format -i without an explicit cleanup change — the configs describe the intended style, not a guarantee that every existing file matches it.
clang-tidy on D3D12 files needs a Windows SDK (and the other way around for Metal). Format does not.
Error and allocation policy
Fail fast for unrecoverable setup invariants and one-time out-of-memory conditions. Best-effort capacity reservation may be ignored only if a later append retries and correctness remains intact. Per-frame backend allocation failures should retry next frame and be counted/throttle-logged; dropped render work must also be counted/throttle-logged.
Keep ownership explicit and avoid hidden per-frame allocations. UI, rendering, and scene frame paths retain/prewarm capacity where appropriate. Persistent World/entity memory goes through GameplayAllocator (src/hikari/src/memory/gameplay_allocator.zig) — do not bypass it with ad hoc page_allocator on spawn hot paths. That heap is game-thread only (Debug/ReleaseSafe panic on a foreign-thread call); anything another thread allocates or frees — render-command payloads (vertex buffers, meshlet topology, particle program bytes), the render command queue and its staging pool, the frame swap and surface-UI mailboxes, the material cache (shader companions are built on the render thread through Material.allocator), parallel slice worker buffers — lives on World.shared_allocator, the thread-safe parent heap. RenderComponent/RenderPrimitive are built on it, and the queue asserts every payload allocator at enqueue.
Debug leak tracking (ReleaseFast is a no-op): GameplayAllocator dumps grouped alloc stacks on World.destroy; SubsystemAllocator (Host, Platform, Dispatch, Game, Physics, Play) uses Zig DebugAllocator with 6-frame stacks in Debug. Play Stop checks the Play world + Play heap; editor/game quit checks every owned world, then Physics/Game/Dispatch/Platform, then the frontend Host heap. A full empty-world unload (unloadScene / budgeted unload) also Debug-checks transient gameplay leftovers (Gameplay(scene)). Overlap-swap (incoming scene already spawned) and additive layer unload do not — other actors still own the heap. World-lifetime structure uses World.durableAllocator() (intern, SoA, handle table, native update batch table, LOD tables, skybox path). A budgeted-unload entity snapshot must be freed before finishBudgetedUnload. HIKARI_ALLOW_LEAK=1 soft-fails subsystem leaks in Debug; Kaji --memory-leak-check --run sets HIKARI_FAIL_ON_LEAK=1 for ReleaseSafe. Per-tick World.arena is not leak-checked as a drain.
Documentation policy
Update the relevant page in this docs/ tree with architecture, build-flow, or platform-status changes. Keep source comments focused on local invariants; keep cross-system behavior here.
AGENTS.md files are for AI assistants: short package maps, hard contracts, forbidden patterns, and validation commands. They must not become a second copy of this docs tree. When behavior changes, update the matching docs/ page for humans and only adjust AGENTS.md when agent-critical paths, invariants, or build commands change.