import type { ForgeTransport } from '@bitmagic/world-forger/pipeline/transport-types.js'; import { type Platform } from '../browser-gl.js'; import { type ForgePageTransportOptions } from './transport.js'; export interface ForgeBrowserHostOptions { /** Port of the project's own vite dev server. */ gamePort: number; gameId: string; /** The project's `src/work/world.json`, parsed — see {@link readProjectGameData}. */ gameData: Record; /** * Base URL the engine reads back as `window.AI_AGENT_URL` (`engine/agentUrl.ts`). In this lane * that must be **api-server**, not game-play-agent: the engine's uploads go to * `POST /api/cli/v1/uploads/signed-url`, which api-server serves. */ agentUrl: string; log?: (message: string) => void; gameLoadedTimeoutMs?: number; /** Passed through to the transport (retry gap). Exposed for tests. */ transportOptions?: ForgePageTransportOptions; } /** * The page URL. `source=creator` is not cosmetic — see precondition 1 in the file header. */ export declare function forgePageUrl(gamePort: number, gameId: string): string; /** * Chromium flags for a forge run. Split out so the rule that matters is testable without a * browser: web security is always off (presigned PUTs from localhost). The GL backend is * `browser-gl.ts`'s call, shared with `verify` — a bake drives the engine's renderer for twenty * minutes, so software rasterisation is the difference between a coffee break and an afternoon. */ export declare function forgeLaunchArgs(env: Record, platform?: Platform): string[]; /** * The game data `LOAD_GAME` carries. There is no `/api/get-game-config` in this lane — the CLI * project owns its game on disk. * * It takes BOTH of the project's `work/` JSON files, because `GameEngine.loadGame` reads from * both: `gameData.worldProfileData` comes from world.json (GameEngine.ts:1431) and * `gameData.gameGenre` from game.json (:1432). Passing world.json alone leaves the engine with no * genre, and it refuses to load. This mirrors the web lane, where `session-file-manager.ts` keeps * `-game.json` and `-world.json` as separate templates and the engine is handed the * combination. * * `gameGenre` must be the GenreLoader REGISTRY name — `Voxel`, not `voxel`. `GenreLoader`'s map is * keyed by it (`GenreLoader.ts:43-46`), and an unmatched key logs "Unknown genre" and returns null, * which surfaces as `Failed to load genre "..."`. game.json already carries the registry-cased * value, which is exactly why it is read rather than derived from `bitmagic.json`'s lowercase * `genre` (that one names the genre DIRECTORY). * * `gameId` is forced from `bitmagic.json` and wins over both files. A scaffolded project's * work-copy JSON is taken from a template and still carries the TEMPLATE's id — a game this * creator does not own. Everything else in a forge already keys off `bitmagic.json` (the design * request, the ownership check), so leaving the template's id in the game data splits one run * across two games; and the engine uses `currentGameData.gameId` as the storage-key prefix when it * asks for a signed upload URL, so every `.vxl`/`.vwld` PUT would be scoped to the wrong game and * refused by `assertOwnsGame`. */ export declare function readProjectGameData(root: string, metadata: { gameId: string; }): Record; export declare class ForgeBrowserHost { private readonly options; private readonly log; private readonly listeners; private readonly pageBridge; private readonly forgeTransport; private context; private page; private userDataDir; constructor(options: ForgeBrowserHostOptions); /** * The pipeline's `ForgeDeps.transport`. Available before `loadGame()` only so the caller can * build its deps in one place; using it earlier fails as a transport failure (null), because * `postCommand` below refuses without a page. */ get transport(): ForgeTransport; private buildBridge; private dispatch; launch(): Promise; /** * Open the game page and complete the `LOAD_GAME` → `GAME_LOADED` handshake. Every forge command * sent before this resolves would be queued by the engine, not executed (precondition 2). */ loadGame(): Promise; /** * Wait for one handshake message. Unlike the transport's waits, these REJECT on timeout: a page * that never boots or a game that never loads is not a "no answer" the caller can shrug off and * retry — nothing downstream can run. */ private waitForPageMessage; closePage(): Promise; close(): Promise; }