import { createPlaytest } from '@playbox-ai/playable-playtest'; import { attachPlaytestDebug, type PauseController } from '@playbox-ai/playable-playtest/debug'; import { attachPlaytestPreview } from '@playbox-ai/playable-playtest/preview'; export interface GameView { ready: boolean; phase: 'playing' | 'won' | 'lost'; timeRemaining: number; player: { x: number; y: number; health: number; isGrounded: boolean }; collected: number; required: number; } /** Call once from the real game bootstrap, not once per frame. */ export function installPlaytest(game: GameView, previewEnabled: boolean, buildId: string, pauseController?: PauseController) { const session = Array.from(crypto.getRandomValues(new Uint32Array(4)), n => n.toString(16)).join('-'); let round = 1; const playtest = createPlaytest({ enabled: previewEnabled, buildId, runId: `${session}:${round}` }); playtest.exposeState({ schemaVersion: 1, description: 'phase: playing/won/lost; position: world units, +X right, +Y up; timeRemaining: seconds. canJump means grounded, not a command.', isReady: () => game.ready, read: () => ({ phase: game.phase, timeRemaining: game.timeRemaining, player: { position: { x: game.player.x, y: game.player.y }, health: game.player.health, canJump: game.player.isGrounded }, objective: { collected: game.collected, required: game.required }, }), }); const detach = attachPlaytestPreview(playtest); const detachDebug = attachPlaytestDebug(playtest, { pauseController }); return { playtest, /** Invoke before the existing restart resets objects; set ready after it completes. */ beforeRestart() { game.ready = false; playtest.startRun(`${session}:${++round}`); }, dispose() { detachDebug(); detach(); playtest.dispose(); }, }; }