Skip to content
hikari
RenderingEditorAIToolchainDocumentation
GitHub
hikari

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsLicenseBack to top
Documentation / Tutorials
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
Tutorials7 min read

Tutorial: Play, Edit, and scenes

On this page
On this pagePlay versus EditPrimary camera preview (Edit)Pause and unpossess (editor Play only)Replace scene loadAdditive layersStop semantics (editor)Both directions are budgeted, and neither blocks the clickVerifyNext Back to top

Editor mode and scene loading are the most common sources of “my script did nothing” / “Stop wiped my level.” This page covers the rules you need day one.

Deep reference: Application lifecycle, Session services, UI and editor (including optional game editor SDK for menus/panels).

Play versus Edit

EditPlay
Scene renderedYesYes
Entity update / physics / scriptsFrozenRunning
Authoring sourceSceneDocumentLive World (preview only)
Stop—Re-instantiates from document UTF-8, not disk, not the last play-loaded path

Warning

Play runs a preview world. Changes made there are not authored scene edits: Stop restores the in-memory scene document, not the Play world or the last play-loaded scene. Make changes you want to keep in Edit mode.

In-editor Quit from game UI stops Play; it does not close the editor window. Standalone games still exit the process.

Asset hot reload is deferred while Play is active.

Primary camera preview (Edit)

The camera toolbar button previews the primary camera as a complete lens: transform, projection, depth of field, shutter, vignette, aberration, and flare. Toggle it again to return to the editor fly-cam exactly where it was parked. The fly-cam itself keeps neutral optics and samples spatial Visual Zones at its own position; preview mode samples them at the primary camera. Preview never moves or otherwise mutates the camera actor.

Pause and unpossess (editor Play only)

Two independent toolbar knobs next to Play/Stop. Policy lives on the session as HostPolicy (session.hostPolicy()):

QueryMeaning
isPlayingSession is in Play
isSimPausedHost freeze — World.sim_hold / shouldAdvanceSim() == false
What freezesEntity / script / swarm / fixed ticks, physics integrate, session-script update; world.deltaTime() is 0
What keeps runningRender, UI begin, GameSubsystem.onTick (single hook), fly-cam when unpossessed
Game clocksSim → world.deltaTime() (0 while paused). Chrome → world.unscaledDeltaTime() / wall time. Never wall-clock for sim
world.timeScale()Game-owned multiplier only — does not include host pause
isViewDetachedEditor fly-cam owns presentation; primary camera stays the game camera
isPlayPending / session.isPlayPending()Play was clicked; its world copy is still building
canAuthor / isAuthoringReadOnlyDocument + inspector writes / undo when not playing and not starting

Toolbar:

ControlEffect
PauseToggles isSimPaused
Unpossess (camera)Toggles isViewDetached

You can unpossess while the sim still runs, pause while still looking through the player cam, or both. Repossess / resume are the same buttons toggled off. Stop clears both.

Dual view (unpossess): freecam is the viewport lens (image VP, TAA) — where you look from. The game primary still owns GPU frustum cull (so you can orbit and see what the player view drops), plus reaction: HostApi primaryCamera, audio listener, visual-zone look (fog/exposure), shadow cascade focus, game LOD. That is the point of unpossess as a cull/reaction debugger.

While detached, freecam navigation may temporarily capture the OS pointer, but game-facing input is scrubbed: Input.pointer_captured() is false and mouse_delta is zero for world/entity/session ticks. Action resolve is also held. That keeps scripts that key look off capture+delta from rotating the primary (and the camera gizmo) with the editor freecam.

While unpossessed, the viewport overlay is Scene-view-style over the live Play world: same show-flags as Edit (grid, cameras, lights, …), live poses each frame. Mass render-only actors stay undecorated. Inspector / document history stay observation-only while isAuthoringReadOnly.


Replace scene load

From Zig (session HUD or entity):

zig
const hi = @import("hikari_game");
const world = hi.world();

world.requestSceneLoad(hi.SceneRef.must("scenes/messaging"), .{});
world.requestSceneReload();
const snap = world.sceneLoadSnapshot();
// snap.ready_stage: none → entities → assets → gpu → all
// Reveal the level only when ready_stage == .all (assets + GPU, not just actors spawned).

From Kawa:

kawa
Scene.load("scenes/messaging");
Scene.reload();
// Scene.ready_stage() → 0 none, 1 entities, 2 assets, 3 gpu, 4 all

Requests are queued and applied on the next session tick — not mid-update. Load-queue status == ready only means actors spawned; wait for readiness all before showing the scene.

StageMeaning
none (0)No layers
entities (1)Actors spawned (on_scene_loaded)
assets (2)Soft-pending meshes/materials resolved
gpu (3)Per-layer GPU resident
all (4)World — every layer at gpu

