# Tutorial: component properties and the inspector

Supported logic-component fields become inspector properties automatically. For the default attachable storage, the scene saves them under `components.<component_name>`. `metadata.properties` is optional presentation and policy metadata; use it to rename, constrain, hide, order, or choose a widget for a field. Edit in the inspector in Edit mode; values seed the live actor on Play / scene load.

Deep reference: [Scenes and gameplay](/docs/systems/scenes-and-gameplay.md). Sample: `cube_entity.zig` (`health` / `max_health` + transient `current_health`).

## 1. Declare fields + metadata

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

const BeaconLogic = hi.defineComponent(.{
    .name = "beacon_logic",
    .data = struct {
        spin_speed: f32 = 1.0,
        label_color: [3]f32 = .{ 1, 1, 1 },
        /// Not serialized / not shown in the inspector.
        runtime_angle: f32 = 0,

        pub const metadata = .{
            .properties = .{
                .spin_speed = hi.UserDataPropertyOptions{ .label = "Spin Speed", .min = 0, .step = 0.05 },
                .label_color = hi.UserDataPropertyOptions{ .label = "Color", .min = 0, .max = 1, .step = 0.01 },
                .runtime_angle = hi.UserDataPropertyOptions{ .transient = true },
            },
        };

        pub fn onChanged(self: *@This(), actor: hi.ActorContext, property: []const u8) void {
            const id = actor.id;
            _ = .{ self, id, property };
            // React to inspector edits while live (Edit preview / Play).
        }

        pub fn update(self: *@This(), _: hi.ActorContext, ctx: *const hi.TickContext) void {
            self.runtime_angle += self.spin_speed * ctx.dt;
        }
    },
});

pub const BeaconEntity = hi.defineActor(.{
    .archetype = "beacon",
    .components = .{BeaconLogic},
});
```

| Option | Effect |
|---|---|
| `.label`, `.tooltip`, `.min`, `.max`, `.step`, `.order` | Inspector presentation and ordering |
| `.hidden = true` | Omitted from the authoring surface; typed gameplay code still owns the field |
| `.transient = true` | Not authored in scene JSON; skipped by `applyJson` |
| `metadata = .{ .runtime_only = true }` | **No** editor property schema; scene `user_data` may still seed JSON-safe fields |

Embedded components may use the actor’s root `user_data` for their authored fields. Use [Component storage](/docs/guides/component-storage.md) when deciding between storage policies.

`runtime_only` means “no inspector sheet”, not “cannot place in a scene” and not “scene cannot seed”. JSON-safe POD fields (bools, numbers, vectors, nested POD structs) still load from `user_data`; pointer/slice fields and anything marked `transient` are skipped. Reflection and metadata are compile-time/static declaration work: component instances gain no property bags, maps, allocations, or per-tick reflection.

## 2. Seed from scene JSON

```json
{
  "id": "beacon_01",
  "archetype": "beacon",
  "transform": { "position": [0, 1, 0], "rotation_euler": [0, 0, 0], "scale": [1, 1, 1] },
  "components": {
    "beacon_logic": {
      "spin_speed": 2.5,
      "label_color": [0.2, 0.8, 1.0]
    }
  }
}
```

Omit keys to keep struct defaults. Multiple actors can share an archetype with different component values (see demo `disco_light_*`).

## 3. Verify in the editor

1. Rebuild, open the scene, select the actor.
2. Inspector shows **Spin Speed** / **Color**; change a value — `onChanged` runs when live.
3. Save the scene and confirm `components.beacon_logic` persisted (non-transient fields only).

## Next

- [First motion](/docs/tutorials/first-motion.md) — tween those fields
- [First mesh and material](/docs/tutorials/first-mesh-material.md)
