Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dpearson2699 avatar

Tabletopkit

  • 2k installs
  • 944 repo stars
  • Updated July 15, 2026
  • dpearson2699/swift-ios-skills

A skill that provides TabletopKit reference patterns for building multiplayer spatial board games on visionOS, covering game setup, equipment state, player seats, actions and turns, gesture interactions, RealityKit rende

About

This skill guides developers through building multiplayer spatial board games with Apple's TabletopKit framework on visionOS, the only platform the framework supports. It covers the full game structure: defining the table surface and shape, equipment such as pawns, cards, and dice with the correct state types, player seats and seat claiming, and turn management through TabletopAction commands. Developers use it when implementing gesture-driven TabletopInteraction delegates, dice tosses with physics-based TossableRepresentation shapes, equipment hierarchies with stacked or fanned card layouts, and RealityKit rendering through EntityRenderDelegate. Multiplayer synchronization runs over FaceTime Group Activities via coordinateWithSession, with no manual message passing required. The document flags availability boundaries, with core APIs on visionOS 2.0+, interaction configuration on visionOS 2.2+, and custom actions and custom equipment state on visionOS 26.0+, lists common mistakes such as mutating state outside actions or testing multiplayer in the Simulator, and closes with a release review checklist.

  • Full TabletopKit type map: TabletopGame, TableSetup, Equipment, TableSeat, TabletopAction, and interaction delegates
  • Dice physics via TossableRepresentation shapes from d4 tetrahedron to d20 icosahedron, reading faces from toss outcomes
  • FaceTime Group Activities multiplayer with automatic state sync through coordinateWithSession, no manual message passing
  • Availability gating called out per API: visionOS 2.0+ core, 2.2+ interaction configuration, 26.0+ custom actions and sta
  • Common-mistakes list and review checklist covering seat claiming, action registration, rollback handling, and Simulator

Tabletopkit by the numbers

  • 2,004 all-time installs (skills.sh)
  • +102 installs in the week ending Jul 29, 2026 (Skillselion tracking)
  • Ranked #20 of 247 Game Development skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Jul 31, 2026 (Skillselion catalog sync)
At a glance

tabletopkit capabilities & compatibility

Capabilities
define tables, equipment, and seats with the cor · dispatch built in and custom tabletopaction comm · handle gesture interactions and physics dice tos · bridge game state to realitykit rendering with e · synchronize multiplayer sessions through facetim
Use cases
frontend · code review · debugging
From the docs

What tabletopkit says it does

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
SKILL.md
TabletopKit integrates directly with GroupActivities for FaceTime-based multiplayer. Define a `GroupActivity`, then call `coordinateWithSession(_:)`.
SKILL.md
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill tabletopkit

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2k
repo stars944
Security audit3 / 3 scanners passed
Last updatedJuly 15, 2026
Repositorydpearson2699/swift-ios-skills

What it does

Implement multiplayer spatial board games on visionOS with TabletopKit: tables, pieces, cards, dice, seats, turns, gesture interactions, and Group Activities sync.

Who is it for?

Swift developers building spatial tabletop game experiences for Apple Vision Pro on visionOS 2.0 or later

Skip if: iOS or macOS game projects; TabletopKit does not exist outside visionOS

When should I use this skill?

Creating tabletop game experiences with boards, pieces, cards, or dice on visionOS; managing seats, turns, equipment state, TabletopAction flows, or TabletopInteraction delegates; or synchronizing gameplay through FaceTi

What you get

Correct TabletopKit game code with valid equipment state types, registered custom actions, physics-driven dice results, RealityKit rendering, and FaceTime-synchronized multiplayer.

  • Swift observer and delegate implementations
  • Multiplayer sync architecture
  • Dice and card layout code

Files

SKILL.mdMarkdownGitHub ↗

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. TabletopKit is visionOS-only. Core APIs are visionOS 2.0+; availability-sensitive APIs are called out below. Targets Swift 6.3.

Contents

Setup

Platform Requirement

TabletopKit is exclusive to visionOS. Core gameplay, equipment, seating, actions, interactions, rendering, and Group Activities coordination are visionOS 2.0+. TabletopInteraction.Configuration is visionOS 2.2+. CustomAction, CustomEquipmentState, TableSetup.register(action:), and the direct TabletopGame.addAction(_ action: some CustomAction) overload are visionOS 26.0+. The Simulator supports single-player layout testing but not Group Activities 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

TypeRole
TabletopGameCentral game manager; owns setup, actions, observers, rendering
TableSetupConfiguration object passed to TabletopGame init
Tabletop / EntityTabletopProtocol for the table surface
Equipment / EntityEquipmentProtocol for interactive game pieces
TableSeat / EntityTableSeatProtocol for player seat positions
TabletopActionCommands that modify game state
TabletopInteractionGesture-driven player interactions with equipment
TabletopGame.ObserverCallback protocol for reacting to confirmed actions
TabletopGame.RenderDelegateCallback protocol for visual updates
EntityRenderDelegateRealityKit-specific render delegate

