Dicey Table

Player Zones, Seats and Seat Templates

The Inspector for a Player Zone showing its COLOR dropdown set to Red and a LABEL text field

The eight-seat model#

DiceyTable authors exactly eight player-color seats — Red, Blue, Green, Yellow, Purple, Orange, Pink, Cyan (PLAYER_SEAT_OPTIONS, packages/shared/src/protocol.ts) — under the Hierarchy's Player Zones (N) folder. Each seat owns one seat group: a group transform (position and rotation Y), one or more translucent zone boxes, and exactly one movable name label. All of it is authored — you place it in the editor, and it lives in the scene document, not on the wire (packages/shared/src/sceneEditor.ts).

In the editor, all eight seat groups render so you can author every one of them. At the live table, a seat's zones and label render only once a player claims that seat. That is not merely cosmetic: the rules a zone enforces are built from the zones that are visible, so an unclaimed seat's zones enforce nothing at all.

Seat templates#

Authoring eight seats by hand is eight times the work, and the angles come out subtly wrong. A seat template is one layout — a name label, typed zones and card-holder definitions — authored once and applied to as many seats as you like. Neither Tabletop Simulator nor Tabletop Playground has an equivalent; in both you duplicate a group of zones per seat and rotate each copy by hand.

A scene has at most one template, and every seat is in one of two states:

State What the seat stores What it follows
Linked Its group transform (position + rotation Y), its color, and an optional per-seat label text Its zones, label geometry and card holders are the template's
Detached Everything — its own world-space zones, label and holders Nothing; the template is ignored for it

A scene with no template has every seat detached, which is exactly the behaviour that existed before templates: nothing changes for an older mod.

Scale belongs to the template, structurally#

The template carries one uniform Scale. A linked seat has nowhere to store a scale — its group transform is position and rotation Y only. "Every seat matches the template" is therefore true by construction rather than by validation, and no player can end up with a bigger hand area than another. A seat that genuinely needs its own size detaches; that is what detaching is for.

Seat-local space — the one thing you can get wrong silently#

Template geometry is authored in seat-local space: the origin is the seat's group pivot, +Z points at the table centre, and units are world units (feet). Each linked seat's group transform then places and turns that layout:

worldPosition = group.position + rotateY(group.rotationY) × (template.scale × localPosition)
worldRotationY = group.rotationY + localRotationY
worldSize      = template.scale × localSize

Rotations here are degrees, everywhere — the group's, the zone's and the label's.

The practical consequence: author the layout as though the seat sits at the near edge of the table looking away from you, with the hand area at small +Z and anything that belongs further into the table at larger +Z. Get the sign wrong and every seat's layout points outward, away from the table, in a way that looks deliberate until you claim a seat.

The five template actions#

All five live on the Player Zones folder's Inspector, except Detach and Link, which are on the seat itself. See Player Zones Folder for the fields.

  • Define — creates an empty template: one seat-local name label and one Hand zone.
  • Promote a seat to template… — copies one seat's zones and label into the template, converting world space to seat-local using that seat's group transform, and links that seat. It also offers to adopt the card holders standing inside that seat's zones as template holders. This is the ergonomic heart of the feature: build one player's area by hand until it looks right, then promote the lot.
  • Apply to… — links the seats you tick and materializes the template onto them. You choose between keep each seat's current group transform and re-arrange the seats evenly around the table. Because applying can create and delete real card holders, the confirmation states the object delta plainly — "creates 12 card holders, updates 4, deletes 0".
  • Detach to customise — freezes the template's current geometry onto that one seat and drops the link. Detaching is a visual no-op: nothing moves, and the seat keeps every card holder it had (they simply stop being template-owned, so no later re-apply can remove them).
  • Clear Template — detaches every seat first, then removes the template. No object is ever deleted by clearing.

Every one of them goes through the ordinary scene-draft undo.

Editing a linked seat#

