# AI Assistant Setup

Give your AI coding assistant context about the Board SDK. Copy the content below and save it to your project as `CLAUDE.md`, `.cursorrules`, or whatever format your tool uses.

# Board Game Development

Build games for the Board platform using the Unity SDK.

Documentation: https://docs.dev.board.fun

## Required Using Statements

```csharp
using Board.Core;      // BoardApplication, BoardPlayer, BoardPauseScreenContext
using Board.Input;     // BoardInput, BoardContact, BoardContactType, BoardContactPhase
using Board.Session;   // BoardSession, BoardSessionPlayer
using Board.Save;      // BoardSaveGameManager, BoardSaveGameMetadata
```

Do NOT use `using Board;` — each namespace must be imported individually.

## Project Setup

Run **Board > Configure Unity Project...** to automatically configure all required settings. The wizard handles platform switching, API levels, scripting backend, and Input System setup.

Settings assets (BoardGeneralSettings, BoardInputSettings) are auto-created on SDK import.

Download Piece Set Models via **Edit > Project Settings > Board > Input Settings** → "Load Available Models".

More info: https://docs.dev.board.fun/unity/getting-started/setup-reference

## Platform Requirements

- Unity 2022.3 LTS or later (Unity 6 supported)
- Android 13 (API 33), ARM64, IL2CPP
- Unity Input System 1.7.0+
- Board OS 1.3.8+

**Unity 6 only**: Set Application Entry Point to "Activity" (not Game Activity) in Player Settings > Android > Other Settings.

More info: https://docs.dev.board.fun/unity/getting-started/setup-reference

## Quick Reference

### Touch Input

More info: https://docs.dev.board.fun/guides/touch-input

```csharp
// Get all active contacts (pieces and fingers)
BoardContact[] contacts = BoardInput.GetActiveContacts();

// Filter by type
BoardContact[] pieces = BoardInput.GetActiveContacts(BoardContactType.Glyph);
BoardContact[] fingers = BoardInput.GetActiveContacts(BoardContactType.Finger);
```

**BoardContact properties:**
- `contactId` - Unique identifier for this contact
- `glyphId` - Which Piece in the set (0 to N-1), or -1 for fingers
- `screenPosition` - Position in screen coordinates
- `orientation` - Rotation in radians
- `phase` - None, Began, Moved, Ended, Canceled, Stationary
- `isTouched` - Whether a finger is touching this Piece
- `type` - Glyph (Piece) or Finger

```csharp
// Check if a specific contact is still active
bool isActive = false;
foreach (var c in BoardInput.GetActiveContacts()) {
    if (c.contactId == savedContactId) { isActive = true; break; }
}
```

### Players & Sessions

More info: https://docs.dev.board.fun/guides/player-management

```csharp
// Get current players (BoardSessionPlayer extends BoardPlayer with sessionId)
BoardSessionPlayer[] players = BoardSession.players;

// Get the active profile (device owner)
BoardPlayer activeProfile = BoardSession.activeProfile;

// Present player selector to add a player (returns true if added, false if dismissed)
bool added = await BoardSession.PresentAddPlayerSelector();

// Present selector to replace a specific player
bool replaced = await BoardSession.PresentReplacePlayerSelector(existingPlayer);

// Reset session to initial state (only active profile)
BoardSession.ResetPlayers();

// Listen for player changes
BoardSession.playersChanged += OnPlayersChanged;
BoardSession.activeProfileChanged += OnActiveProfileChanged;
```

### Save Games

More info: https://docs.dev.board.fun/guides/save-games

```csharp
// Create a save
var metadataChange = new BoardSaveGameMetadataChange {
    description = "Level 5 Complete",
    playedTime = 2700,  // seconds (ulong)
    gameVersion = Application.version,
    coverImage = screenshotTexture  // optional (Texture2D, converted to 432x243 PNG)
};
BoardSaveGameMetadata saved = await BoardSaveGameManager.CreateSaveGame(saveData, metadataChange);
string saveId = saved.id;

// Load a save (automatically activates the save's players in BoardSession.players)
byte[] data = await BoardSaveGameManager.LoadSaveGame(saveId);

// Update an existing save
BoardSaveGameMetadata updated = await BoardSaveGameManager.UpdateSaveGame(saveId, newData, metadataChange);

// List saves for current app
BoardSaveGameMetadata[] saves = await BoardSaveGameManager.GetSaveGamesMetadata();

// Load cover image
Texture2D cover = await BoardSaveGameManager.LoadSaveGameCoverImage(saveId);
```

### Pause Menu

More info: https://docs.dev.board.fun/guides/pause-menu

```csharp
// Configure pause screen (call once at startup)
BoardApplication.SetPauseScreenContext(
    applicationName: "My Game",
    showSaveOptionUponExit: true  // shows save option when exiting
);

// Or use the struct-based overload
BoardApplication.SetPauseScreenContext(new BoardPauseScreenContext {
    applicationName = "My Game",
    showSaveOptionUponExit = true,
    customButtons = null,
    audioTracks = null
});

// Update specific fields without replacing everything
BoardApplication.UpdatePauseScreenContext(showSaveOptionUponExit: false);

// Listen for pause actions
BoardApplication.pauseScreenActionReceived += (action, audioTracks) => {
    switch (action) {
        case BoardPauseAction.Resume:
            // User resumed
            break;
        case BoardPauseAction.ExitGameSaved:
            // Save game, then call BoardApplication.Exit()
            break;
        case BoardPauseAction.ExitGameUnsaved:
            BoardApplication.Exit();
            break;
    }
};

// Listen for custom button presses
BoardApplication.customPauseScreenButtonPressed += (buttonId, audioTracks) => {
    // Handle custom button
};

// Show/hide profile switcher overlay
BoardApplication.ShowProfileSwitcher();
BoardApplication.HideProfileSwitcher();

// Exit the game (returns to Library)
BoardApplication.Exit();
```

