# Tutorial: first runtime spawn

Scene JSON is not the only way to create actors. Spawn at runtime from session code or an entity when content is dynamic (pickups, VFX hosts, debug helpers).

Deep reference: [Session services — layer membership](/docs/systems/session-services.md#layer--partition-membership-runtime). Related: [First session services](/docs/tutorials/first-session-services.md).

## Layer membership

| API | Layer | Survives additive unload? |
|---|---|---|
| Scene-authored actor | Loading scene’s layer | No |
| `w.spawn` with `.layer = .loose` | `null` (loose) | Yes (until replace unload / destroy) |
| `w.spawn` with `.layer = .{ .layer = key }` | Named layer | No |
| `w.spawn` with `.layer = .{ .inherit = id }` | Copied from parent | Depends |

In Play, the hierarchy **Global** group lists loose entities (`layer_key == null`).

## Spawn from a descriptor

Game modules spawn through `hi.world().spawn` with a `SpawnDesc`. The host returns an `ActorRef` (`EntityId` is the same type — prefer `ActorRef`).

```zig
const hi = @import("hikari_game");

const cube_mesh = hi.AssetRef.must(.model, "asset://./models/cube");
const pickup_material = hi.AssetRef.must(.material, "asset://./materials/cube");
const pickup_script = hi.AssetRef.must(.script, "asset://./scripts/pickup");

const w = hi.world();
const id = try w.spawn(&.{
    .archetype = "cube",
    .id = "pickup_01",
    .name = "Pickup",
    .active = true,
    .transform = .{ .position = .{ 0, 1, 0 } },
    .components = .{
        .physics = .{
            .body_type = .dynamic,
            .collider_shape = .sphere,
            .collider_radius = 0.35,
            .mass = 0.5,
        },
        .render = .{
            .geometry = cube_mesh,
            .material = pickup_material,
        },
    },
    .script = .{
        .mode = .override,
        .asset = pickup_script,
        .params_json = "{\"respawn_seconds\":5}",
    },
    .user_data_json = "{\"score\":100}",
    .layer = .loose,
});
_ = id;
```

Omit `id` (or pass `""`) to let the host generate a unique scene id. Prefer stable unique ids when you will look the actor up later — additive loads reject duplicates across layers.

The request can initialize parent, active state, transform, render, physics, camera,
light, visual zone, audio, script parameters, and authored component
fields. An override is accepted only when the archetype declares that capability.
Asset references, JSON, component values, parent/layer handles, and the scene id are
validated before the entity is published.

`awake` and `start` observe the complete initial state. A failed request is rolled back;
it does not leave a partially configured entity in the world. Script mode defaults to
the archetype script; use `.none` to suppress it or `.override` to replace it.

Kawa uses the same pipeline:

```kawa
let pickup = World.spawn({
    archetype = "cube",
    id = "pickup_02",
    position = [2, 1, 0],
    user_data_json = "{\"score\":100}",
    components = {
        physics = { body_type = "dynamic", collider_shape = "sphere", collider_radius = 0.35, mass = 0.5 },
        render = { mesh = "asset://./models/cube", material = "asset://./materials/cube" }
    },
    script = { asset = "asset://./scripts/pickup", params_json = "{\"respawn_seconds\":5}" },
    layer = "loose"
});
```

Kawa returns an actor ref or `nil` when validation or spawning fails. `script: nil`
suppresses the archetype default script. `parent` and `inherit_layer` accept actor refs;
`layer` accepts `"loose"` or a loaded layer key.

Open `scenes/runtime_spawn_showcase.json` for a side-by-side executable example. The
red group is spawned by `spawn_showcase_zig_entity.zig`; the blue group is spawned by
`assets/scripts/spawn_showcase_kawa.kawa`. Each path creates a parented configured prop, a dynamic
physics sphere, a runtime light, initial component data, and a configured child script.

`scenes/runtime_spawn_showcase` inherits the scene-placed anchor’s layer (`inherit_layer` / `.layer = .{ .inherit = anchor }`); step through the scene tour on the session HUD to reach it. Use `.layer = .loose` when the actor must survive additive unload.

Keep cross-scene policy on the subsystem; do not re-place a “manager” actor in every scene JSON.

## Verify

1. Spawn a loose cube from a button or `onSceneLoaded`.
2. Additive-load then unload a layer — loose actor remains; layer actors disappear.
3. `requestSceneLoad` (replace) — loose actors are torn down with the world rebuild.

## Next

- [Play, Edit, and scenes](/docs/tutorials/play-edit-and-scenes.md)
- [First physics](/docs/tutorials/first-physics.md)