Full rules: Session services — readiness.

The session HUD’s Tour buttons cycle eight showcases from scene_catalog.zig. Browse scenes exposes all standalone examples under Showcase tour, Tutorials, and Diagnostics. The UI showcase also has an Examples button on its title screen. Streaming cells are loaded by their tutorial and are not standalone entries.


Additive layers

zig
const hi = @import("hikari_game");
const messaging = hi.SceneRef.must("scenes/messaging");
const world = hi.world();

world.requestSceneLoad(messaging, .{ .mode = .additive });
world.requestSceneUnloadLayer(messaging.path);
const layer = world.sceneLayerReadiness(messaging.path); // terminal stage = .gpu

Kawa: Scene.load_additive("scenes/messaging"), Scene.unload_layer("scenes/messaging"), Scene.layer_ready_stage("scenes/messaging"). Kawa and the host strip optional .json / .shinscene suffixes; layer keys are extension-free stems.

Scene.load and Scene.load_additive take the same presentation policy as the Zig API, as an optional second argument ("immediate" / "gated" / "deadline" / "manual"), with deadline taking its milliseconds third. An unrecognised name refuses the load rather than falling back to immediate — a silently ignored gate looks like an engine bug, a scene that never loads points at its own call.

kawa
Scene.load_additive("scenes/menu", "manual")

fn on_layer_ready(key)          # resident and uploaded, deliberately not shown
  if key == "scenes/menu" then menu_ready = true end
end

fn on_tick(dt)
  if menu_ready and splash_done then
    Scene.present_layer("scenes/menu")   # opens the gate; fires on_layer_loaded
  end
end

Rules:

  • Layer key is the request path string.
  • Entity "id" values must be unique across all loaded layers or the load fails.
  • Unloading a layer destroys entities bound to that layer; loose spawns (.layer = .loose) remain until replace unload / termination.
  • World ready_stage == .all only when every loaded layer (base + additive) is at gpu.

Stop semantics (editor)

  1. Author scene A in Edit (unsaved edits are fine).
  2. Play, then load scene C via gameplay.
  3. Stop → renderer and tools switch to the retained authoring world for scene A. Unsaved A survives; play-only C does not become the document.

Both directions are budgeted, and neither blocks the click

The editor keeps its authoring world intact during Play, so Play and Stop are world switches rather than scene reloads. Neither one stalls the frame it happens on:

PhaseWhat happens
Click PlaySerialize the document, create the Play world + its physics backend, queue its scene job. Returns immediately
Starting…The job advances on the normal scene-load budget while the authoring world keeps rendering and taking input
CommitRenderer, physics, HostApi and input rebind to the copy; the first Play frame is that same tick
Click Stop / Quit-from-PlayStop physics/audio, fire onPlayEnded, rebind presentation to the retained authoring world before returning
After StopRetire the invisible Play world in bounded slices; no authored actors are reconstructed

There is no visible restore window at Stop: canAuthor is true again the moment it returns.

There is a visible starting window at Play, and it is deliberately visible. While the copy builds:

  • the Play button reads Starting… and clicking it again withdraws the request
  • canAuthor is false — the document Play will run was captured at the click, so an edit now would be silently missing from the session about to start
  • opening another scene or prefab withdraws the request too
zig
// Host / tooling only — games use HostPolicy, not a special API.
if (session.isPlayPending()) {
    // The world copy is still building; do not author into the document.
}

onSceneLoaded runs when the Play copy is built; Stop does not emit it for the already-live editor world. Pressing Play while a previous copy is still retiring closes that copy first rather than queueing a hidden intent.

Never author into packaged bin/.../data/scenes — use the project assets root.


Verify

  1. Edit a transform, do not save, Play, Stop — edit still present.
  2. Play → Next Scene → Stop — document scene returns, not the play destination.
  3. Additive load messaging → unload layer → pad/doors gone; session HUD still there.
  4. Large scene / many actors: Play stays responsive while Starting… — the authored viewport still renders and the fly-cam still moves. Stop returns to that viewport immediately, and cleanup continues invisibly without changing editor-world stats.
  5. Click Play then click it again before it commits — the request withdraws and the button returns to Play with nothing else changed.

Next

  • First session services
  • Tutorials index
PreviousTutorial: first messagesNext Tutorial: first runtime spawn

Documentation follows the current engine checkout.

Snapshot cc148c75Source docs/tutorials/play-edit-and-scenes.md
On this pagePlay versus EditPrimary camera preview (Edit)Pause and unpossess (editor Play only)Replace scene loadAdditive layersStop semantics (editor)Both directions are budgeted, and neither blocks the clickVerifyNext Back to top