import type { Environment } from '../config/environments.js'; import { withVoxelizeSession } from '../forge/voxelize-session.js'; import type { ProjectContext } from '../project/context.js'; import { type VoxelizeFlags, type VoxelizeGlb } from './voxelize-glb.js'; /** Which stored source a re-bake will read. */ export type RevoxelizeSource = { kind: 'master'; masterUrl: string; } | { kind: 'glb'; glbUrl: string; }; export interface RevoxelizeOptions { context: ProjectContext; environment: Environment; token: string; assetId: string; voxel: VoxelizeFlags; log: (message: string) => void; fetchImpl?: typeof globalThis.fetch; /** Injected in tests so the lane runs without Chrome. */ session?: typeof withVoxelizeSession; /** Injected in tests; used for the GLB source, which reuses the shared bake. */ voxelize?: VoxelizeGlb; } export interface RevoxelizeResult { assetId: string; assetName: string; source: RevoxelizeSource['kind']; assetUrl?: string; voxelCount?: number; /** Placed instances that changed with it — the blast radius, reported rather than assumed. */ instanceCount: number; } /** * The stored source to re-bake, preferring the master. * * The master is preferred when both are present because it is a hundredth of the bytes for the * same answer at or below its own resolution. `assertCanGoFiner` is what catches the case where * that preference would cost detail. */ export declare function chooseSource(asset: Record): RevoxelizeSource | null; /** The asset to re-bake, refused before the browser starts if it cannot be. */ export declare function findRevoxelizableAsset(world: Record, assetId: string): { asset: Record; source: RevoxelizeSource; }; /** * A voxel size fit to show a person and to store. * * The `.vxl` header holds float32, so the size read back out of it is `0.019999999552965164` where * the creator asked for `0.01` and got a doubling. Six significant figures is far beyond any * resolution this means anything at, and it puts the number back in the vocabulary the flag uses — * both in the note and in world.json, which would otherwise carry the artifact into every diff. */ export declare function tidyVoxelSize(value: number): number; /** * What the bake actually produced, when that is not what was asked for. * * The engine's compiler halves resolution until the model fits a leaf budget * (`compileVoxelModelToVxlAsset`: `while (cells.size > maxLeaves) { downsample; vs *= 2 }`, budget * 600k for a working asset). It says so — but only as a `console.warn` inside the headless Chrome * the CLI drives, which reaches nobody, and `REVOXELIZE_FROM_VXL_MASTER_RESULT` did not carry it. * So a request just under the knee came back at exactly twice the size, silently, with a zero exit * code. * * The effect is worse than "not quite what you asked for", which is why this is said out loud * rather than left to whoever compares two numbers in world.json: the result is NON-MONOTONIC. * One step below the knee the asset is coarser than a COARSER request would have produced — * measured on a 2.78 m stall with a 512³ master, 0.011 m gave 556k voxels and 0.0105 m gave 141k. * Nothing about "I asked for finer voxels and got a blockier model" suggests looking at a budget. * * Detected from the .vxl header rather than from anything the engine says, deliberately: the * header is what was actually written, and the CLI drives whatever engine the project vendored — * including ones that predate any reply field carrying this. */ export declare function describeBudgetCoarsening(requested: number, achieved: number): { coarsened: boolean; factor: number; note: string; }; /** * The height a master re-bake will be resampled to. * * `--height` wins. Otherwise the height the asset was BAKED at, which the master path records in * `voxelizeSettings` for exactly this. Falling through to neither is the case worth naming: a * master is a grid, not a size, so the engine has nothing to derive a height from and lands on its * own `DEFAULT_TARGET_HEIGHT` of 2 m — silently returning a 10 m building as a 2 m one. The mesh * branch has no such gap, which is why `--height`'s "omit to keep the source's own size" only ever * described half of this command. */ export declare function resolveMasterTargetHeight(asset: Record, requested: number | undefined): number | undefined; /** * Refuse a master re-bake that asks for more detail than the master holds. * * The engine would refuse it too — `resampleMaster` throws "re-forge at a higher resolution rather * than upsampling" — but only after the browser, vite and the page have started. Saying it here * costs nothing and names the thing to do instead, which the engine's message cannot know. * * The floor is derived from the MASTER, not from the previous bake. Those are very different * numbers: a 512³ master baked once at 0.1 m holds detail down to about `targetHeight / 512`, and * refusing everything below 0.1 m would make that headroom unreachable from the CLI — which is the * whole reason the master is stored rather than the working asset. * * Deliberately PERMISSIVE. The engine's real limit is the master's occupied span along the up * axis, which is at most its resolution and usually less, so a request between * `targetHeight / resolution` and `targetHeight / spanUp` still throws in the browser. That is the * right way round: this pre-flight exists to avoid paying for a browser on a request that cannot * work, never to refuse a size the master can serve. The engine stays the authority. * * Falls back to the old "no finer than the last bake" rule when either field is missing, so assets * written before `sourceVxlMasterResolution` existed — or hand-edited ones — keep a guard rather * than losing it. */ export declare function assertMasterCanGoFiner(asset: Record, requestedVoxelSize: number, targetHeight: number | undefined, name: string): void; export declare function revoxelizeAsset(options: RevoxelizeOptions): Promise;