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
Systems7 min read

Platforms and support

On this page
On this pageCurrent supportWindows D3D12 Agility SDKWindows DPILinuxLayeringWell-known directoriesSecure storageFrontend responsibilitiesEditor argvGame argvFrame pacingGraphics parity Back to top

Current support

PlatformArchitectureGraphicsStatus
macOSApple Silicon (aarch64)Metal 4.0 MSL; product floor macOS 26Active and validated
Windowsx86_64D3D12 + Agility SDKActive
Linuxx86_64Vulkan plumbingIncomplete — not a product peer of Metal/D3D12. Shaders already cook: build.akari.vulkan.json → SPIR-V via Akari

macOS products ship with LSMinimumSystemVersion 26.0 (Kaji packaging default). Akari compiles MSL with -std=metal4.0 and -mmacosx-version-min=26.0. Device creation requires MTLGPUFamilyMetal4; the live submit path is MTL4CommandQueue / reusable MTL4CommandBuffer with per-slot allocators (one begun buffer per allocator — reset only after that slot's queue marker completes), queue-attached residency sets (persistent/scene/frame), reused argument tables, a texture-view pool for mip views, MTL4Compiler PSOs, counter-heap pass timing, and shared-event ring wait (no classic command buffers). Metal 4 compute encoders unify blit and dispatch: when a dispatch consumes a preceding copy, keep both commands in one encoder and encode an explicit device-visible MTLStageBlit → MTLStageDispatch barrier. Do not encode intrapass barriers that wait on fragment/tile (illegal on Apple silicon).

Windows D3D12 Agility SDK

Windows products ship the DirectX 12 Agility SDK redistributable so the app is not limited to the OS-inbox D3D12 feature set.

PieceLocation
Vendored pin3rd-party/d3d12-agility/ (VERSION, D3D12SDKVersion, headers, bin/x64/*.dll)
EXE exportssrc/hikari/src/native_frontends/Windows/main.cpp — D3D12SDKVersion + D3D12SDKPath=.\D3D12\
Staged runtimebin/game/D3D12/D3D12Core.dll (+ d3d12SDKLayers.dll) via Kaji packaging

macOS / Metal never references this tree. Update steps: 3rd-party/d3d12-agility/README.md.

Windows DPI

The process must be PerMonitorV2. The PE manifest src/hikari/src/native_frontends/Windows/app.manifest (embedded by Kaji's Windows frontend link) plus an early SetProcessDpiAwarenessContext fallback in main.cpp are both required. Without them Windows OS-upscales a 96-DPI surface and the whole UI looks soft.

Client rect, swapchain, and session.resize sizes are physical pixels. WM_SIZE often arrives before device init — runtime_session/graphics_ops.resize must still push size into the renderer so D3D12 does not create a logical-sized swapchain that DXGI stretches. Device create uses HWND GetClientRect and syncs render_target_options from the real buffer size.

Linux

Linux has early device/callback scaffolding under src/hikari/src/platform/Linux/. It does not run the shared render-graph / renderer_core path used on macOS and Windows, and the static native backend does not create a Linux renderer. Treat module names such as vulkan as scaffolding until that path is brought up.

The shader side is not scaffolding: Akari emits Vulkan-dialect HLSL and compiles it to SPIR-V with the vendored DXC (build.akari.vulkan.json, linux-vulkan in the asset daemon, Kaji and Shinra), one .spv per stage. A Linux renderer must mirror the descriptor-set contract in akari_emit_common::vulkan (sets 0–3: buffers, textures, samplers, renderer-global bindless + geometry sampler catalog); see Shader authoring.

The target distinction matters: a platform directory or RHI vocabulary does not mean a product feature is ready on every host.

Layering

Shared cross-platform logic stays in src/hikari/src/graphics/, scene/, physics/, input/, scripting/, and other common engine folders. API-specific Zig implementation lives under src/hikari/src/platform/<platform>/; native Objective-C++/C++ implementation lives under src/hikari/src/native/<platform>/.

Keep proprietary console SDK headers, constants, handles, and conditionals entirely in their platform driver. Shared engine and graph code must compile without a console SDK installed.

Well-known directories

src/hikari/src/platform/paths.zig resolves OS directories via paths.get(.kind, buffer) / paths.getAlloc:

KindWindowsmacOSLinux
temp%TEMP% / %TMP%$TMPDIR → /tmp$TMPDIR → /tmp
persistent%LOCALAPPDATA%~/Library/Application Support$XDG_DATA_HOME → ~/.local/share

Add a Kind and one table entry per OS to extend. Callers append product/subpaths; the API does not create directories. Project-scoped state stays under <project>/.engine/, not these roots.

Secure storage

src/hikari/src/platform/secure_storage.zig stores opaque byte secrets via the OS vault:

OpZigBackend
Upsertsecure_storage.set(key, value)macOS Keychain generic password (com.hikari.secure_storage / account = key); Windows Credential Manager (Hikari/<key>, CRED_TYPE_GENERIC)
Readsecure_storage.get(key, buffer)Same; returned slice aliases buffer
Removesecure_storage.delete(key)Same

Limits: max_key_bytes (256), max_value_bytes (2560 — Windows credential blob floor). Keys must be non-empty and NUL-free. Errors: NotFound, Overflow, InvalidKey, Unavailable. Native bodies: native/macOS/src/SecureStorage.m, native/Windows/src/SecureStorage.cpp. Linux is unsupported (Unavailable).

First consumer: editor MCP Bearer token (editor/agent/project_state.zig) — key hikari.mcp.<sha256(project_id)[0..16] hex>; never written to .engine/user/mcp.json. The durable project UUID keeps the vault identity stable across project moves and renames.

Frontend responsibilities

The macOS and Windows native frontends are intentionally small. They own process entry and event loop integration and communicate over hikari_frontend.h. They do not own world simulation policy, scene loading, graphics orchestration, or game logic.

Editor argv

hikari-editor (macOS main.mm, Windows main.cpp):

text
hikari-editor [--project=<directory|hikari.project.json>] [--log=<path>]

Flag form only — bare positional project paths are rejected. Omit --project= to boot the project selector. --log= sets HIKARI_LOG_FILE before application create (redirects the engine NDJSON log file; editor default is <project>/.engine/logs/editor-latest.json). Kaji --run already forwards --project=.

Game argv

hikari accepts optional --log=<path> the same way. Packaged game data paths remain next to the executable (not via argv).

Frame pacing

Standalone games take present policy from configs/game.json → window.vsync (default on) and window.frame_rate (default unlimited = display-driven with VSync, uncapped without). They never inherit editor host pacing. Editor host policy is configs/editor.json → frame_pacing (defaults: 30 Hz idle Edit, 60 Hz interaction/Play; halved on battery, lower under OS battery saver; every row also offers unlimited), always via Window.setFrameRate (do not issue per-frame native pacing policy calls). Play-in-editor uses the editor table, never the game window. Finite rates stay integer divisors of 60 so CAFrameRateRange can honour them; unlimited is setFrameRate(0,0,0) / CAFrameRateRangeDefault. Unclassifiable power-source reads as AC — never throttle on a guess.

macOS applies the preferred range to CADisplayLink. The OS paces the tick; there is no CPU limiter on that host.

Standalone App also requires a render admission ticket before input sampling, simulation, HUD construction, or publication. The fixed capacity is two outstanding frames, including the CPU reservation: one may execute on the render thread while another is prepared or pending. A pending packet cannot be replaced. Dequeue opens the packet mailbox but retains its ticket; completion releases the ticket only after renderFrame returns, including backend frame-slot waits. This bounds CPU production under GPU pressure while preserving overlap. It does not mean GPU execution or display scanout has finished; the backend GPU ring remains independent.

When admission is deferred, native event processing continues and main-thread residency/retirement maintenance is serviced at most once per 16 ms. No gameplay tick, input consumption, UI construction, publication, or simulation-clock advance occurs on that path. Scene transitions, readiness callbacks, and operations that need a render fence stay on admitted frames; asynchronous workers continue independently. Existing residency budgets still apply. Keyboard and mouse press/release edges are latched until the next accepted input frame, including taps that end between frames; repeated edges coalesce, while gamepads retain their existing sampled behavior. The next admitted tick uses elapsed time through the normal session clock and existing fixed-step limits.

Mailbox availability and frame completion wake a waiting producer through the native main-thread service mechanism; neither invokes game code on the render thread. macOS retries an already deferred display request. Windows waits for messages or a bounded maintenance timeout. Suspend cancels an unsubmitted reservation; render-thread stop invalidates outstanding ticket generations so stale completions cannot release resumed work. Editor frames retain their independent pacing and packet coalescing policy.

There is no new author setting for admission depth. Existing game VSync and frame-rate settings remain exposed in the editor's Project Settings window. RenderThread.pacingStats() provides admitted/deferred/completed/cancelled counts, outstanding tickets, and replaced packets for diagnostics; standalone replacements should remain zero. hi.render().fps() continues to measure primary render-thread delivery, independently of host tick attempts.

Windows stores min/max/preferred on the native app and checks its accumulated deadline before admitting a tick. Longer waits remain message-aware; hikari_precise_wait_until_ns handles the final short interval. Two rules the loop depends on:

  1. The limiter returns “unlimited” whenever a blocking vsync Present already paces the frame — two pacers in series beat against each other.
  2. The deadline advances by one period per admitted frame, rather than restarting after work. A delay exceeding two periods rebases it to avoid an unbounded catch-up burst. Admission deferral consumes no deadline.

Measure with hikari_monotonic_now_ns (QPC). Never GetTickCount64: its ~15.6 ms step is not sharpened by timeBeginPeriod(1), and a gate comparing it against a 16 ms period pins the editor near 40 FPS. timeBeginPeriod(1) itself is focus-gated.

Graphics parity

Shared rendering changes generally require Metal and D3D12 updates. Keep pipeline behavior and UI's one-draw-call contract aligned. Where a capability is genuinely not available, report it from the RHI capability set and retain a graceful shared policy instead of exposing native resources to game code.

PreviousFrontends and driversNext Session services and cross-scene state

Documentation follows the current engine checkout.

Snapshot cc148c75Source docs/systems/platforms.md
On this pageCurrent supportWindows D3D12 Agility SDKWindows DPILinuxLayeringWell-known directoriesSecure storageFrontend responsibilitiesEditor argvGame argvFrame pacingGraphics parity Back to top