# Development guide

## 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](/docs/tutorials.md) (entity → session → UI → input → messages → Play/Edit), then return here for change seams.

**Paths that look alike:** see the [naming glossary](/docs/systems/architecture.md#naming-glossary-easy-to-confuse) (`frontend` vs `frontends`, `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/`](https://github.com/Flying-Rat/hikari/blob/develop/tools/vscode-extensions/). They also load in Cursor, VSCodium, Windsurf, and other editors that use the VS Code extension format.

```sh
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.ps1
```

Reload 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](/docs/systems/ui-and-editor.md#game-editor-sdk-hieditor)) |
| 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](/docs/guides/plugins.md)) |
| UI / gameplay motion | `src/hikari/sdk/src/motion/` ([Motion Kit](/docs/systems/motion-kit.md)) |
| Timers / delays | `src/hikari/sdk/src/temporal/` ([Temporal Kit](/docs/systems/temporal-kit.md)) |
| 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](/docs/systems/ui-and-editor.md#profiler)) |
| 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](/docs/guides/build-and-packaging.md#graphics-diagnostics-d3d12--metal)) |
| 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](/docs/systems/asset-residency.md#diagnostics)) |
| Backend replacement | `backend/registry.zig` and type-erased contract; dynamic dylib roots in `backend/modules/` |
| Offline tools / benches | Engine: `src/hikari/src/tools/` (`benchmarks/`, `tests/`, `checks/`, `assets/`); host helpers: `src/hikari/tools/`. 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`](/docs/systems/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](/docs/systems/architecture.md#editor-authoring-seam), [ui-and-editor](/docs/systems/ui-and-editor.md#editor-documents-and-property-editing)) |
| Scene transactions (plugins) | `sdk/src/editor_scene.zig` (`hi.editor.scene()`) → host `game_contrib` → `scene_mutation` ([Plugin API](/docs/guides/plugin-api.md)) |
| Package settings | `sdk/src/settings.zig` (`hi.settings`, read-only) → host `src/plugin_settings.zig` → `configs/plugin_settings.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](/docs/systems/ui-and-editor.md#contributions-host-chrome)) |
| 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.zig` owns stable builtin names, reserved authoring identities, ids, and capability flags. `builtin_components.zig` supplies the concrete SDK declarations and is order-checked against it; entity queries and scripting filters walk the generated capability fields.
- `scene/components/<name>.zig` is the public contract for one engine-authored component: its `Desc`, optional `authoring_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.zig` only maps the actor tag to that module. `scene_asset.zig`, `editor/component_catalog.zig`, `editor/component_schema.zig`, and `scene/world/live_fields.zig` are adapters/facades, not second definitions.
- `scene/world/world_components.zig` owns typed store shape, capacity, reservation policy, lifecycle walks, and ownership binding. Resource-specific teardown remains explicit in `scene/entity.zig`; a new store without a teardown policy fails compilation.
- `scene/actor_fields.zig` derives 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.zig` is the canonical actor writer for both scene documents and prefabs; JSON→descriptor conversion must go through the checked `copyMatching` path.
- `graphics/renderer/dynamic_buffer_manifest.zig` owns shared frame-ring buffer membership and lifecycle. Each backend maps every logical name to a concrete buffer in `dynamic_buffer_types.zig`; creation/destruction is compile-time unrolled, and render code reads `renderer.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.17.0**, pinned in `3rd-party/zig/VERSION`). Use:

```zig
const std = @import("hikari_std");
// then std.mem, std.Thread, std.heap, …
```

- **Source:** `src/hikari/sdk/src/std/root.zig` (module name `hikari_std`; also `hi.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.zig` and `build/**` (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=../../../hikari-games/example/src` (or another game `src/`).
- Entity performance changes: `zig build entity-bench -Dgame-src=../../../hikari-games/example/src` (ReleaseFast; split metrics + composition tile sweep; baselines in `src/hikari/src/tools/benchmarks/baselines/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` (wrappers `native-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; see [Build and packaging](/docs/guides/build-and-packaging.md#host-support)).

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` |

```bash
# Zig 0.17 (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 root
```

```powershell
src/hikari/tools/native-style.ps1 format
src/hikari/tools/native-style.ps1 tidy
```

clangd 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 `SafeAllocator` 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`. `--leak-policy=report` reports subsystem leaks without panicking; Kaji `--memory-leak-check --run` forwards `--leak-policy=fail`. Explicit policy overrides build defaults. 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. Public pages describe implemented behavior and must serve their stated audience. Keep plans, roadmaps, and internal review records under `docs/internal/`, which is excluded from website publication.

Run `python3 tools/docs/check_public_docs.py` from the repository root to validate JSON examples and local links. Optional compiler checks:

- `--kawac=bin/kawa/<platform-arch>/kawac` syntax-compiles every Kawa fence.
- `--zig=zig` builds the complete tutorial actor declarations and project configuration against the SDK.
- `--akari=bin/akari/akari` type-checks complete shader examples and transpiles them for Metal, D3D12, and Vulkan.

These checks use locally built tools. They do not execute host APIs or prove a sample’s gameplay behavior. In the website checkout, `npm run docs:sync -- --strict` also checks public document anchors and rebuilds the publication from scratch.

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