# Ownership

> Let players write the values they control, such as a cursor or a dragged card, while the room decides what everyone sees.

Every entity has an **owner**: the room, or a player. The room owns
everything unless it gives an entity to a player, and the owner of an
entity may write the fields its kind declares **writable** straight from
the client, with no round trip. The room still checks each write and
always has the last word.

Use ownership for values a player sets directly: a cursor, aim, a camera,
a piece being dragged, per-player settings. Movement the room simulates
stays room-owned and goes through [prediction](/docs/guides/prediction).
Scores, hit results and anything else that decides the game stay
room-owned too.

## Declaring writable fields

```ts
// schema.ts
import { defineSchema, entity } from "@irt/room";

export const Cursor = entity("cursor", {
  x: "f32",
  y: "f32",
  color: "u8",
}, { writable: ["x", "y"] });

export const Settings = entity("settings", { volume: "u8", coins: "u32" }, { writable: ["volume"] });

export const schema = defineSchema({ private: Settings, entities: [Cursor] });
```

- Only the listed fields can be written by the owner. The rest are the
  room's, even on an entity a player owns, so `color` above is set by the
  room alone.
- Any kind that isn't [hidden](/docs/guides/state-sync#hidden-entities)
  can be given to a player, with or without writable fields. The owner
  of a kind with none can't write it, but the entity is always in its
  view, it's listed by `ownedBy` and `mine`, and its owner-only fields
  reach it.
- `ownerOnly` lists fields only the owner's connections are sent; every
  other connection holds their defaults and gets no changes to them. A
  predicted car's velocity, which only its driver's replay reads, is one:
  `entity("car", { x: "f32", y: "f32", vx: "f32", vy: "f32" }, { ownerOnly: ["vx", "vy"] })`.
  A new owner gets them right after the ownership change; the previous
  owner keeps the values it had. They save bandwidth, and hide only
  values a player never owned. Room state saves them as any field, and
  they're part of the schema hash.
- The shared state, `room.state`, is always the room's and can't have
  writable fields.
- A player owns its own private state, so it can write the private kind's
  writable fields.
- Writable fields are part of the schema hash: a client built with
  different writable fields fails to connect with 4004.

## In the room

```ts
// room.ts
import { defineRoom } from "@irt/room";
import { Cursor, schema } from "./schema.js";

export default defineRoom({
  schema,
  onJoin(room, conn) {
    if ([...room.entities.ownedBy(conn.player, Cursor)].length > 0) return;
    room.entities.create(Cursor, { color: conn.player.length % 8 }, { owner: conn.player });
  },
  onWrite(room, conn, entity, next) {
    if (entity.kind !== Cursor) return next;
    const x = next.x as number | undefined;
    if (x !== undefined && (x < 0 || x > 800)) return { ...next, x: Math.min(Math.max(x, 0), 800) };
    return next;
  },
});
```

- `create(kind, values, { owner })` gives a new entity to a player.
  `room.entities.setOwner(entity, player)` moves one, and
  `setOwner(entity, undefined)` gives it back to the room.
  `ownerOf(entity)` returns the player, or `undefined` for the room, and
  `ownedBy(player, kind)` iterates what a player owns.
- Owners are player IDs, not connections. Ownership survives reconnects
  and is saved in room state, so it survives hibernation. Leaving
  releases nothing; call `setOwner` in `onLeave` if the game needs that.
- The room may write any field of any entity, including fields a player
  owns.

### onWrite

`onWrite(room, connection, entity, next)` runs for each owner write at
the start of the tick, before the stepper. `next` holds the fields
written. Return:

| Return | Applies |
|---|---|
| `next` | the write as sent |
| other values | those values, such as `next` clamped to the board |
| `undefined` | nothing; the write is rejected |

`onWrite` must be synchronous. If it throws, the error is logged and the
write is rejected. Without `onWrite`, writes apply as sent: a writable
field is then a claim the room accepts from the player.

Before `onWrite` runs, the room drops writes to entities the player
doesn't own, to fields that aren't writable, and values of the wrong
type.

## On the client

```ts
// client.ts
import { connect } from "@irt/client";
import { Cursor, schema } from "./schema.js";

const room = await connect({ router: "wss://eu.irt.io", project: "my-game", room: "board-1", schema });

addEventListener("pointermove", (e) => {
  for (const cursor of room.entities.mine(Cursor)) {
    cursor.x = e.clientX;
    cursor.y = e.clientY;
  }
});

room.private.volume = 5;

room.entities.onCorrect(Cursor, (cursor, fields) => console.log("the room moved", cursor.id, fields));
room.entities.onOwner(Cursor, (cursor) => console.log(cursor.id, "is now", room.entities.ownerOf(cursor) ?? "the room's"));
```

- `room.entities.mine(kind)` iterates the entities of a kind the local
  player owns. Setting a writable field changes the client's copy at
  once; writing any other field, or an entity the player doesn't own,
  throws a `TypeError`.
- The client sends its writes at most once per room tick. Within that
  time the last write to a field wins.
- Clients can't create or delete entities, or change an owner. Send the
  room a message and let it decide.

## Corrections

The room wins. A client keeps its own values for the writable fields it
owns and ignores the room's ordinary changes to them. When the room's
value differs from what the owner wrote, because `onWrite` clamped or
rejected it or room code wrote the field, the owner gets a
**correction** with the room's value:

- It overwrites the client's copy and drops the client's unsent writes to
  those fields.
- `onChange` runs for the fields, then `onCorrect`, where a game can snap
  or smooth.
- Every other client only ever sees the room's value.

A correction arrives with the rest of its tick's state update. On a
reconnect or a change of generation, unsent writes are dropped and the
full state update brings the room's values.

## Interest

A player always sees the entities it owns, whatever the room's interest
says. They count toward the cap on entities a connection sees.

## Common mistakes

- **Making fields the stepper writes writable.** The room overwrites the
  owner's value every tick and sends a correction each time. Keep
  simulated movement room-owned and use prediction.
- **Trusting writable fields without `onWrite`.** A modified client can
  write any value of the field's type. Bound what's possible, such as a
  speed or the board's edges.
- **Writing in `onEnter` before the owner op arrives.** An entity given
  to a player mid-game becomes writable when its owner change is applied;
  use `onOwner` or `mine` rather than assuming.