Select a linked seat and its Hierarchy row shows no children — there is nothing seat-specific to select, because its layout is the template's. Its Inspector shows a TEMPLATE LINK section badged LINKED, a GROUP TRANSFORM whose Scale is disabled and badged 🔒 from template, and a LABEL TEXT override.

Dragging a zone gizmo while a linked seat is the template's preview seat edits the template: the committed world transform is converted back into seat-local space, and every linked seat moves in the same frame. That is the intended way to work — you are looking at one seat and editing all of them.

Zone types#

A zone box carries a type, and the type is what the zone does. Four types have behaviour today.

Type What it does
Hand The seat's private hand area. An entity dropped here gets that seat as its owner, feeding the hand panel and the face-down redaction rules. At most one per seat.
Area A play area or personal board. Gates who may interact with what is inside. Confers no hand membership.
Hidden Conceals its contents from anyone not entitled to the seat, regardless of face-up or face-down.
Scripting A geometry-only trigger volume. No engine rule of its own — it exists so a script can attach meaning to a region.

Five more types — Reveal, Layout, Randomize, Fog of War and Drop — parse and render but have no behaviour yet. They are in the schema so a scene authored on a newer client still loads on an older one. Authoring one gives you an inert region and enter/leave events, nothing more.

Enter and leave events fire for every type, Scripting included — see globalEvents.onZoneEnter for table scripts and onZoneEnter for mods. Scripting is therefore best read as "a region that means something to my rules and nothing to the engine", not as "the only type that raises events".

The one-hand-per-seat rule#

A seat — and the template — may define at most one Hand zone, and the limit is enforced by the schema, not just by the editor: a mod carrying two fails its publish-time parse. Setting a second zone to Hand therefore silently demotes the previous one to Area and posts a status-bar message naming it — "Zone 1 is no longer the hand zone.", using the same row label the Hierarchy shows. There is no dialog to dismiss, and the change undoes like any other.

This is a deliberate v1 simplification. Both competitors allow multiple hand zones with one marked primary, which is why the Primary option exists on the Hand type even though only one hand zone can exist to be primary today.

Options every type has#

Option Effect
Name The Hierarchy row label. An unnamed zone reads Zone 1, Zone 2, … — not its type name, so naming nothing never turns three untitled areas into three identical Area rows. The type is on the row already, as its type token.
Interaction Anyone · Owner seat only · Blocked. Enforced on Area zones only, and offered on Area zones only — see the note below.
Show boundary Everyone · Owner seat only. Who sees the zone outline.
Tag filter (APPLIES TO) Gates occupancy itself.

Tag filter is the option to reach for first. Its semantics copy Tabletop Simulator exactly: no tags matches everything; one or more tags matches an entity sharing at least one of them. An entity the filter rejects is not in the zone as far as any rule is concerned — it does not get an owner from a Hand zone, it is not concealed by a Hidden zone, it raises no enter or leave event, and it is absent from getZoneObjects. Reserved dt: platform tags are rejected as filter entries; you filter on your own tags.

Interaction is an Area option. The positional interaction gate consults Area zones and nothing else, so the Inspector offers the control on Area zones only. The schema still accepts interaction on any type — a scene published while it was offered everywhere keeps parsing, and its stored value is simply inert. To restrict who may act somewhere, draw an Area zone there; a Hand or Hidden zone will not do it for you.

Options the type adds#

Type Option Effect
Hand Primary Which hand zone receives deals and feeds the on-screen hand panel.
Hand Hide from other players On (the default), this seat's face-down cards are private to it. Off, they are public — see below.
Hand Card overlap Which neighbour a card is drawn over. Obeyed by the cards on the table and by the on-screen hand strip.
Hand New cards go to Which end a drawn or dealt card lands at while the player has their hand unsorted.
Hand Sort methods The orders a player can choose from by right-clicking their hand. See below.
Hidden Hides Everything conceals the whole entity; Card faces only neutralizes just the identity fields.