Game Configuration

Build a game in three steps: define the table, configure the setup, create the TabletopGame instance.

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)))

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.

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:

// 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 TypeUse Case
BaseEquipmentStateGeneric pieces, pawns, tokens
CardStatePlaying cards (tracks faceUp / face-down)
DieStateDice with an integer value
RawValueStateCustom data encoded as UInt64
CustomEquipmentStatevisionOS 26.0+ custom state with a BaseEquipmentState plus game data

Defining Equipment

// 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
  • .restrictedCurrent([seatID1, seatID2]) -- specific seats only while they are in turn
  • .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 for card fan, grid, and overlap layout examples.

Player Seats

Conform to EntityTableSeat and provide a pose around the table:

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:

// 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 on visionOS 26.0+, conform to CustomAction. Custom action application and validation must depend only on the action data and the supplied TableState / TableSnapshot so every peer resolves the same result. Register custom action types during setup before dispatching them:

setup.register(action: CollectCoin.self)
game.addAction(CollectCoin(coinID: coinID, playerID: playerID))

CustomAction, CustomEquipmentState, setup.register(action:), and .customAction(_:context:) are visionOS 26.0+. Register each custom action type before dispatching it. See references/tabletopkit-patterns.md for full custom action and custom state examples.

Score Counters

setup.add(counter: ScoreCounter(id: .init(0), value: 0))
// Update: game.addAction(.updateCounter(matching: .init(0), value: 42))
// Read:   snapshot.counter(matching: .init(0))?.value

State Bookmarks

Save and restore game state for undo/reset:

game.addAction(.createBookmark(id: StateBookmarkIdentifier(1)))
game.jumpToBookmark(matching: StateBookmarkIdentifier(1))

Interactions

TabletopInteraction.Delegate

Return an interaction delegate from the .tabletopGame modifier to handle player gestures on equipment:

.tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
    if game.tabletopGame.equipment(of: GameDie.self, matching: value.startingEquipmentID) != nil {
        return DieInteraction(game: game)
    }
    return DefaultInteraction(game: game)
}

Use interaction.value.gesture for gesture-specific state. Avoid deprecated gesturePhase. For destination control, prefer interaction.setConfiguration(.init(allowedDestinations: ...)) on visionOS 2.2+ rather than deprecated setAllowedDestinations(_:) or value.allowedDestinations.

Handling Gestures and Tossing Dice

Basic toss(equipmentID:as:) is core TabletopKit. onTossStart, TabletopInteraction.TossOutcome, and TossableRepresentation.face(for:) are visionOS 26.0+.

class DieInteraction: TabletopInteraction.Delegate {
    let game: Game

    func update(interaction: TabletopInteraction) {
        switch interaction.value.phase {
        case .started:
            interaction.setConfiguration(.init(allowedDestinations: .any))
        case .update:
            if interaction.value.gesture?.phase == .ended {
                interaction.toss(
                    equipmentID: interaction.value.controlledEquipmentID,
                    as: .cube(height: 0.02, in: .meters)
                )
            }
        case .ended, .cancelled:
            break
        }
    }

    func onTossStart(interaction: TabletopInteraction,
                     outcomes: [TabletopInteraction.TossOutcome]) {
        for outcome in outcomes {
            let face = outcome.tossableRepresentation.face(for: outcome.restingOrientation)
            interaction.addAction(.updateEquipment(
                die, rawValue: face.rawValue, pose: outcome.pose
            ))
        }
    }
}

Tossable Representations

Dice physics shapes: .cube (d6), .tetrahedron (d4), .octahedron (d8), .decahedron (d10), .dodecahedron (d12), .icosahedron (d20), .sphere. All take height:in: (or radius:in: for sphere) and optional restitution:.

Programmatic Interactions

Start interactions from code: game.startInteraction(onEquipmentID: pieceID).

See references/tabletopkit-patterns.md for group toss, predetermined outcomes, interaction acceptance/rejection, and destination restriction patterns.

RealityKit Rendering

Conform to EntityRenderDelegate to bridge state to RealityKit. Provide a root entity. TabletopKit automatically positions EntityEquipment entities.

class GameRenderer: EntityRenderDelegate {
    let root = Entity()

    func onUpdate(timeInterval: Double, snapshot: TableSnapshot,
                  visualState: TableVisualState) {
        // Custom visual updates beyond automatic positioning
    }
}

Connect to SwiftUI with .tabletopGame(_:parent:automaticUpdate:) on a RealityView:

struct GameView: View {
    let game: Game

    var body: some View {
        RealityView { content in
            content.entities.append(game.renderer.root)
        }
        .tabletopGame(game.tabletopGame, parent: game.renderer.root) { value in
            GameInteraction(game: game)
        }
    }
}

