import type { Environment } from '../config/environments.js'; import type { BakeSource } from '../assets/voxelize-glb.js'; import { type KeepGlb } from '../assets/keep-glb.js'; import { withVoxelizeSession } from '../forge/voxelize-session.js'; import { type SmartObjectSpec } from '@bitmagic/asset-core'; import type { ProjectContext } from '../project/context.js'; import { type JsonObject } from '../project/world-json.js'; /** * The grid a voxel MASTER is forged at, deliberately far finer than any size it is baked to. * * The master is the stored original, and the engine's `resampleMaster` only ever merges cells — * it refuses to upsample rather than inventing detail. So this number is the ceiling on every * later `bitmagic assets revoxelize`, and forging at the working size (0.1 m, ~20 cells for a 2 m * prop) would permanently cap the asset at the one size nobody had judged yet. * * 512 rather than "whatever TRELLIS emits" (`grid: 0`) so the stored size is predictable and does * not move when a pipeline type changes. It is the top of the allowlist, and the engine's * `MASTER_MAX_LEAVES` budget was set to clear a 512³ forge without coarsening. */ export declare const DEFAULT_MASTER_GRID = 512; /** * What the bake was handed: a mesh to voxelize, or a voxel master to compile. * * The very union `assets add` bakes from, named again here because this lane's callers speak of * props — a union rather than two optional URLs because exactly one is always true, and the error * messages differ in the only part that matters: what was kept and how to retry with it. */ export type PropSource = BakeSource; /** * Whether this project's engine can bake a voxel master. * * A capability probe, not a version comparison. `withVoxelizeSession` runs the project's OWN * vendored engine, and `CREATE_ASSET_FROM_VXL_MASTER` only exists in engines from 2026-08-02 — * but the failure mode of guessing wrong is what makes this worth checking rather than trying: * an engine with no handler for a message does not answer, so the request stalls for the full * VOXELIZE_TIMEOUT_MS (10 minutes) and then again on the retry, after which the paid generation * is thrown away. Reading a version string is the indirect way to ask; the file is the answer. * * `engine` resolves to `engine/engine` — see scaffold/aliases.ts. */ export declare function engineSupportsVxlMaster(projectRoot: string): boolean; interface FitBox { x: number; z: number; height: number; } export interface PropVoxelizeOptions { context: ProjectContext; environment: Environment; token: string; assetId: string; prompt: string; source: PropSource; log: (message: string) => void; /** * Injected by tests so the bake runs without Chrome, matching the `voxelize` seam * `generateAndInstallModel` already has. Without it none of the routing, capability or fallback * logic below can be covered at all — every path ends in a real browser. */ session?: typeof withVoxelizeSession; } export interface GenerateAndInstallOptions { context: ProjectContext; environment: Environment; token: string; assetId: string; prompt: string; /** The creator's own request, verbatim — recorded server-side for analytics, never generated from. */ creatorPrompt?: string; /** Skip the paid generation and voxelize this mesh instead — how a failed run is retried. */ glbUrl?: string; /** Skip the paid generation and bake this voxel master instead — the same, for the voxel path. */ masterUrl?: string; fetchImpl?: typeof globalThis.fetch; log: (message: string) => void; /** See PropVoxelizeOptions.session. */ session?: typeof withVoxelizeSession; /** * Ask for a mesh rather than voxels (`--mesh`). The voxel path is the default; this is the way * back to the source that has no resolution ceiling. A vendored engine too old to bake a master * takes the mesh path regardless of this. */ mesh?: boolean; /** * Grid to forge the master at (`--voxel-grid`); defaults to {@link DEFAULT_MASTER_GRID}. Has no * meaning on the mesh path, which the command refuses rather than ignoring. */ voxelGrid?: number; /** * Ask the Forger to find the prop's moving parts and lights (`--smart`, `--smart-hint`) and * bake them as a smart object. `true`, or the designer's hint ("the blades should spin"). * Voxel path only; the mesh path has no master to analyse. */ smart?: boolean | string; /** * A SmartObjectSpec to bake a master with (`--smart-spec`), for the paths that skip the * generation that would have produced one: `--master-url`. The generation's own spec is * stored beside the master as `/smart/spec.json`, so a retry is not a second analysis. */ smartSpec?: SmartObjectSpec; /** * Keep the generated mesh as a `glb` asset at the placeholder's fitBox height instead of * voxelizing it — a low-poly project's default (game.json `artStyle`), or `--keep-glb`. Forces * the mesh path; `masterUrl` is refused. No browser is needed. */ keepGlb?: boolean; /** Injected in tests so the kept-mesh lane runs without a network. */ keepGlbUpload?: KeepGlb; } /** * What the asset was made from, reported so callers do not have to guess. * * Two fields rather than one, because both end up in machine-readable output — the `--json` * document and the editor's journal — and a voxel master reported under `glbUrl` would tell an * agent there is a mesh to retry with when there is not, on the one path whose whole point is * that no mesh exists. */ export type PropSourceResult = { glbUrl: string; masterUrl?: undefined; } | { masterUrl: string; glbUrl?: undefined; }; export interface PropVoxelizeResult { assetId: string; assetName: string; assetUrl?: string; /** How many placed instances now point at the upgraded asset. */ instanceCount: number; } /** A thrown CliError's message, without letting a non-Error stringify to "[object Object]". */ export declare function messageOf(error: unknown): string; /** * The asset this command upgrades, with the `fitBox` the bake has to be constrained to. * * A missing `fitBox` is refused rather than defaulted. It records the size the object occupied * when the level was baked, and every instance was placed against it — voxelizing to a different * envelope produces an asset that is the wrong size everywhere it already sits, which nothing * downstream would flag. */ export declare function findPropAsset(world: JsonObject, assetId: string): { asset: JsonObject; fitBox: FitBox; }; /** * Building-scale boxes voxelize hollow (a closed shell); prop-scale fills solid. Same thresholds * as the HQ job — a solid-filled building is millions of wasted voxels, and a hollow prop reads as * a shell the moment anything clips into it. */ export declare function shouldFillInterior(fitBox: FitBox): boolean; /** Who to ask for a generated source, and what to ask for. */ export interface SourceRequest { environment: Environment; token: string; gameId: string; prompt: string; /** The creator's own request, verbatim — recorded server-side for analytics, never generated from. */ creatorPrompt?: string; fetchImpl: typeof globalThis.fetch; log: (message: string) => void; } /** * Whether to ask for voxels at all. * * Says out loud when a vendored engine too old to bake a master is what put this run on the mesh * path: that is the one way onto it without asking for it. Every other route announces itself, and * a creator reading docs about a voxel asset has nothing else to connect them to what they got. */ export declare function shouldTryVoxelPath(projectRoot: string, mesh: boolean, log: (message: string) => void): boolean; /** * Ask for a voxel master, or null when the GENERATION failed and the mesh path is worth trying. * * Only a generation failure comes back as null. What the caller does with the master — the bake — * throws on its own, because the master exists and was paid for, so the useful answer there is * "here is what is kept", not another generation. */ export declare function requestVoxelMaster(request: SourceRequest, voxelGrid?: number, smart?: boolean | string): Promise; /** * The analysis hint a smart placeholder implies: its declared moving parts and * lights, phrased for the Forger. Undefined for a placeholder that declares * nothing, so an ordinary rock costs no vision call. */ export declare function inheritedSmartHint(asset: JsonObject): string | undefined; /** A voxel master and, when asked for and found, the smart-object spec to bake it with. */ export interface VoxelMasterSource { masterUrl: string; smartObject?: SmartObjectSpec; } /** Ask for a mesh. There is no further source to fall back to, so every failure throws. */ export declare function requestMesh(request: SourceRequest): Promise; /** * All three steps, for the two callers that want the whole job: `bitmagic generate prop` and the * editor's "Generate high-quality version" button. Shared rather than duplicated — the editor * running a materially different flow from the command it also prints would be a trap. * * Everything the caller must resolve first (project, environment, token) is a parameter, so this * stays free of CLI output plumbing and of how the token was obtained. */ export declare function generateAndInstallProp(options: GenerateAndInstallOptions): Promise; /** * Steps 2 and 3. Split from the command so the browser work is callable directly on a retry that * already has a GLB, and so the command file stays about flags and output. */ export declare function voxelizeAndInstall(options: PropVoxelizeOptions): Promise; export {};