Zones are two-dimensional: a footprint on the table plane with no height. There is deliberately no height field, because the occupancy test is a point-in-rotated-rectangle check that never reads one. An entity lifted high above a zone is still inside it, and a card resting on a board above a zone counts too. Every rule that reads zones agrees on that, which is the point.

Two Hidden zones overlapping is an AND, not a race. An entity inside seat A's and seat B's Hidden zone is concealed from anyone not entitled to both — so neither A nor B can read it, and only a viewer entitled to every owner (A's teammate who is also B's, or the host) sees it. Hides resolves the same way: the strictest of the overlapping zones wins, so one Everything zone makes the whole entity vanish even where the other says Card faces only. This matters most with a seat template, whose materialized zones sit on the table perimeter at seat spacing — a generous template puts adjacent seats' zones within touching distance of each other, and an entity that drifts into the seam belongs to both.

Who owns what a zone holds#

This section used to say the opposite, and the opposite was wrong. Stating it plainly:

  • A Hand zone does confer ownership. Drop an entity inside one and the host stamps it with that seat, which is what makes it a hand card, what routes deals to it, and what feeds the face-down redaction rules.
  • An Area zone does not. It gates interaction and nothing else. This is the fix for a real bug: before typed zones, dropping a card anywhere in any of a seat's boxes set the owning seat, so a discard pile counted as a hand card. A single-zone seat behaves identically before and after; a multi-zone seat gets the behaviour it always should have had.
  • A card holder no longer confers ownership by itself either — see Hand zone or card holder? below.

There is now exactly one writer of a seat's ownership: the seat's Hand zone (plus one explicit opt-in on a holder). If an entity is not in a hand zone, it is not in anybody's hand.

Table zones — the deck slot and the discard pile#

A player zone is authored once per seat, which is right for a hand or a personal play area and wrong for everything a game has exactly one of. The Hierarchy's Table Zones (N) folder holds those: a deck location, a discard pile, a shared market row.

A table zone is the same box with the same options, with two differences:

  • It is authored once, not eight times.
  • It names its owner explicitly, because there is no seat above it to inherit one from.

Choosing an owner#

The Owner dropdown at the top of a table zone's ZONE section offers three kinds of answer:

Owner What it means
Table (no owner) Nobody owns it. The right answer for a shared deck slot or discard pile.
A seat colour That seat owns it whether or not anyone is sitting there.
A seat variable ($activePlayer) Whichever seat the variable holds right now.

Ownership is not cosmetic — it is what owner-seat-only interaction compares against, and what a Hidden zone conceals from. An unowned Hidden zone therefore conceals its contents from everyone, including the host. That is deliberate: an owner that could not be resolved must never open a permission.

There is no Hand type on a table zone. A hand zone stamps its seat onto what is dropped in it and is capped at one per seat; an owner that can move mid-game breaks both. Hands stay under Player Zones.

Seat variables — ownership that follows the turn#

A seat variable is a named slot that holds a seat: activePlayer, dealer, startingPlayer. Declare them in the SEAT VARIABLES section of the Table Zones folder's Inspector, give one an optional starting seat, and bind a zone's owner to it.

One authored zone then covers the whole rotation. A script moves it:

await world.setSeatVariable("activePlayer", "blue");
const active = await world.getSeatVariable("activePlayer"); // "blue"

The zone's tint follows the new owner, an owner-seat-only rule starts admitting them, and a Hidden zone starts concealing from everyone else — all in the same step.

Three things to know before you rely on it:

  • Setting is host-only, like every other mutating script call. Table scripts already run on the authoritative peer, so this costs you nothing you had.
  • Clearing is meaningful. setSeatVariable(name, null) makes a bound zone unowned — not "still owned by whoever held it last". Between rounds that is usually what you want.
  • Deleting a variable leaves any zone that named it owned by the Table the next time the scene loads. The zone is not deleted and its geometry is untouched; only the binding goes.

