# Game-facing refs

Game-facing identity handles. **The only way game code (Zig / Kawa) references things.**

| Type | Scheme | Use for | Not for |
|------|--------|---------|---------|
| **`AssetRef`** | `asset://…` stem + **`AssetKind`** | Cooked assets: texture, model, material, model_doc, audio, collision, script, shader, font, sprite_atlas, prefab, particle, animation_graph, ui_document, localization | JSON tables, OS paths, cooked suffixes, skybox (use a `.texture` ref) |
| **`ContentRef`** | `content://` (dirs allowlist, **AssetStore only**) · `writable://` / `project://` (FS) | Tables, configs, saves | Scenes, cooked assets |
| **`SceneRef`** | relative stem `scenes/foo` (runtime: cooked `.shinscene` only) | Level load via AssetStore | Content tables, free-form content |
| **`PrefabRef`** | relative stem `prefabs/foo` (runtime: cooked `.shinprefab` only) | Prefab retain / spawn | Scenes, loose content |
| **`ActorRef`** | generation handle (`EntityId` alias) | Live actors (find / send / bind) | Strings as “entities” |
| **`VoiceHandle`** | generation-checked slot | Live audio voice from `hi.audio().playCue` / `crossfade` | Cue paths (use `AssetRef`) |

> [!NOTE]
> **`AssetRef` paths are extension-free.** Disk names (`.shinmodel`, `.shintexture`, …) belong to packaging. `AssetStore` / Shinra maps `AssetKind` to the on-disk suffix; game code never writes cooked extensions.

**URI forms** (full rules: [Assets and Shinra — Asset URIs and packs](/docs/systems/assets-and-shinra.md#asset-uris-and-packs)):

| Form | Meaning |
|------|---------|
| `asset://<pack>/<logical>` | Explicit pack; scene loads retain it through their derived asset dependency closure |
| `asset://./<logical>` | Unscoped absolute logical path; product catalog maps path → pack; AssetStore acquires the owning pack automatically. **Not** FS-relative |

There is no pack-search form. Seal enforces one path → one pack.

**Path identity (engine internals):** Shinra pipeline events may still emit cooked suffixes. Compare stems with `asset_kind.stemsEqual` / `asset_deps.stemsEqual` (not bare string/`pathsEqual`). Scenes use `scene_ref.stemOf` / `withAuthoringJson` / `withCooked` for store and editor disk loads.

**Shaders** are `AssetKind.shader` but identity is a **package id** (`gbuffer_wacky`, `_engine/gbuffer`), not a file stem. Platform expand lives in `graphics/material/shader_artifact.zig` (Metal one `.metallib`; D3D12 `_vs_`/`_ps_`/`_cs_.cso`). Materials still set `shader.package`; runtime acquires expanded store paths only for the synchronous native copy/compile and then releases them. Session launch binds AssetStore; device-init must follow launch.

Not refs (plain names): archetype, input action, message name, physics preset, audio bus.

Engine-internal native atoms (`AudioHandle`, `RenderHandle`, `PhysicsHandle` in `src/hikari/src/native/handle.zig`) back subsystem tables — games use `AssetRef` for cue paths and `VoiceHandle` for live voices.

| API surface | Type |
|-------------|------|
| `RenderUpdate.mesh` / `.material` / `.albedo` | `AssetRef` (kinds `.model` / `.material` / `.texture`) |
| `setSkyboxTexture` / `attachSessionScript` / `defineActor(.{ .script = … })` | `AssetRef` (skybox = `.texture`; script = `.script`) |
| `paths.read` / `json.parseRef` | `ContentRef` (`content://` first segment ∈ project `content_dirs`) |
| `requestSceneLoad` (replace / additive) | `SceneRef` |
| `retainPrefab` / `spawnPrefab` | `PrefabRef` |
| `find` / `findBySceneId` / `findByName` / raycast hit / spawn return / lifecycle `actor.id` | `ActorRef` |
| `hi.audio().playCue` / `crossfade` return | `VoiceHandle` |
| Procedural `setMesh(vertices)` | **no asset** (CPU verts only) |

Dead / invalid `ActorRef` → host **no-ops** (never crash). Bad `AssetRef` / `ContentRef` → validate fail / soft no-op.

Implementation: `sdk/src/asset_ref.zig`, `content_ref.zig`, `scene_ref.zig`, `prefab_ref.zig`, `actor_ref.zig`. Kind + suffix table: `sdk/src/asset_kind.zig` (engine `assets/asset_kind.zig` re-exports). Host: `host_api.zig` + `game_api/host_bind.zig`.

## Zig

```zig
const tex = hi.AssetRef.must(.texture, "asset://./textures/pbr_capsule_albedo");
const mat = hi.AssetRef.must(.material, "asset://./materials/default");
const cfg = hi.ContentRef.must("content://resources/ref_demo.json");

var parsed = try hi.json.parseRef(Config, allocator, cfg);
defer parsed.deinit();

const target = hi.world().find("target_zig");
if (!hi.world().isAlive(target)) return;

try hi.render().applyRender(target, .{
    .mesh = hi.AssetRef.must(.model, "asset://./models/cube"), // optional
    .material = mat,
    .albedo = tex,
    .notify_dirty = true,
    .release_primitive = true,
});

hi.world().send(target, ping);
hi.world().send(hi.ActorRef.invalid, ping); // no-op

const load = hi.world().requestSceneLoad(hi.SceneRef.must("scenes/refs_demo"), .{}) catch return;
_ = hi.world().releaseSceneLoad(load);
```

- Literals: `AssetRef.must(kind, uri)` / `ContentRef.must` / `SceneRef.must` (comptime non-empty; scene/asset cooked suffixes are compile errors).
- Dynamic strings: `tryFrom` then host validate.
- `EntityId` is an alias of `ActorRef`.

## Kawa

```text
World.find(id) / Actors.find(id)        → ActorRef | nil
Actor.is_alive / Actors.is_alive / send → safe on dead
Actors.has / get_number / set_number    → component fields by name (dead → false/nil)
Content.read("content://…")            → string | nil   (content_dirs + AssetStore; no FS fallback)
Render.set_material(actor, mat [, alb])→ asset:// stems only (no .shin*)
Scene.set_skybox_texture("asset://…")  → asset:// stem (primary)
Scene.set_skybox_texture_secondary(…)  → asset:// stem (secondary)
Scene.set_skybox_blend(t)              → dual cubemap mix [0,1]
Scene.load("scenes/foo")               → SceneRef stem (not content://; not .json)
```

`ActorRef` is weak (holding a ref never keeps an actor alive). Prefer `Actors.*` / `hi.actors` for new find/send/field access; full guide: [Actor communication](/docs/systems/entity-messaging.md).

## Demo

**`scenes/refs_demo`** (Browse scenes → Tutorials → References; on disk `refs_demo.json` / `.shinscene`):

| Actor | Role |
|-------|------|
| `target_zig` / `target_kawa` | `ref_target` — flash on `ping` |
| `driver_zig` | ContentRef read → AssetRef material+albedo → ActorRef message |
| `driver_kawa` | `scripts/ref_demo` — same |

Data: `resources/ref_demo.json`, `resources/ref_demo_note.txt`.
