import type { Environment } from '../config/environments.js'; import { withVoxelizeSession } from '../forge/voxelize-session.js'; import type { ProjectContext } from '../project/context.js'; /** * Whether this project's vendored engine can answer these messages at all. * * A capability PROBE, not a version compare, exactly as `engineSupportsVxlMaster` * does and for the reason its own doc gives: an engine with no handler never * replies, so the request would stall for the full ten-minute bake timeout and * then report a timeout, with nothing pointing at the real cause. */ export declare function engineSupportsMaterialClasses(projectRoot: string): boolean; /** One colour group of an asset, as the engine described it. */ export interface MaterialGroup { id: string; hex: string; share: number; shape: string; } /** What the engine reports about an asset's materials. */ export interface MaterialSignatureReply { version: number; hash: string; voxelCount: number; paletteSize: number; groups: MaterialGroup[]; existingSlots: string[]; } /** A single group's verdict, as written into a `--set` file. */ export interface MaterialVerdictInput { id: string; class: string; confidence: number; } export interface MaterialsBaseOptions { context: ProjectContext; environment: Environment; token: string; assetId: string; log: (message: string) => void; fetchImpl?: typeof globalThis.fetch; /** Injected in tests so the lane runs without Chrome. */ session?: typeof withVoxelizeSession; } export interface ReadMaterialsResult { assetId: string; assetName: string; signature: MaterialSignatureReply; /** The engine's own rendering of the signature — printed verbatim. */ table: string; /** The asset's generation prompt or description, when it records one. */ promptText?: string; } /** * The asset to work on, refused before the browser starts if it cannot be. * * Only a `vxl` asset has voxels to classify. A GLB kept as a mesh, an image, a * sound: none of them have a palette, and saying so up front costs nothing while * a browser launch costs seconds. */ export declare function findVoxelAsset(world: Record, assetId: string): { asset: Record; url: string; name: string; }; /** * The text this asset was generated from, if it records any. * * The single most valuable signal a classifier has — it names the materials * outright — and also what the plausibility guard checks a precious-material * claim against. Absent for an asset added from a file, which is exactly the case * where classifying on colour alone would be guessing. */ export declare function assetPromptText(asset: Record): string | undefined; /** * Ask the engine what the asset is made of. * * Reads only — no upload, no world.json write, nothing spent. Safe to run as often * as you like, which matters because it is the step an agent runs first. */ export declare function readAssetMaterials(options: MaterialsBaseOptions): Promise; export interface SetMaterialsOptions extends MaterialsBaseOptions { /** The verdicts to apply, and the signature hash they were computed against. */ signatureVersion: number; signatureHash: string; verdicts: MaterialVerdictInput[]; } export interface ClearMaterialsOptions extends MaterialsBaseOptions { clear: true; } export interface SetMaterialsResult { assetId: string; assetName: string; /** The material classes now on the asset. Empty after a --clear. */ slots: string[]; /** Classes that lost a share floor, the slot budget, or the plausibility guard. */ dropped: Array<{ class: string; reason: string; share: number; }>; /** Fraction of the asset left with the default look. */ baseShare: number; /** True when nothing changed, so no upload and no world.json write happened. */ unchanged: boolean; assetUrl?: string; instanceCount: number; } /** * Apply an assignment — or strip every class with `--clear` — and record the * result in world.json. * * The engine hands back BYTES rather than uploading, the same split * `REVOXELIZE_FROM_VXL_MASTER` uses: in a pro project the CLI owns world.json, * and two writers on one asset thrash the reload watcher. */ export declare function setAssetMaterials(options: SetMaterialsOptions | ClearMaterialsOptions): Promise; /** What api-server answers `POST /api/cli/v1/assets/materials/classify` with. */ export interface ClassifyReply { signatureHash?: unknown; verdicts?: unknown; model?: unknown; } /** The model step, injectable so the lane is testable without api-server. */ export type ClassifyMaterials = (request: { gameId: string; assetName: string; table: string; signatureHash: string; promptText?: string; }) => Promise; export interface ClassifyMaterialsOptions extends MaterialsBaseOptions { /** Test seam; the default posts to api-server. */ classify?: ClassifyMaterials; /** * Leave an asset that already carries material classes as it is, without * asking the model. A forged archetype baked with the level's * `materialByColor` map has its classes from the designer's own words; a * model's guess must not overwrite them, and the round trip is wasted. */ skipIfClassed?: boolean; } export interface ClassifyMaterialsResult extends SetMaterialsResult { /** The model that answered, for provenance. Absent when no verdicts came back. */ model?: string; /** True when `skipIfClassed` found classes already on the asset and asked nothing. */ alreadyClassed?: boolean; } /** * Read, classify and apply in ONE browser session: what `generate prop|model` run * on the asset they just baked, and what `assets materials --auto` runs on demand. * * The middle step is api-server's light model rather than the creator's coding * agent — the same prompt that agent would be shown, answered without a round * trip through it, so a generated sword shines before anyone has looked at it. * `assets materials --set` still overrides whatever this decided. * * An empty verdict list skips the APPLY entirely: the engine would leave the * file byte-identical anyway, and not asking is one fewer thing to time out. */ export declare function classifyAssetMaterials(options: ClassifyMaterialsOptions): Promise; /** * The tail of `generate prop|model` and `assets revoxelize`: classify the asset * just baked, and say what happened. * * **Never throws.** The asset is baked, uploaded and in world.json by the time * this runs, and a generation is paid for; a classification that fails — * api-server without a model credential, a browser that would not come up a * second time, a vendored engine too old to write classes — costs a note, never * the asset. `bitmagic assets materials --auto` re-runs it on demand. */ export declare function classifyAssetMaterialsQuietly(options: ClassifyMaterialsOptions): Promise<{ slots: string[]; } | null>;