# Choosing component storage

Use **attachable** components for ordinary gameplay, **embedded** components for
permanent actor state, and **swarms** for large populations that can operate
without full actor services.

Storage is chosen per component, not per actor. One `defineActor` can combine
attachable and embedded components. `defineSwarm` is a separate population API:
its rows cannot go in an actor's `.components` tuple.
The choice belongs to the component declaration: the same component type does
not switch between attachable and embedded storage on different actors.

## Compare the three choices

| | Attachable | Embedded | Swarm |
|---|---|---|---|
| Declaration | `defineComponent` (default) | `defineComponent` with `.storage = .embedded` | `defineSwarm` |
| Data layout | Dense array of whole component values; actor stores a row index | Component value inside the actor payload | Separate array for each data field (structure of arrays / SoA) |
| Identity | Belongs to an actor with `ActorRef` | Belongs to an actor with `ActorRef` | Population row with `SwarmRef` |
| Membership | Add/remove individual components at runtime | Fixed by the actor definition | Fixed row schema; spawn/retire rows |
| Capacity | Grows as needed; growth can allocate | Follows actor population and payload size | Maximum capacity reserved when the store is created |
| Ordinary actor hooks | Yes | Yes | No; bulk `integrate` or per-row `update` |
| Scene and inspector fields | Yes, for supported authored fields | Yes, for supported authored fields | No individual scene actor or actor inspector |
| Component replication option | Supported for eligible fields | Unsupported | Unsupported |
| Strongest fit | Flexible, reusable gameplay components | Small, permanent actor-specific logic/state | Uniform simulation over many simple rows |

Both actor storage kinds allow mutable data, including transient `Data.state`.
Fixed membership does not make embedded values immutable. A swarm's fixed
capacity does not make its population size fixed.

## Attachable: the general-purpose default

Choose attachable components when different actors share the same behavior, or
when an individual actor may gain or lose that behavior. Health, inventory,
status effects, interaction, and optional abilities are typical examples.

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

pub const Health = hi.defineComponent(.{
    .name = "health",
    .data = struct {
        hp: u32 = 100,
    },
});

pub const Player = hi.defineActor(.{
    .archetype = "player",
    .components = .{Health},
});

// The target must currently lack Health.
pub fn giveHealth(target: hi.ActorRef) !void {
    try target.component(Health).add();
}
```

`.storage = .attachable` is optional because it is the default. Exported content
discovery registers the actor and component. Components used only for runtime
attachment must also be registered with the game/module.

**Strengths:** component values are packed together by type, and membership can
change without resizing the actor's fixed payload. Empty `Data` is also valid:
a component that only supplies hooks still participates in dispatch and
add/remove operations.

**Limitations:** resolving an actor's data needs a row lookup. Structural changes
can move rows, so pointers are borrowed only for the current callback. Declared
components have indexed payload slots; components attached to an instance outside
its declared tuple use a side-list lookup. Declare commonly expected components
in the actor tuple even when they must remain removable.

Add/remove requests inside callbacks commit at a later structural boundary.
Adding and then immediately reading or setting the new component in the same
callback does not see the queued instance. Use defaults or `awake` for its initial
data. See [runtime membership](/docs/guides/gameplay-api.md#runtime-component-membership) for
dependencies, errors, and activation order.

## Embedded: permanent actor state

Choose embedded components for compact logic or state that every instance of an
actor type always needs: a door controller, puzzle state, or a permanent motion
controller.

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

pub const Spin = hi.defineComponent(.{
    .name = "spin",
    .storage = .embedded,
    .data = struct {
        degrees_per_second: f32 = 45,
        state: struct { yaw: f32 = 0 } = .{},

        pub fn update(self: *@This(), actor: hi.ActorContext, tick: *const hi.TickContext) void {
            self.state.yaw = @mod(self.state.yaw + self.degrees_per_second * tick.dt, 360);
            actor.setRotationEuler(.{ 0, self.state.yaw, 0 });
        }
    },
});

pub const Spinner = hi.defineActor(.{
    .archetype = "spinner",
    .components = .{Spin},
});
```

This rotates the actor's transform. Add and configure a render component if the
actor should be visible. `degrees_per_second` is authored; `state.yaw` is runtime
state, excluded from scene output and the inspector.

**Strengths:** data lives directly in the actor payload, so access avoids the
separate component-row lookup. Embedded components use the same ordinary hook
signatures and supported authored field schemas as attachable components.

**Limitations:** the component cannot be added or removed from an individual
actor, and the component replication option is unavailable. Large embedded data
enlarges every instance's payload. Reading one embedded field across many actors
does not provide the contiguous field array a swarm does. Use measurement before
concluding that embedding makes an entire workload faster.

Membership is fixed because the actor's generated payload has compile-time sizes
and offsets, with no nullable row slot for embedded data. Removing or inserting
embedded values would change that layout and invalidate compiled sibling access.
Use an attachable type when presence must vary; use fields on an embedded type to
represent states such as idle/running. Typed add/remove is a compile error;
name-based add/remove returns catchable `error.NotAttachableStorage`.

