import { _decorator, Component } from 'cc'; import { createPlaytest, type Playtest } from '@playbox-ai/playable-playtest'; import { attachPlaytestDebug, type PauseController } from '@playbox-ai/playable-playtest/debug'; import { attachPlaytestPreview } from '@playbox-ai/playable-playtest/preview'; const { ccclass } = _decorator; /** Implement this view with getters into your actual round controller. */ export interface RoundView { readonly ready: boolean; readonly phase: string; readonly timeRemaining: number; readonly player: { readonly worldPosition: { readonly x: number; readonly y: number; readonly z: number } }; readonly collected: number; readonly required: number; } @ccclass('PlaytestState') export class PlaytestState extends Component { private playtest?: Playtest; private detach?: () => void; private detachDebug?: () => void; private session = ''; private round = 1; // Call from the game's bootstrap after its real round controller exists. bind(game: RoundView, previewEnabled: boolean, buildId: string, pauseController?: PauseController): void { if (this.playtest) throw new Error('PlaytestState is already bound'); this.session = Array.from(crypto.getRandomValues(new Uint32Array(4)), n => n.toString(16)).join('-'); this.playtest = createPlaytest({ enabled: previewEnabled, buildId, runId: `${this.session}:${this.round}` }); this.playtest.exposeState({ schemaVersion: 1, description: 'phase comes from the round controller; position: Cocos world coordinates, +Y up; timeRemaining: seconds. objective counts collected and required objects.', isReady: () => this.enabledInHierarchy && game.ready, read: () => { const p = game.player.worldPosition; return { phase: game.phase, timeRemaining: game.timeRemaining, player: { position: { x: p.x, y: p.y, z: p.z } }, objective: { collected: game.collected, required: game.required }, }; }, }); this.detach = attachPlaytestPreview(this.playtest); this.detachDebug = attachPlaytestDebug(this.playtest, { pauseController }); } // Real restart must mark its controller not ready before this call, // reset/recreate game objects, and only then mark the controller ready. beforeRestart(): void { this.playtest?.startRun(`${this.session}:${++this.round}`); } onDestroy(): void { this.detachDebug?.(); this.detach?.(); this.playtest?.dispose(); } }