Values live in the replicated snapshot, so every peer resolves the same owner the host does. A variable that is unset — or that a re-authored scene no longer declares — resolves to nobody.

Privacy: what a hand zone actually hides#

An author arriving from either competitor will expect something different from what DiceyTable does, so this is worth spelling out.

Product What is hidden in a player's private area
Tabletop Simulator A hand zone's contents, regardless of face orientation (Hands.hiding = 1) — a face-up card in your hand is still secret.
Tabletop Playground Face-up cards on an owned card holder. A face-down card there is public.
DiceyTable Only face-down cards. A face-up card is public regardless of who owns it, because flipping it up is how a player chooses to show it.

So if you want the Tabletop Simulator behaviour — contents concealed whichever way up they are — a Hand zone is not it. Use a Hidden zone, which is exactly the rule that ignores facing. Hidden is not an exotic extra type; it is the type that gives TTS-equivalent hand privacy, and pairing a Hidden zone with the Hand zone is the normal way to build a hand area for a game where holding a card face-up must not reveal it.

Teammates are always entitled to a seat's private information, unconditionally, on both competitors and here. There is no option to exclude them.

Sorting a hand#

A Hand zone can offer the player named sort methods. Each one points at a field and a direction: either a built-in (Card name, Card ID) or one of the deck's own card fields from the deck editor — so a game with a power or cost field on its cards can offer "Power (high to low)" without any scripting.

The player picks one by right-clicking their own hand zone, and the choice is remembered for that game — it is stored on their account, so it follows them to a new room and a new device. It is their preference, not table state: it changes the order of their own hand only, and nobody else can see that order anyway.

Two options are always offered on top of whatever you author:

  • Unsorted, where the player arranges the hand themselves by dragging cards sideways, and new cards land at the end you chose in New cards go to.
  • Card name (A→Z), which is also the fallback.

That fallback is what makes this safe to change after publishing. If you remove a sort method some players had chosen, they are not stuck and nothing errors — they simply get card name order the next time they play, and can pick again. The same is true of a Default you delete. Renaming a method keeps everyone's choice, because a method is identified by an id the editor mints once, not by its name.

A card that has no value for the sorted field always sorts last, in both directions.

Turning a hand's privacy off#

Hide from other players on a Hand zone defaults to on, and on is everything described above. Turning it off declares that seat's hand public: the host stops withholding those cards' identities and every client draws their faces, so an open-hand game — a teaching table, a solitaire layout, a bidding game where hands are revealed — needs no script to arrange it.

It is a privacy release, so it only ever adds visibility, and only to the one rule it names:

  • It applies to cards owned by that seat — the ownership a Hand zone stamps — whether they are sitting in the zone, being dragged, or resting just outside it. Ownership is the fact, not proximity.
  • A Hidden zone still conceals. If a public hand overlaps one, the Hidden rule wins.
  • A card a script revealed to one team stays restricted to that team. An explicit reveal is a deliberate act and outranks a zone-wide default.
  • Teammates are unaffected — they were always entitled, and still are.
  • The seat's deck still keeps its order secret. Publishing a hand's card faces is not a licence to publish the order of a pile.

Off is authoring data, not a per-player toggle: it lives in the scene, applies to every session of the mod, and there is no in-game control to flip it back on mid-game.

There is no GM or see-all role#

Both competitors have one — Tabletop Simulator's Black seat and Tabletop Playground's per-slot Game Master both see inside every hidden area. DiceyTable has no equivalent. A Hidden zone conceals its contents from every viewer not entitled to the seat, and a spectator is not entitled to anything. There is no exemption to grant a referee, a dealer or a dungeon master.

Do not build an RPG-style mod on Hidden zones expecting a GM to see through them. The gap is recorded, but nothing in the redaction contract grants it today.