An actor can mix the two kinds: `.components = .{Spin, Health}` permanently embeds
`Spin` while keeping `Health` removable. Both can access sibling data through
callback-scoped `actor.get(C)` / `actor.require(C)`.

## Swarm: bulk population simulation

Choose a swarm when many instances share one data shape and do not need actor
hierarchy, component attachments, individual actor scripts, or actor events.
[Brain execution for swarm populations](/docs/guides/swarm-ai.md) supplies shared programs, compact execution and budgeted sensing.
Examples include background crowds, boids, and simple projectiles whose movement
and collision policy you implement in population logic.

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

pub const Motes = hi.defineSwarm(.{
    .name = "motes",
    .capacity = 10_000,
    .data = struct {
        x: f32 = 0,
        speed: f32 = 2,
        remaining: f32 = 3,

        pub fn integrate(store: *hi.SwarmStore(@This()), dt: f32) void {
            const x = store.column("x");
            const speed = store.columnConst("speed");
            const remaining = store.column("remaining");
            for (store.liveIndices()) |index| {
                x[index] += speed[index] * dt;
                remaining[index] -= dt;
                if (remaining[index] <= 0) store.retireAt(index);
            }
        }
    },
});

// Call after the population has been registered in the active World.
pub fn emitMote() !void {
    _ = hi.swarm(Motes).spawn(.{ .x = 1, .speed = 4 }) orelse
        return error.PopulationFull;
}
```

The engine runs `integrate` and then drains retirements. Retiring a row during
integration keeps the current iteration valid; that slot becomes reusable after
the drain. Duplicate retirement requests are coalesced, and retirement uses
capacity reserved at construction. Keep generation-checked `SwarmRef` handles
when referring to rows across ticks.

**Strengths:** field arrays let bulk logic touch only the fields it needs.
`liveIndices()` visits the live population even when most capacity is unused.
Rows avoid the full actor object and component dispatch machinery. This example
allocates no memory during integration or retirement.

**Limitations:** capacity is required, nonzero, and bounded by the `u32` index
range. Unused capacity still consumes memory for field arrays and bookkeeping.
`spawn` returns `null` when full. Rows must be non-owning values; owning
resources and `deinit` belong in actor components. Borrowed strings and references
must outlive their use. A swarm is not a drop-in replacement for actors with
physics, animation, hierarchy, or messages. Rendering needs an explicit visual
binding; the simulation above does not draw anything.

The schema is fixed because each field has a population-wide column, and bulk
kernels compile against those columns. A per-row optional component would require
another layout and dispatch model, losing the uniform field traversal this API
is designed for. Mutate existing fields, use a flag for an optional state, or
spawn into a separately defined population with the other schema. This is an
explicit application operation, not an automatic migration preserving a
`SwarmRef`. Use actors when individual objects need dynamic capabilities.

`SwarmRef` cannot be passed where `ActorRef` is required. Typed actor component
access rejects swarm definitions at compile time, not as a catchable error.
Passing a registered swarm name to name-based actor membership is rejected as
`NotAttachableStorage`; an unregistered name returns `UnknownComponent`.

Use bulk `integrate(store, dt)` for field-oriented work. The alternative is
`update(self, hi.swarm_store.SwarmEntry(hi.SwarmStore(@This())), dt)`, which gathers a row into
a temporary value and writes it back. Choose one hook, not both. Column slices
cover capacity, including dead and uninitialized slots: iterate `liveIndices()`
unless a full-capacity traversal explicitly checks liveness. Let the engine drain
retirements after callbacks; do not drain while iterating borrowed live indices.

## Decide by the required behavior

| Requirement | Choice |
|---|---|
| A reusable gameplay component, with no special constraints | Attachable |
| Add/remove a capability on individual actors | Attachable |
| Use the SDK's component replication option | Attachable |
| Permanent, small actor state with frequent direct access | Embedded |
| A large uniform population that can avoid actor services | Swarm |
| Thousands of objects that still need full actor services | Actor components; population size alone does not justify a swarm |

Storage and scheduling are separate choices. `.parallel = true` selects parallel
tick hooks for ordinary components; it is not a fourth storage kind and changes
the available callback API. Neither attachable nor embedded pointers may escape
a callback. See [Gameplay API](/docs/guides/gameplay-api.md) and
[Component lifecycle](/docs/guides/component-lifecycle.md) for those contracts.

The `hikari-games/example/scenes/runtime_components.README.md`
keeps an embedded controller while adding/removing user and system attachments.
It also demonstrates a catchable name-based rejection without stopping the actor.

### Inspecting runtime changes

Play's inspector stays read-only but reads live values for both embedded and
attachable components. Attaching a user component exposes its generated public
field schema immediately; runtime assignments in its callbacks update the shown
values. Built-in additions behave the same way. Added components carry a
**Runtime** pill, and removal drops the section. Hidden fields and `state` stay hidden. Transient fields are not serialized;
`read_only` metadata can expose them as live diagnostics. Embedded membership remains fixed even though its public values are live;
swarms have no actor inspector. See [live actor inspection](/docs/systems/ui-and-editor.md#live-actor-inspection-during-play)
for notification costs and the future Play-only edit boundary.
