# Tutorial: first physics body and trigger

Opt into physics with `hi.ComponentPhysics` on the archetype, then author a `physics` block under scene `"components"`. Scene-authored physics defaults to **Static** when `body_type` is omitted (or the whole block is missing on a physics-capable archetype) — set `"body_type": "Dynamic"` explicitly for props that should fall. Runtime spawn still defaults to Dynamic. Collisions reach Zig via `onCollision` and Kawa via `on_collision`.

Deep reference: [Physics](/docs/systems/physics.md). Samples: `trigger_entity.zig`, `cube_entity.zig`, `fall_sensor` in `scenes/physics_playground.json`.

## 1. Dynamic body (simple)

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

pub const BoxEntity = hi.defineActor(.{
    .archetype = "box",
    .components = .{ hi.ComponentRender, hi.ComponentPhysics },
});
```

```json
{
  "id": "box_01",
  "archetype": "box",
  "transform": { "position": [0, 2, 0], "rotation_euler": [0, 0, 0], "scale": [1, 1, 1] },
  "components": {
    "render": {
      "mesh": "asset://./models/cube",
      "material": "asset://./materials/cube"
    },
    "physics": {
      "body_type": "Dynamic",
      "collider_shape": "Box",
      "collider_box_size": [1, 1, 1],
      "mass": 1.0
    }
  }
}
```

Render scale and collider size are independent when hand-authored. To size the collider from the mesh local AABB (box / sphere / capsule, local space — transform scale is applied at body create):

```zig
_ = hi.world().fitColliderToRender(id);
// or read bounds yourself:
if (hi.render().localBounds(id)) |aabb| {
    const min_v, const max_v = aabb;
    _ = .{ min_v, max_v };
}
```

Common `body_type` values in samples: `Dynamic`, `Static`, `Trigger`.

Optional `"is_active": false` parks the body without removing the component (entity `"active": false` also disables effective physics). `"enabled"` is accepted as an alias, matching how `light` / `visual_zone` spell the same switch; `is_active` stays canonical and is what gets written back on save. Runtime: `hi.world().setPhysicsActive(id, false)`. See [Active / enable trio](/docs/systems/scenes-and-gameplay.md#active--enable-trio).

## 2. Trigger volume

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

const TriggerLogic = hi.defineComponent(.{
    .name = "trigger_logic",
    .storage = .embedded,
    .data = struct {
        pub const metadata = .{ .runtime_only = true };

        pub fn onCollision(_: *@This(), _: hi.ActorContext, info: *const hi.CollisionInfo) void {
            if (!info.other.isValid()) return;
            const other_name = hi.world().name(info.other);
            switch (info.phase) {
                .enter => std.log.info("enter {s}", .{other_name}),
                .exit => std.log.info("exit {s}", .{other_name}),
                .stay => {},
            }
        }
    },
});

pub const TriggerEntity = hi.defineActor(.{
    .archetype = "trigger",
    .components = .{ hi.ComponentPhysics, TriggerLogic },
});
```

```json
{
  "id": "fall_sensor",
  "archetype": "trigger",
  "transform": { "position": [0, 1, 0], "rotation_euler": [0, 0, 0], "scale": [1, 1, 1] },
  "components": {
    "physics": {
      "body_type": "Trigger",
      "collider_shape": "Box",
      "collider_box_size": [24, 0.5, 24]
    }
  }
}
```

Triggers do not need a mesh. This sensor sits below the box in step 1. Play with both actors in the same scene and watch the enter/exit logs as the box falls through.

## 3. Fire gameplay from a collision

Sample `message_pad_entity.zig` sends messages on first `.enter` — combine with [First messages](/docs/tutorials/first-messaging.md).

## Verify

1. Play `scenes/physics_playground` — cubes fall; `FallSensor` logs when they pass through.
2. Edit mode — no simulation (bodies frozen).
3. Stop — document transforms restore; play-time physics poses do not write back.

## Next

- Character controller → [First character controller](/docs/tutorials/first-character-controller.md)
