tabletopkit
Create multiplayer spatial board games using TabletopKit on visionOS. Use when building tabletop game experiences with boards, pieces, cards, and dice, managing player seats and turns, synchronizing game state over FaceTime with Group Activities, rendering game elements with RealityKit, or implementing piece snapping and physics on a virtual table surface.
What this skill does
# TabletopKit
Create multiplayer spatial board games on a virtual table surface using
TabletopKit. Handles game layout, equipment interaction, player seating, turn
management, state synchronization, and RealityKit rendering. **visionOS 2.0+
only.** Targets Swift 6.3.
## Contents
- [Setup](#setup)
- [Game Configuration](#game-configuration)
- [Table and Board](#table-and-board)
- [Equipment (Pieces, Cards, Dice)](#equipment-pieces-cards-dice)
- [Player Seats](#player-seats)
- [Game Actions and Turns](#game-actions-and-turns)
- [Interactions](#interactions)
- [RealityKit Rendering](#realitykit-rendering)
- [Group Activities Integration](#group-activities-integration)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
## Setup
### Platform Requirement
TabletopKit is exclusive to visionOS. It requires visionOS 2.0+. Multiplayer
features using Group Activities require visionOS 2.0+ devices on a FaceTime
call. The Simulator supports single-player layout testing but not multiplayer.
### Project Configuration
1. `import TabletopKit` in source files that define game logic.
2. `import RealityKit` for entity-based rendering.
3. For multiplayer, add the **Group Activities** capability in Signing &
Capabilities.
4. Provide 3D assets (USDZ) in a RealityKit content bundle for tables, pieces,
cards, and dice.
### Key Types Overview
| Type | Role |
|---|---|
| `TabletopGame` | Central game manager; owns setup, actions, observers, rendering |
| `TableSetup` | Configuration object passed to `TabletopGame` init |
| `Tabletop` / `EntityTabletop` | Protocol for the table surface |
| `Equipment` / `EntityEquipment` | Protocol for interactive game pieces |
| `TableSeat` / `EntityTableSeat` | Protocol for player seat positions |
| `TabletopAction` | Commands that modify game state |
| `TabletopInteraction` | Gesture-driven player interactions with equipment |
| `TabletopGame.Observer` | Callback protocol for reacting to confirmed actions |
| `TabletopGame.RenderDelegate` | Callback protocol for visual updates |
| `EntityRenderDelegate` | RealityKit-specific render delegate |
## Game Configuration
Build a game in three steps: define the table, configure the setup, create the
`TabletopGame` instance.
```swift
import TabletopKit
import RealityKit
let table = GameTable()
var setup = TableSetup(tabletop: table)
setup.add(seat: PlayerSeat(index: 0, pose: seatPose0))
setup.add(seat: PlayerSeat(index: 1, pose: seatPose1))
setup.add(equipment: GamePawn(id: .init(1)))
setup.add(equipment: GameDie(id: .init(2)))
setup.register(action: MyCustomAction.self)
let game = TabletopGame(tableSetup: setup)
game.claimAnySeat()
```
Call `update(deltaTime:)` each frame if automatic updates are not enabled via
the `.tabletopGame(_:parent:automaticUpdate:)` modifier. Read state safely with
`withCurrentSnapshot(_:)`.
## Table and Board
### Tabletop Protocol
Conform to `EntityTabletop` to define the playing surface. Provide a `shape`
(round or rectangular) and a RealityKit `Entity` for visual representation.
```swift
struct GameTable: EntityTabletop {
var shape: TabletopShape
var entity: Entity
var id: EquipmentIdentifier
init() {
entity = try! Entity.load(named: "table/game_table", in: contentBundle)
shape = .round(entity: entity)
id = .init(0)
}
}
```
### Table Shapes
Use factory methods on `TabletopShape`:
```swift
// Round table from dimensions
let round = TabletopShape.round(
center: .init(x: 0, y: 0, z: 0),
radius: 0.5,
thickness: 0.05,
in: .meters
)
// Rectangular table from entity
let rect = TabletopShape.rectangular(entity: tableEntity)
```
## Equipment (Pieces, Cards, Dice)
### Equipment Protocol
All interactive game objects conform to `Equipment` (or `EntityEquipment` for
RealityKit-rendered pieces). Each piece has an `id` (`EquipmentIdentifier`) and
an `initialState` property.
Choose the state type based on the equipment:
| State Type | Use Case |
|---|---|
| `BaseEquipmentState` | Generic pieces, pawns, tokens |
| `CardState` | Playing cards (tracks `faceUp` / face-down) |
| `DieState` | Dice with an integer `value` |
| `RawValueState` | Custom data encoded as `UInt64` |
### Defining Equipment
```swift
// Pawn -- uses BaseEquipmentState
struct GamePawn: EntityEquipment {
var id: EquipmentIdentifier
var initialState: BaseEquipmentState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "pieces/pawn", in: contentBundle)
self.initialState = BaseEquipmentState(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Card -- uses CardState (tracks faceUp)
struct PlayingCard: EntityEquipment {
var id: EquipmentIdentifier
var initialState: CardState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "cards/card", in: contentBundle)
self.initialState = .faceDown(
parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
// Die -- uses DieState (tracks integer value)
struct GameDie: EntityEquipment {
var id: EquipmentIdentifier
var initialState: DieState
var entity: Entity
init(id: EquipmentIdentifier) {
self.id = id
self.entity = try! Entity.load(named: "dice/d6", in: contentBundle)
self.initialState = DieState(
value: 1, parentID: .init(0), seatControl: .any,
pose: .identity, entity: entity
)
}
}
```
### ControllingSeats
Restrict which players can interact with a piece via `seatControl`:
- `.any` -- any player
- `.restricted([seatID1, seatID2])` -- specific seats only
- `.current` -- only the seat whose turn it is
- `.inherited` -- inherits from parent equipment
### Equipment Hierarchy and Layout
Equipment can be parented to other equipment. Override `layoutChildren(for:visualState:)`
to position children. Return one of:
- `.planarStacked(layout:animationDuration:)` -- cards/tiles stacked vertically
- `.planarOverlapping(layout:animationDuration:)` -- cards fanned or overlapping
- `.volumetric(layout:animationDuration:)` -- full 3D layout
See [references/tabletopkit-patterns.md](references/tabletopkit-patterns.md) for card fan, grid, and overlap layout examples.
## Player Seats
Conform to `EntityTableSeat` and provide a pose around the table:
```swift
struct PlayerSeat: EntityTableSeat {
var id: TableSeatIdentifier
var initialState: TableSeatState
var entity: Entity
init(index: Int, pose: TableVisualState.Pose2D) {
self.id = TableSeatIdentifier(index)
self.entity = Entity()
self.initialState = TableSeatState(pose: pose, context: 0)
}
}
```
Claim a seat before interacting: `game.claimAnySeat()`, `game.claimSeat(matching:)`,
or `game.releaseSeat()`. Observe changes via `TabletopGame.Observer.playerChangedSeats`.
## Game Actions and Turns
### Built-in Actions
Use `TabletopAction` factory methods to modify game state:
```swift
// Move equipment to a new parent
game.addAction(.moveEquipment(matching: pieceID, childOf: targetID, pose: newPose))
// Flip a card face-up
game.addAction(.updateEquipment(card, faceUp: true))
// Update die value
game.addAction(.updateEquipment(die, value: 6))
// Set whose turn it is
game.addAction(.setTurn(matching: TableSeatIdentifier(1)))
// Update a score counter
game.addAction(.updateCounter(matching: counterID, value: 100))
// Create a state bookmark (for undo/reset)
game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))
```
### Custom Actions
For game-specific logic, conform to `CustomAction`:
```swift
struct CollectCoin: CustomAction {
let coinID: EquipmentIdentifier
let playerID: EquipmentIdentifier
init?(from action: some TabletopAction) {
// DecodRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.