# Changelog

Notable changes to the Board Godot SDK. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

> **Note**: The Godot SDK is at v1.0.0-beta.1, the first beta. It covers the full public-API surface of the Board platform and is the first version intended for building games against, ahead of the stable 1.0.0.

## [1.0.0-beta.1] — 2026-05-26

First beta release. The SDK covers the full public-API surface of the Board platform across input, session, save, avatar, pause, and application domains, with a no-build-script export-and-install path and a polished sample app.

### Added

- **`Board.input.contacts_received`** — the single per-frame touch signal: a snapshot `Array` of contact `Dictionary` entries, each carrying `phase_id` (`BEGAN` / `MOVED` / `ENDED` / `CANCELED` / `STATIONARY`), `is_touched`, `glyph_id`, and `contact_id`. Games that want discrete edges (tap-down / tap-up via `is_touched`, or piece-lifted) keep their own previous-frame map keyed by `contact_id` and diff it each frame. This mirrors the Unity SDK’s per-frame snapshot model.
- **Cached, player-scoped avatar loader on `Board.avatar`** (mirrors Unity’s `BoardPlayer.avatar`):
  - `await_load_avatar(avatar_id: int) -> ImageTexture` — returns a ready-to-display, already-decoded `ImageTexture`, or `null` on failure / off-device. Cached after first load; concurrent loads of the same id coalesce onto one fetch. Pass a player’s `avatar_id` from `Board.session.get_players()` (the player `Dictionary`’s `avatar_id` is a `String`, so call with `int(player.avatar_id)`).
  - `await_default_avatar() -> ImageTexture` — the default avatar (id `0`).
  - `clear_cache() -> void` — drop cached textures.
- **`Board.save.get_app_storage_info() -> Dictionary`** — synchronous storage query. Keys: `total_storage` (int bytes), `used_storage` (int bytes), `remaining_storage` (int bytes), `usage_percentage` (float `0.0`–`1.0`). Off-device returns `{}`. Use it to render a storage meter or pre-flight whether a save fits. Mirrors Unity’s `GetAppStorageInfo`.
- **Optional `ai_type_indices` filter on the session selector** — `Board.session.present_add_player(ai_type_indices := PackedInt32Array())` and `Board.session.present_replace_player(session_id, ai_type_indices := PackedInt32Array())` now take an optional trailing `PackedInt32Array`. An empty array (the default) offers all AI types registered via `set_ai_player_types()`; a non-empty array restricts the selector’s “Add AI” options to that subset. Existing zero-arg / `(session_id)`-only calls still work. Mirrors Unity’s selector AI-type filter.
- **Documented `noCompress` one-time setup** — after installing the Custom Android Build Template, add `noCompress "pck", "sparsepck"` to `android/build/build.gradle`’s `aaptOptions` block (right after the `ignoreAssetsPattern` line). Godot’s stock template doesn’t set this; without it the PCK is deflated inside the APK and cold boot takes ~2 minutes instead of a few seconds, because the runtime can mmap the PCK instead of decompressing every `FileAccess` call through ZipInputStream. This is now part of the project setup docs rather than a build-script side effect.
- **`scripts/prune_import_cache.py`** — idempotent orphan `.godot/imported/` cleanup.
- **Sample boot scene template** — `sample/boot/boot_scene.{tscn,gd}` — threaded scene-load with progress bar; ready to swap in as the launch scene.
- **Pause-context restart custom button** wired in the sample sidebar: matches on `Board.pause.ACTION_CUSTOM_BUTTON` + `custom_button_id == "restart"` → `get_tree().reload_current_scene.call_deferred()`.
- **`bootstrap-board-godot-game` skill** — interactive Claude skill that scaffolds a new Board game with every best practice from this SDK baked in.
- **Per-domain consumer-facing `CLAUDE.md`** at `addon/addons/board_sdk/CLAUDE.md` — drops into game-side `addons/` so Claude can read it and know how to use the SDK.
- **Full Godot documentation** under `docs/` (22 markdown files) mirroring `docs.dev.board.fun`’s sectioning (Getting Started, Learn, Guides, Reference, More, FAQ).

### Changed

- **`plugin.cfg`** version bumped `0.1.0` → `1.0.0-beta.1`.
- **`Board.session.is_ready()` → `Board.session.is_session_ready()`** — names now mirror the underlying plugin method and pair cleanly with `are_services_ready()`. **Breaking** for callers of the previous name.
- **Sample package renamed**`fun.board.smoketest` → `fun.board.godot_example`.
- **Sample display name**`Board Smoke Test` → `Godot Board Example`.
- **`board_application.gd`** — dropped the “(later)” lifecycle-hook stub from the file header; lifecycle stays Godot-native via `NOTIFICATION_APPLICATION_*`.
- **`board_export_plugin.gd`** — release export with a missing release AAR now emits `push_warning(...)` instead of silently shipping the debug AAR.
- **`board_avatar.gd`** / **`board_pause.gd`** — clarified docstrings on `await_load_avatar()` and `poll_result()` so the caching/coalescing behavior and signal/fallback duality are visible.

### Removed

- **`Board.services_ready` signal** — was declared on `board.gd` but never emitted. Use `Board.session.are_services_ready()` to query state directly.

### Compatibility

- The compiled `board.aar` ships pre-built with the addon at `addon/addons/board_sdk/android/bin/debug/board.aar`. End users don’t build it.
- Requires Godot 4.6.x stable. JDK 17 required for Android builds (Godot 4.6 ships Java 17 bytecode).
- Targets Android API 33 (compile/target SDK), minimum 29.
- Single ABI: `arm64-v8a`.

### Migration Notes

If you’re upgrading from a pre-v1.0 prerelease:

1. **Rename `is_ready()` calls** — search-and-replace `Board.session.is_ready()` → `Board.session.is_session_ready()` across your codebase.
2. **Drop the `Board.services_ready` signal handler** if you wired one — the signal was never emitted; query `Board.session.are_services_ready()` directly instead.
3. **Replace the addon** — re-copy `addon/addons/board_sdk` from the new SDK release into your project’s `addons/` directory. The bundled `board.aar` updates with it.
4. **Bump `plugin.cfg`** in your local copy if you symlinked the addon at a specific version. New version: `1.0.0-beta.1`.
5. **Touch is a single per-frame snapshot** — `Board.input.contacts_received` is the only touch signal. If you need discrete edges (tap-down / tap-up via `is_touched`, or piece-lifted), keep your own previous-frame map keyed by `contact_id` and diff each frame:

```
   var _prev := {}  # contact_id -> is_touched

func _on_contacts_received(contacts: Array) -> void:
          var seen := {}
          for contact in contacts:
              var id: int = contact.contact_id
              seen[id] = true
              var was_touched: bool = _prev.get(id, false)
              if contact.is_touched and not was_touched:
                  _on_tap_down(contact)
              elif was_touched and not contact.is_touched:
                  _on_tap_up(contact)
              _prev[id] = contact.is_touched
          for id in _prev.keys():
              if not seen.has(id):
                  _on_piece_lifted(id)
                  _prev.erase(id)
   ```

6. **Avatars are loaded per-player, not by arbitrary id** — replace any raw-bytes loading with `await Board.avatar.await_load_avatar(int(player.avatar_id))`, which returns a ready-to-display `ImageTexture`. Drop your own decode and caching; the loader decodes, caches, and coalesces concurrent loads for you.

## Earlier Versions

The 0.x prerelease series isn’t documented here. v1.0.0-beta.1 is the first documented release, intended for building games against ahead of the stable 1.0.0.
