import type { Environment } from '../config/environments.js'; import { type VoxelizeFlags, type VoxelizeGlb } from '../assets/voxelize-glb.js'; import { type KeepGlb } from '../assets/keep-glb.js'; import type { SmartObjectSpec } from '@bitmagic/asset-core'; import type { ProjectContext } from '../project/context.js'; export interface GenerateModelOptions { context: ProjectContext; environment: Environment; token: string; prompt: string; /** The creator's own request, verbatim — recorded server-side for analytics, never generated from. */ creatorPrompt?: string; /** Asset name; defaults to a slug of the prompt. This is the name game code places by. */ name?: string; /** Pre-minted `asset_…` id, so code already referencing it resolves once this lands. */ assetId?: string; voxel: VoxelizeFlags; /** Skip the paid generation and bake this mesh instead — how a failed run is retried for free. */ glbUrl?: string; fetchImpl?: typeof globalThis.fetch; /** Injected in tests so the lane is exercised without Chrome. */ voxelize?: VoxelizeGlb; /** * Keep the generated mesh as a `glb` asset instead of voxelizing it — a low-poly project's * default (game.json `artStyle`), or `--keep-glb`. Forces the mesh path; `masterUrl` is refused. * `voxel.targetHeight` becomes the entry's `targetHeight` (2 m when absent, as `assets add`). */ keepGlb?: boolean; /** Injected in tests so the kept-mesh lane runs without a network. */ keepGlbUpload?: KeepGlb; /** * Ask for a mesh rather than voxels (`--mesh`). The voxel path is the default; a vendored engine * too old to bake a master takes the mesh path regardless of this. */ mesh?: boolean; /** Skip the paid generation and bake this voxel master instead — the voxel path's `--glb-url`. */ masterUrl?: string; /** * Grid to forge the master at (`--voxel-grid`); defaults to prop.ts's `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 model's moving parts and lights (`--smart`, `--smart-hint`) and * bake them as a smart object: `true`, or the creator's hint ("the blades should spin"). Voxel * path only — a mesh has no master to analyse, and a kept mesh is never baked at all. */ smart?: boolean | string; /** * A SmartObjectSpec to bake `masterUrl` with (`--smart-spec`): the generation stores its own * beside the master as `/smart/spec.json`, so a retried bake keeps the moving parts * without paying for a second analysis. */ smartSpec?: SmartObjectSpec; now?: () => Date; log: (message: string) => void; } export interface GenerateModelResult { assetId: string; assetName: string; assetUrl?: string; voxelCount?: number; /** * What this was baked from — carried so a later failure can be retried without paying. * * Exactly one is set. A voxel master reported under `glbUrl` would name a `--glb-url` retry that * cannot work, on the one path that produces no mesh. */ glbUrl?: string; masterUrl?: string; /** True when the mesh was kept as a `glb` asset rather than baked to voxels. */ keptMesh?: boolean; /** The moving parts the bake wrote on the record, when the model came back smart. */ smartParts?: string[]; } /** Where the generation stores its smart-object spec: beside the master, under `smart/`. */ export declare function smartSpecUrlBeside(masterUrl: string): string; /** * A usable asset name from the creator's sentence. Names are what game code places by, so this * favours something short and typeable over something faithful: the prompt itself is kept on the * asset's `description`, which is where the full wording belongs. */ export declare function modelNameFromPrompt(prompt: string): string; export declare function generateAndInstallModel(options: GenerateModelOptions): Promise;