1/**2 * CUA (computer-use agent) test harness hooks, consumed by Playwright-driven3 * test clients:4 *5 * - `window.render_game_to_text()` → concise JSON of the current app state.6 * - `window.advanceTime(ms)` → deterministic frame stepping (runs the same7 * per-frame hooks as the render loop at a fixed 60 Hz dt).8 * - Dev key bindings for history navigation, because test clients drive real9 * keyboard input but can't call the history API: `b` → back, `a` → forward.10 */11import type { SceneCtx } from "./scene.ts";1213export type CuaState = Record<string, unknown>;1415declare global {16 interface Window {17 render_game_to_text?: () => string;18 advanceTime?: (ms: number) => void;19 }20}2122export function installCua(ctx: SceneCtx, getState: () => CuaState): void {23 // Debug handle for harness-side scene interrogation (dev tooling only).24 (globalThis as unknown as Record<string, unknown>).__ctx = ctx;25 (globalThis as unknown as Window).render_game_to_text = () =>26 JSON.stringify({27 coords: "y up; x/z horizontal grid, unit 1.0; rotations in quarter turns",28 hash: location.hash,29 paused: !!ctx.paused,30 ...getState(),31 });3233 (globalThis as unknown as Window).advanceTime = (ms: number) => {34 const steps = Math.max(1, Math.round(ms / (1000 / 60)));35 for (let i = 0; i < steps; i++) {36 for (const fn of ctx.onFrame) {37 fn(ctx.paused ? 0 : 1 / 60, performance.now() / 1000);38 }39 }40 ctx.renderer.render(ctx.scene, ctx.camera);41 };4243 addEventListener("keydown", (e) => {44 const target = e.target as HTMLElement | null;45 if (46 target?.isContentEditable ||47 target?.closest("input, textarea, select, button, summary")48 ) return;49 if (e.code === "KeyB") history.back();50 else if (e.code === "KeyA") history.forward();51 });52}