## Common Patterns

### Tracking Pieces Across Frames

```csharp
private Dictionary<int, GameObject> trackedPieces = new();

void Update() {
    var contacts = BoardInput.GetActiveContacts(BoardContactType.Glyph);
    var activeIds = new HashSet<int>();

foreach (var contact in contacts) {
        activeIds.Add(contact.contactId);

if (contact.phase == BoardContactPhase.Began) {
            // New piece placed
            var piece = Instantiate(piecePrefabs[contact.glyphId]);
            trackedPieces[contact.contactId] = piece;
        }

if (trackedPieces.TryGetValue(contact.contactId, out var obj)) {
            // Update position/rotation
            obj.transform.position = ScreenToWorld(contact.screenPosition);
            obj.transform.rotation = Quaternion.Euler(0, 0, -contact.orientation * Mathf.Rad2Deg);
        }
    }

// Clean up lifted pieces
    foreach (var id in trackedPieces.Keys.ToList()) {
        if (!activeIds.Contains(id)) {
            Destroy(trackedPieces[id]);
            trackedPieces.Remove(id);
        }
    }
}
```

### Detecting Piece Touch

```csharp
foreach (var contact in BoardInput.GetActiveContacts(BoardContactType.Glyph)) {
    if (contact.isTouched) {
        // Finger is touching this piece
    }
}
```

## GlyphID vs. ContactID

**Critical distinction—track by `contactId`, NOT `glyphId`.**

- **`glyphId`** - Non-unique identifier for the Piece type (0 to N-1 in your Piece Set). Multiple Pieces of the same type share the same `glyphId`.
- **`contactId`** - Unique identifier for each contact (Piece or finger). No two contacts ever share a `contactId`. Use this to track individual Pieces across frames.

## Board Input Settings

More info: https://docs.dev.board.fun/guides/touch-input

Configure via **Edit > Project Settings > Board > Input Settings**:

| Setting | Default | Description |
|---------|---------|-------------|
| Translation Smoothing | 0.5 | 0-1, higher = smoother but more lag |
| Rotation Smoothing | 0.5 | 0-1, higher = smoother but more lag |
| Persistence | 4 | Frames to keep contact without confirmation |
| Piece Set Model | — | The .tflite model file in StreamingAssets |

Settings properties are **readonly at runtime**. To use different values, create multiple BoardInputSettings assets in the Editor and switch between them:

```csharp
// Switch to a different settings asset at runtime (cancels all contacts!)
BoardInput.settings = alternateSettings;

// Read current settings
float smoothing = BoardInput.settings.translationSmoothing;
string model = BoardInput.settings.pieceSetModelFilename;
```

## Build & Deploy

More info: https://docs.dev.board.fun/unity/getting-started/deploy

Build an APK via **File > Build Settings > Build**. Deploy to Board hardware over Board Connect, the device's built-in HTTP API on the LAN (port 8843, no USB cable). The Board shows its address under **Settings > System**.

**Human (web UI):** open the Board Connect web UI in a browser at the Board's address, drag the `.apk` onto the page, then launch the game from the Library / Sideloaded Apps.

**Agent (board-connect CLI):** install the CLI once, then pair once and deploy. Only `pair` takes the Board address; every later command resolves the saved default (or `-b/--board` / `BOARD_HOST` / discovery), so it needs no host. The `` is your Unity Player Settings package name.

```bash
# Install the CLI (macOS/Linux); Windows: irm https://dev.board.fun/connect/install.ps1 | iex
curl -fsSL https://dev.board.fun/connect/install | sh

# Pair once; the user taps Approve on the device
board-connect pair

# Install the APK and bring it to the foreground
board-connect install path/to/game.apk --launch

# Launch app
board-connect launch com.yourcompany.yourgame

# Stream logs (add --follow to stream live until Ctrl-C)
board-connect logs com.yourcompany.yourgame

# Stop app
board-connect stop com.yourcompany.yourgame

# List installed apps
board-connect apps

# Check device readiness
board-connect status

# Remove app
board-connect remove com.yourcompany.yourgame
```

Board Connect requires Board OS 1.10.0+. (Legacy: `bdb` over USB-C is still available; see https://docs.dev.board.fun/tools/bdb.)

## Important Notes

- Piece Set Models (`.tflite` files) define which Pieces Board can detect—only one set active at a time
- Changing `BoardInput.settings` at runtime cancels all active contacts
- Switching Piece Set models causes a brief delay (no input during load)
- Always call `BoardApplication.Exit()` when exiting—don't just quit
- Session always requires at least one Profile player

## Additional Resources

- API Reference: https://docs.dev.board.fun/unity/api/
- Simulator Guide: https://docs.dev.board.fun/unity/simulator
- Changelog: https://docs.dev.board.fun/unity/changelog
