Shapes, behaviors & value bindings
Beyond the kind-specific fields, every component — piece, card, die, token — shares a set of optional rendering refinements. Each one defaults to today’s plain behaviour when absent, so you only add them when an object needs a non-rectangular outline, snap rotation, an attached marker, or a live number.
These are still table-as-scene fields: they change how an object is drawn
and handled, not what is legal. In particular a value’s min / max /
step are display hints — taybl never clamps the number.
How to edit in the Studio
Open your game → Layout tab and select the component. The Behaviors
section exposes the Flippable, Rotatable, and Stackable toggles.
The finer refinements — shape, footprint, rotationStep / orientations,
attachable, and valueBinding — are produced by the generation pipeline
into the GDL and don’t have a dedicated inspector control yet; adjust them by
editing the generated definition.
Shape — the outline within the size box
A component’s shape is the outline drawn inside its size bounding box —
it sets the hit-area, the colour fallback, and how tiles tessellate. It is a
discriminated union on type. Absent → rect.
type | Extra fields | What it draws |
|---|---|---|
rect | — | A rectangle (the default) |
circle | — | A circle with diameter min(w, h) |
hex | orientation: pointy or flat | A hexagon, point-up or flat-top |
polygon | points: array of { x, y } (≥ 3) | A free outline; each point is 0..1 of the size box (0,0 = top-left, 1,1 = bottom-right) — used for meeple silhouettes |
Footprint — multi-cell pieces
A footprint lets one component cover several grid cells (a domino, a
polyomino, a 2×2 building). It is { cells: { col, row }[] } — integer
offsets from the anchor cell, which is the cell a placement or move
names. The anchor { col: 0, row: 0 } must be present; the covered cells
are derived at render time, so no runtime state changes. Absent → the
component occupies a single cell.
Behaviors — how a component can be handled
The behaviors object toggles the drag affordances on a component. The
first three are checkboxes in the inspector:
| Field | Values | Default | What it does |
|---|---|---|---|
flippable | true | .optional() | The component can be turned over (front ↔ back) |
rotatable | true | .optional() | The component can be rotated |
stackable | true | .optional() | Copies can be stacked into a pile |
rotationStep | number > 0 (degrees) | .optional() | When rotatable, the snap increment (e.g. 90 → four orientations). Absent → free rotation. Ignored whenever orientations is present |
orientations | array of angles in degrees | .optional() | Explicit allowed angles (e.g. [0, 90, 180, 270]). When present this is the full set of legal angles and overrides rotationStep. Each entry is wrapped into [0, 360) and de-duplicated |
Attachable — pinned markers
attachable: true lets a component be pinned onto another object instance
— a +1/+1 counter on a creature, a damage marker on a card. A pinned marker
follows its host when it moves and is removed or captured with it; the engine
handles that ripple, and the renderer draws the marker as an overlay on the
host. Absent → not attachable.
| Field | Values | Default | What it does |
|---|---|---|---|
attachable | true | .optional() | This component may be pinned onto another object instance |
Attach slots — named positions on a host
When a host component declares attachSlots, markers that attach to it can
target a specific slot instead of the generic anchor (corner / center / edge).
Each slot has fractional x / y coordinates within the host's bounding box
(0,0 = top-left, 1,1 = bottom-right), an optional label, an accepts filter,
and a capacity (default 1). The renderer draws the marker centred at the
slot's position. When no slotId is provided on attach, the engine auto-assigns
the first slot with available capacity.
| Field | Values | Default | What it does |
|---|---|---|---|
attachSlots[].id | text (snake_case) | — | Unique slot identifier within this component |
attachSlots[].label | text | .optional() | Display name shown on hover |
attachSlots[].x | 0–1 | — | Horizontal position as a fraction of the host width |
attachSlots[].y | 0–1 | — | Vertical position as a fraction of the host height |
attachSlots[].accepts | array of kinds | .optional() | Which component kinds may attach here (omit = any) |
attachSlots[].capacity | integer > 0 | 1 | Maximum markers this slot holds |
Value binding — a live number on a component
A valueBinding renders a runtime number on the component — a life total, a
money count, a dial. The renderer reads key from either global or the
owning player’s state. As with every taybl field, the bounds are advisory:
min / max / step shape the spinner widget but the engine does not
enforce them. Absent → no number is drawn.
| Field | Values | Default | What it does |
|---|---|---|---|
scope | global player | — | Whether the number lives on the table or on the owning player |
key | text | — | Which state value to read and display |
display | number dial | .optional() | A numeric badge (default) or a dial widget |
min | number | .optional() | Display-only lower bound for the spinner — not enforced |
max | number | .optional() | Display-only upper bound for the spinner — not enforced |
step | number > 0 | .optional() | Spinner increment size — display hint only |
Related
- Components overview — the shared fields and kinds
- Tokens — the most common home for a
valueBindingcounter - Pieces — meeple silhouettes via
shape: polygon - Grids — the cells a
footprintcovers