Cost: hidden information caps a table at ~4–6 remote peers#

A hidden zone is not itself expensive to replicate. Seat zones are authoring data — they live in the scene document and never cross the data channel at all. What is expensive is what a hidden zone causes. The moment one object needs redacting, the host can no longer send a single shared update to everybody: it drops out of delta replication entirely and sends each peer its own full, individually redacted copy of the table. That costs roughly 4.8 Mbps of sustained upload from the host, plus about 14 redact-and-serialise passes per second on the host's main thread, so a table holding hidden information caps out at around 4–6 remote peers (players other than the host) today — well below the seat count. The same ceiling applies to any face-down card or deck whether or not a hidden zone is involved; a hidden zone just makes it easy to reach deliberately.

An empty hidden zone is free. You only pay while objects are actually inside one — if no hidden zone is occupied, every update the host sends is byte-for-byte what it would have been without the zone. Authoring a hidden zone you fill only occasionally costs nothing the rest of the time.

Hand zone or card holder?#

Both are retained, and neither replaces the other. They answer different questions.

Hand zone Card holder
What it is A volume in seat-local space A named pile location with physical presence
What it defines Privacy and seat membership Where a pile sits, and a proximity capture radius
Table geometry None — an outline Static furniture with a box collider
Where it lives The scene document, per seat The replicated snapshot, as an entity
Right for "cards here are mine and private" deal targets, per-player pile rows, discard piles, market rows, solitaire tableaus

They compose, and composing them is the point. A card holder standing inside a hand zone is a private pile: the holder gives the cards a place to sit and a capture radius, and the zone around it makes them the seat's and hides the face-down ones. Neither primitive does both jobs, and you want both more often than not.

A holder no longer confers seat ownership by itself. It used to: a holder that had drifted into a seat's area stamped every card it captured with that seat, quietly competing with the zone rule. That implicit fallback is gone. What survives is the explicit opt-in — set metadata.holderOwnerSeat on the holder and it is treated as that seat's rack, cards included, exactly as before. A published mod that deliberately used a holder as a hand rack keeps working; a holder that merely happened to sit in a seat's area stops making claims it never meant to.

Older saves are promoted, once, on load. A holder written before the opt-in existed can carry a seat in ownerSeat and nothing in metadata. Loading a table copies that seat into metadata.holderOwnerSeat, so the rack keeps gating who may take cards out of it instead of silently becoming a free-for-all. The promotion runs once and never overwrites an answer you gave: a holder you set to no seat stays seatless. If you want an old holder to stop claiming a seat, clear metadata.holderOwnerSeat (and its ownerSeat) rather than expecting the load to do it for you.

Capacity, spread and fan all belong on the holder or on the deferred Layout zone type, never on the hand zone.

Card holders owned by the seat template#

A template can own card-holder definitions as well as zones, which is what makes "every player has three tableau piles and a discard" authorable once. Add them from the Seat Template panel's + Add menu alongside the zone types.

A holder definition is deliberately not an entity. It carries a stable holder id, a slug, a kind, a seat-local position and rotation, an optional scale, and a small metadata payload — and no world transform, no owning seat and no parent, because all three would be wrong for seven of the eight seats. Applying the template turns each definition into a real entity per linked seat, with a derived id and a derived unique slug ({label}-{seat}), and those entities appear under Entities (N) like anything else.

Re-applying updates in place; it never duplicates. Generated holders carry a provenance marker, so a re-apply recomputes the ones it made, creates the ones that are missing, deletes the ones whose definition is gone — and never touches a card holder you placed yourself, even one standing squarely inside a seat's area.

Only that marker can get an entity deleted. A re-apply will adopt an entity whose id happens to match a generated holder's — that is what lets a scene whose ids were rewritten on import reconcile instead of duplicating — but it will never remove one on the strength of its id alone. So an entity that arrives from an imported scene or a hand-written setup.json with an id shaped like a template holder's is safe: worst case it is adopted, never silently deleted.

