# Costumes, combat feedback and interactive readouts

An experience can temporarily dress a player using native artwork in that room (visible placements or hidden stock).

An experience can temporarily dress a player using native artwork in that room
(visible placements or hidden stock). Use a placement id, an asset's name, or its
thing table. Keep the costume assets in every room that uses them.

`costume(player, {character="Ninja", body="White Belt", hand="Katana", companion="Fox"})`
sets the visual layer. Slots are `character`, `head`, `body`, `legs`, `hand` and
`companion`. `costume(player, nil)` removes it. Each asset keeps its placement's
filter and selected version. Matching wearable and character grids compose frame
for frame, exactly as in the wardrobe. The player's own hat stays unless the
creator supplies `head`; other outfit slots come from the costume.

The player's inventory and real equipped items never change. **Hide costume**
restores their own appearance and suppresses scripted visual effects and command
animations; gameplay continues. Keep earned rank and equipped game items in saved
script data, so cosmetic visibility never becomes an access check. Leaving the
experience restores the real wardrobe automatically. A `companion` is a small
following native character asset, visible while the costume is shown.

`effect(player, spec)` broadcasts short feedback near a thing or player:

- `kind`: `hit`, `burst` or `rise`.
- `style`: `plain` (default) or `comic`, with outlined lettering and a jagged burst.
- `target`: optional visible thing/player id or table; defaults to the actor.
- `animation`: optional command clip on the character, such as `strike`.
- `text`: optional floating text, at most 40 characters.
- `color`: `#rrggbb`.

Clients bound simultaneous effects and skip distant remote effects. This is
presentation only. Validate range, cooldowns, damage and rewards in the script.
An authored command turns the character toward its target and finishes before
the next command starts. Repeated effects retain only the latest pending command,
so rapid input cannot continually restart a windup or build an animation backlog.
The associated visual impact plays midway through the clip. Author anticipation,
contact and recovery around that timing. Events without a matching clip play
their effect immediately. Costume changes and departures clear pending commands.
`burst` feedback displays immediately even with an animation command, so a
finishing cue or reward cannot be lost behind queued swings. Its character
gesture still plays. A recent hit whose contact frame was skipped during loading
is delivered once; stale combat after a background-tab wakeup is discarded.

These are temporary world-space visuals, not inventory items. Text can be a word,
a computed damage number or another short value. Comic labels sit above the whole
target, including multi-tile objects, while sparks appear on its body. Clients
limit the room to 32 active effects (16 for remote effects) and two labels per
target. Nearby players can see comic hits; local feedback gets label priority.

```lua
on("click", function(p, monster)
  effect(p, {target=monster, animation="strike", kind="hit",
    style="comic", text="SLASH!", color="#ffd27b"})
  -- Or show a number computed by your damage logic:
  -- effect(p, {target=monster, kind="rise", text="-" .. tostring(damage)})
end)
```

### Animating placed things

Room scripts can call `set_state(thing, name)` to play a state authored in that
native item's Behavior. Thing scripts retain the one-argument `set_state(name)`.
A room can only change its own visible or stock things. Names are 1–24 bytes;
unknown names fall back to the item's initial state. Repeating a state restarts
it, so throttle hit reactions if the current pose should finish first.

All depth columns follow one clock. A looping state repeats; a one-shot follows
its authored `onEnd` state, or holds its final pose when there is no `onEnd`.
For example, author `hurt → idle`, `defeated` with no successor, and
`recover → idle`, then call:

```lua
local function flinch(monster) set_state(monster, "hurt") end
-- Call when your server-side HP reaches zero:
local function defeat(monster) set_state(monster, "defeated") end
-- Call later, from your respawn timer:
local function revive(monster) set_state(monster, "recover") end
```

The visual state and start time travel with the thing in snapshots and survive
hide/show. Late joiners see the current pose. This does not change collision,
inventory or HP, and it is independent of players hiding their costumes. Keep
gameplay state in Lua and time the recovery before accepting another attack.

### Interactive HUD

`panel(player, spec)` adds an interactive HUD alongside the existing panel forms:

```lua
on("ui", function(p, id)
  if id == "open_training" then
    panel(p, {
      title = "TRAINING",
      rows = {{"SPIRIT", load(p, "spirit") or 0}},
      meter = {label = "Guardian", value = 75, max = 100},
      actions = {{id = "strike", label = "STRIKE"}, {id = "shop", label = "UPGRADES"}}
    })
  end
end)
```

Up to six rows and six uniquely named buttons are allowed. Buttons fire `ui`,
like window buttons. Meter values are non-negative with a positive maximum.
HUDs replay after reconnect; touch controls retain usable tap targets.

Set `layout="combat"` for a compact bottom action dock above chat. Each action
may name an `icon`: `sword`, `forge`, `belt`, `map`, or `leaf`. Keep rows brief
(three small stats work well on a phone). With this layout, `meter.target` may
be the placement id of a visible room object, such as `guardian.id`. Its health
bar follows the complete object's lower edge while the camera moves or zooms.
Hidden objects and placements in other rooms have no label. Costume visibility
does not affect this readout. Omit combat layout to retain the original readout.

Card images resolve only to scenery or stock in the current room, never remote
URLs or another player's inventory. The client loads visible cards through the
bounded preview worker queue and preserves the artwork's filter and version.
Locked cards remain visible with their requirements. `disabled` prevents normal
clicks; your script must still check purchases and unlocks on the server.

`now()` returns server Unix time in seconds, with fractional precision. It can be
saved for cooldowns or offline crop growth; it is not the client's clock.