Debug outlines: game.tabletopGame.debugDraw(options: [.drawTable, .drawSeats, .drawEquipment])

Group Activities Integration

TabletopKit integrates directly with GroupActivities for FaceTime-based multiplayer. Define a GroupActivity, then call coordinateWithSession(_:). TabletopKit automatically synchronizes all equipment state, seat assignments, actions, and interactions. No manual message passing required.

import GroupActivities

struct BoardGameActivity: GroupActivity {
    var metadata: GroupActivityMetadata {
        var meta = GroupActivityMetadata()
        meta.type = .generic
        meta.title = "Board Game"
        return meta
    }
}

@Observable
class GroupActivityManager {
    let tabletopGame: TabletopGame
    private var sessionTask: Task<Void, Never>?

    init(tabletopGame: TabletopGame) {
        self.tabletopGame = tabletopGame
        sessionTask = Task { @MainActor in
            for await session in BoardGameActivity.sessions() {
                tabletopGame.coordinateWithSession(session)
            }
        }
    }

    deinit { tabletopGame.detachNetworkCoordinator() }
}

Implement TabletopGame.MultiplayerDelegate for joinAccepted(), playerJoined(_:), didRejectPlayer(_:reason:), and multiplayerSessionFailed(reason:). See references/tabletopkit-patterns.md for custom network coordinators and arbiter role management.

Common Mistakes

  • Forgetting platform restriction. TabletopKit is visionOS-only. Do not

conditionally compile for iOS/macOS; the framework does not exist there.

  • Skipping seat claim. Players must call claimAnySeat() or claimSeat(_:)

before interacting with equipment. Without a seat, actions are rejected.

  • Mutating state outside actions. All state changes must go through

TabletopAction or CustomAction. Directly modifying equipment properties bypasses synchronization.

  • Missing custom action registration. Custom actions must be registered with

setup.register(action:) before use. Custom actions are visionOS 26.0+.

  • Not handling action rollback. Actions are optimistically applied and can be

rolled back if validation fails on the arbiter. Implement actionWasRolledBack(_:snapshot:) to revert UI state.

  • Ignoring discarded actions on visionOS 26. Implement

actionWasDiscarded(_:) when local action queue pressure matters; it is called for local actions that cannot be enqueued.

  • Using wrong parent ID. Equipment parentID in state must reference a

valid equipment ID (typically the table or a container). An invalid parent causes the piece to disappear.

  • Ignoring TossOutcome faces. After a toss, read the face from

outcome.tossableRepresentation.face(for: outcome.restingOrientation) rather than generating a random value. The physics simulation determines the result.

  • Testing multiplayer in Simulator. Group Activities do not work in Simulator.

Multiplayer requires physical Apple Vision Pro devices on a FaceTime call.

Review Checklist

  • [ ] import TabletopKit present; answer explicitly states TabletopKit is visionOS-only and target is visionOS 2.0+
  • [ ] Availability checked for visionOS 2.2+ TabletopInteraction.Configuration and visionOS 26.0+ custom action/custom state APIs
  • [ ] TableSetup created with a Tabletop/EntityTabletop conforming type
  • [ ] All equipment conforms to Equipment or EntityEquipment with correct state type
  • [ ] Seats added and claimAnySeat() / claimSeat(_:) called at game start
  • [ ] All custom actions registered with setup.register(action:)
  • [ ] TabletopGame.Observer implemented for reacting to confirmed actions

and, on visionOS 26.0+, discarded local actions when relevant

  • [ ] EntityRenderDelegate or RenderDelegate connected
  • [ ] .tabletopGame(_:parent:automaticUpdate:) modifier on RealityView
  • [ ] GroupActivity defined and coordinateWithSession(_:) called; multiplayer described as Group Activities/SharePlay synchronization
  • [ ] Group Activities capability added in Xcode for multiplayer builds
  • [ ] Debug visualization (debugDraw) disabled before release
  • [ ] Device notes state Simulator is single-player only; multiplayer requires 2+ Apple Vision Pro units on FaceTime

References

Related skills

FAQ

Which platforms does TabletopKit support?

TabletopKit is exclusive to visionOS. Core APIs require visionOS 2.0+, TabletopInteraction.Configuration requires 2.2+, and custom actions and custom equipment state require visionOS 26.0+.

How does multiplayer synchronization work with TabletopKit?

Define a GroupActivity and call coordinateWithSession; TabletopKit automatically synchronizes equipment state, seat assignments, actions, and interactions over FaceTime with no manual message passing.

Can I test TabletopKit multiplayer in the Simulator?

No. The Simulator supports single-player layout testing only; Group Activities multiplayer requires two or more physical Apple Vision Pro devices on a FaceTime call.

Is Tabletopkit safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.