Editing a generated holder directly is overwritten on the next re-apply. Selecting one shows a read-only FROM SEAT TEMPLATE panel with an Edit Template button rather than the usual entity Inspector, because a re-apply — on load, on save, or on any template edit — recomputes the object wholesale from the definition and the seat's group transform. Edit the template. If you want one seat's holder to differ from the rest, detach that seat.

What it costs#

Measured on the real materializer: about 0.4 KB and one static physics body per holder, so a four-holder template costs roughly 1.7 KB and four static bodies per claimed seat. Holders are static, so they cost broadphase only — no solver work. At the steady state below (holders exist only for claimed seats) a two-player table pays for eight holders, not thirty-two.

What happens at the table#

The template is authoring state — there is no template affordance at /table, and no player or spectator can edit one. But templated holders do appear and disappear at runtime, which is player-visible and surprising the first time. The host, and only the host, decides:

  • Claiming a seat spawns that seat's templated holders. This is what keeps a table authored on eight seats from being cluttered with six unused racks.
  • Explicitly leaving a seat removes them. That means choosing No color selected in the player menu, a host setting your seat to none, or Join as Spectator. Leaving a seat is remembered for as long as you stay connected: the table will not quietly sit you back down, and your racks will not reappear until you pick a seat again. Picking any seat — or coming back with Switch to Player — puts you back in the normal rotation and respawns them.
  • A disconnect does not remove them. A network blip must not destroy someone's tableau, and the presence model genuinely cannot tell a ten-second drop from a deliberate leave. A disconnected player's rack is waiting when they come back.
  • A seat that stops existing does not count as leaving it. If a scene load changes which seats the table defines, players holding a seat that is gone are simply re-seated on the new set; the scene-loaded pass below is what fixes up the holders.
  • Host migration never removes a rack either. When a new host takes over it reconciles the claimed seats only, so it can create but never destroy — a rack left by an earlier disconnect survives the migration that disconnect may well have caused.
  • Cards are never deleted. When a rack goes away its contents stay exactly where they were in world space and simply stop being "in" the holder. They are not teleported, scattered or removed, so a carefully arranged tableau survives a seat change intact. Re-claiming the seat respawns the rack and it picks the cards back up.

Save and load are deterministic in both directions: loading a scene reconciles every seat against who is actually sitting down, so a save made with two seats claimed and reloaded with none removes those holders — and still leaves every card on the table.

The name label#

Every seat group has exactly one label — position, box size, rotation Y, font size, and an optional custom text. Left blank it falls back to the seat's stable id (seat-group-<index>) in the editor and to the seated player's username at the live table; a seated player's name always wins there, whatever text you authored.

On a linked seat the label's geometry and its shared text come from the template, so a per-seat name lives in its own LABEL TEXT override field instead — the shared text would otherwise be overwritten by the next materialization.

Recoloring a seat#

Select the seat group itself (not a zone or the label) for its Color picker: one of the eight standard colors, or Custom… for an arbitrary hex override. The override tints the zone boxes and the label frame, in the editor and at the table, claimed or not. A zone's type only modulates that colour — it never replaces it, because "whose area is this" is read at a glance far more often than "what kind of zone is this".

Because a recolored or relabeled seat can no longer be identified by its original color name, a seat's identity is a stable, color-independent id (seat-group-<index>) that never changes. The #seat-group tag targets every seat group at once; the stable id targets one forever, however it has been recolored or relabeled.

Seat layout — putting seats back on the table#

Seat positions are derived from the table: its footprint, its ring layout (Edge for a rectangle, Radial for a round or oval table) and how many seats it declares. Swap the table and the seats do not follow on their own — a layout you authored is yours, and nothing moves it without asking. The SEAT LAYOUT block on the Player Zones folder is where you put them back.

It names the table the seats are laid out on, its size, its ring layout and its seat count, so you can see at a glance that a game pinned to a round table still has its seats spread around a rectangle.

There are two strengths, and the difference matters:

What it does What it keeps
Move seats onto this table's ring Moves each seat's pivot, its first zone and its name label onto the new ring Zone sizes, zone types, colours, custom label text, and every zone beyond the first — left exactly where you put them
Reset seats to this table's default Rebuilds each seat as a fresh seat on this table: ring position, rotation, zone size and name-label geometry Colours, custom label text and each seat's first zone type

The first appears on its own as a warning whenever the seats stop matching the table — that is the one to take after you resize a table, because your authoring survives it intact.

The second is the one to take when the table changed shape. A hand zone stretched along a rectangle's long edge is still that shape on a circular table, and no amount of moving fixes it. Resetting also clears rotations you set by hand and deletes every zone beyond a seat's first — so it is an explicit action behind a button, never something that happens to you.

Neither touches your objects. A seat's card holders, and anything standing in its zones, stay exactly where they are.

Both show a before/after preview first: a row per seat with its position before and after, what the seat loses, and Apply or Cancel. Cancel is a real answer. Apply is one undo step (Ctrl+Z) — though undoing restores the old layout rather than re-opening the preview.

Seats beyond the table's seat count are removed. A table pack declares how many seats it lays out, and the ring for six seats is not the first six positions of the ring for eight — so lowering the count re-spaces the survivors as well. The preview names every seat it would remove, and a removed seat loses its zones, its label and its colour override.

Snap to table perimeter on a seat group and Apply to… → re-arrange evenly on the template use this same ring, on this same table. One seat or all of them, the answer to "where does this seat go" is the same.

Deleting and restoring a seat#

Delete Player Zone on a seat group removes its zones and label, but the seat slot itself stays available — re-add it later from the Player Zones folder's "Add seat" list, which shows every seat color not currently configured.

Common mistakes#

  • Expecting an unclaimed seat's zone to work at the table. It is editor-only visibility by design, and the rules follow the visibility: an unclaimed seat renders nothing and enforces nothing. Not a bug if a zone you placed seems to "disappear" once you leave Edit Mode with nobody seated there.
  • Assuming any zone makes something a hand card. Only a Hand zone does. If a discard pile stopped counting toward a player's hand, that is the fix, not a regression.
  • Expecting a hand zone to hide a face-up card. It does not — use a Hidden zone.
  • Expecting a spectator or a referee to see into a Hidden zone. Nobody does. There is no see-all role.
  • Editing a template-generated card holder in place. The next re-apply recomputes it. Edit the template, or detach that one seat.
  • Authoring a template layout with +Z pointing away from the table. Every seat will face outward. +Z points at the table centre.
  • Renaming a seat's label and expecting it to stick once someone sits down. A claimed seat's label always shows the player's username; your custom text only shows while the seat is empty.
  • Swapping the table and expecting the seats to follow. They do not move on their own — open SEAT LAYOUT on the Player Zones folder and either re-derive the ring or reset the seats to the new table's default.
  • Looking for a ninth seat. The palette is fixed at eight (PLAYER_SEAT_OPTIONS) — there is no way to author a ninth color slot.

See also#

  • Platform panels — every field on the Player Zones folder, the Seat Template, a seat group and a zone.
  • The Hierarchy Panel — the Seat Template row and the linked / detached badges.
  • Glossary — Seat Template, linked, detached and the zone types, defined once.
  • Tags and groups — the #seat-group tag and how GROUP EDIT can target every seat at once.
  • The Scene Model — Folders vs Tag Groups vs Entities.
  • Setup JSONseatTemplate and seatZones as they are stored.
  • Mod scripting APITableObjectDefinition.ownerSeat and TableHandState, seats as a mod sees them.
  • Mod hooks and capabilitiesonSeatChanged, and the onZoneEnter / onZoneLeave hooks these zones raise.