Files
workinf_Blender_Wasm/web/protocol/nonmesh-binary.ts
2026-08-12 04:47:48 -04:00

414 lines
24 KiB
TypeScript

import type {
NonMeshAttributeDataType,
NonMeshAttributeDomain,
NonMeshAttributeIR,
} from "./scene-ir";
export const NON_MESH_BINARY_SCHEMA = 1 as const;
export const NON_MESH_MAX_POINTS = 1_000_000;
export const NON_MESH_DEFAULT_CHUNK_POINTS = 65_536;
export const NON_MESH_MAX_SCENE_BYTES = 256 * 1024 * 1024;
export type NonMeshAttributeArray = Float32Array | Int32Array | Uint8Array;
export interface NonMeshAttributeSource extends NonMeshAttributeIR {
values: NonMeshAttributeArray;
}
export interface NonMeshGeometrySource {
dataId: string;
positions: Float32Array;
radii?: Float32Array;
curveOffsets?: Uint32Array;
attributes?: NonMeshAttributeSource[];
}
export interface NonMeshAttributeChunk extends NonMeshAttributeIR {
storage: "FLOAT32" | "INT32" | "UINT8";
elementOffset: number;
elementCount: number;
data: ArrayBuffer;
}
export interface NonMeshGeometryChunk {
schemaVersion: typeof NON_MESH_BINARY_SCHEMA;
dataId: string;
chunkIndex: number;
chunkCount: number;
pointOffset: number;
pointCount: number;
totalPointCount: number;
byteLength: number;
sha256: string;
positions: ArrayBuffer;
radii?: ArrayBuffer;
curveOffsets?: ArrayBuffer;
attributes: NonMeshAttributeChunk[];
}
export interface NonMeshBinaryBudget {
maxPoints: number;
maxChunkPoints: number;
maxSceneBytes: number;
}
export interface NonMeshPerformanceMetrics {
pointCount: number;
chunkCount: number;
transferredBytes: number;
elapsedMs: number;
peakBytes: number;
}
export interface NonMeshPerformanceGate {
status: "READY" | "BLOCKED";
code?: "NON_MESH_DATA_BUDGET_EXCEEDED" | "NON_MESH_PERFORMANCE_BUDGET_EXCEEDED";
metrics: NonMeshPerformanceMetrics;
}
export interface ReassembledNonMeshAttribute extends NonMeshAttributeIR {
storage: NonMeshAttributeChunk["storage"];
values: NonMeshAttributeArray;
}
export interface ReassembledNonMeshGeometry {
dataId: string;
positions: Float32Array;
radii?: Float32Array;
curveOffsets?: Uint32Array;
attributes: ReassembledNonMeshAttribute[];
}
const defaultBudget: NonMeshBinaryBudget = {
maxPoints: NON_MESH_MAX_POINTS,
maxChunkPoints: NON_MESH_DEFAULT_CHUNK_POINTS,
maxSceneBytes: NON_MESH_MAX_SCENE_BYTES,
};
function fail(message: string): never {
throw new Error(`NON_MESH_BINARY_INVALID: ${message}`);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isSafeNonNegativeInteger(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}
function isArrayBuffer(value: unknown): value is ArrayBuffer {
return value instanceof ArrayBuffer;
}
function storageFor(dataType: NonMeshAttributeDataType): NonMeshAttributeChunk["storage"] {
if (dataType === "BOOL" || dataType === "BYTE_COLOR") return "UINT8";
if (dataType === "INT") return "INT32";
return "FLOAT32";
}
function bytesPerScalar(storage: NonMeshAttributeChunk["storage"]): number {
return storage === "UINT8" ? 1 : 4;
}
function domainSize(domain: NonMeshAttributeDomain, pointCount: number, curveCount: number): number {
if (domain === "POINT") return pointCount;
if (domain === "CURVE") return curveCount;
return 1;
}
function exactArrayType(source: NonMeshAttributeSource): boolean {
const storage = storageFor(source.dataType);
return (storage === "FLOAT32" && source.values instanceof Float32Array) ||
(storage === "INT32" && source.values instanceof Int32Array) ||
(storage === "UINT8" && source.values instanceof Uint8Array);
}
function validateSource(source: NonMeshGeometrySource, budget: NonMeshBinaryBudget): number {
if (!source.dataId) fail("dataId is required");
if (!(source.positions instanceof Float32Array) || source.positions.length % 3 !== 0) fail(`${source.dataId} positions must be float32 vec3 values`);
const pointCount = source.positions.length / 3;
if (pointCount > budget.maxPoints) fail(`${source.dataId} exceeds the ${budget.maxPoints} point budget`);
if (source.positions.some((value) => !Number.isFinite(value))) fail(`${source.dataId} positions contain NaN or Infinity`);
if (source.radii && (!(source.radii instanceof Float32Array) || source.radii.length !== pointCount || source.radii.some((value) => !Number.isFinite(value) || value < 0))) fail(`${source.dataId} radii do not match the point domain`);
let curveCount = 0;
if (source.curveOffsets) {
const offsets = source.curveOffsets;
curveCount = Math.max(0, offsets.length - 1);
if (!(offsets instanceof Uint32Array) || offsets.length < 2 || offsets[0] !== 0 || offsets[offsets.length - 1] !== pointCount) fail(`${source.dataId} curve offsets do not cover the point domain`);
for (let index = 1; index < offsets.length; index++) if (offsets[index] <= offsets[index - 1]) fail(`${source.dataId} curve offsets must be strictly increasing`);
}
const names = new Set<string>();
for (const attribute of source.attributes ?? []) {
if (!attribute.name || names.has(attribute.name)) fail(`${source.dataId} has a missing or duplicate attribute name`);
names.add(attribute.name);
if (![1, 2, 3, 4].includes(attribute.components)) fail(`${source.dataId}.${attribute.name} has an invalid component count`);
if (!exactArrayType(attribute)) fail(`${source.dataId}.${attribute.name} does not use the required scalar storage`);
const elements = domainSize(attribute.domain, pointCount, curveCount);
if (attribute.values.length !== elements * attribute.components) fail(`${source.dataId}.${attribute.name} does not match its ${attribute.domain} domain`);
if (attribute.values.some((value) => !Number.isFinite(value))) fail(`${source.dataId}.${attribute.name} contains NaN or Infinity`);
}
return pointCount;
}
function copyRange(values: NonMeshAttributeArray | Float32Array | Uint32Array, scalarStart: number, scalarCount: number): ArrayBuffer {
const bytesPerValue = values.BYTES_PER_ELEMENT;
const byteStart = values.byteOffset + scalarStart * bytesPerValue;
const output = new Uint8Array(scalarCount * bytesPerValue);
output.set(new Uint8Array(values.buffer, byteStart, output.byteLength));
return output.buffer;
}
function hex(bytes: Uint8Array): string {
return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
}
async function digestChunk(chunk: Omit<NonMeshGeometryChunk, "sha256">): Promise<string> {
const subtle = globalThis.crypto?.subtle;
if (!subtle) throw new Error("NON_MESH_BINARY_INVALID: SHA-256 is unavailable");
const header = new TextEncoder().encode(JSON.stringify({
schemaVersion: chunk.schemaVersion,
dataId: chunk.dataId,
chunkIndex: chunk.chunkIndex,
chunkCount: chunk.chunkCount,
pointOffset: chunk.pointOffset,
pointCount: chunk.pointCount,
totalPointCount: chunk.totalPointCount,
attributes: chunk.attributes.map(({ name, domain, dataType, components, storage, elementOffset, elementCount }) => ({ name, domain, dataType, components, storage, elementOffset, elementCount })),
}));
const buffers = [chunk.positions, chunk.radii, chunk.curveOffsets, ...chunk.attributes.map((attribute) => attribute.data)].filter((value): value is ArrayBuffer => value instanceof ArrayBuffer);
const bytes = new Uint8Array(header.byteLength + buffers.reduce((total, buffer) => total + buffer.byteLength, 0));
bytes.set(header);
let offset = header.byteLength;
for (const buffer of buffers) {
bytes.set(new Uint8Array(buffer), offset);
offset += buffer.byteLength;
}
return hex(new Uint8Array(await subtle.digest("SHA-256", bytes)));
}
export async function chunkNonMeshGeometry(
source: NonMeshGeometrySource,
options: Partial<NonMeshBinaryBudget> = {},
): Promise<NonMeshGeometryChunk[]> {
const budget = { ...defaultBudget, ...options };
if (!Number.isSafeInteger(budget.maxPoints) || budget.maxPoints <= 0 || !Number.isSafeInteger(budget.maxChunkPoints) || budget.maxChunkPoints <= 0 || !Number.isSafeInteger(budget.maxSceneBytes) || budget.maxSceneBytes <= 0) fail("binary budget is invalid");
const totalPointCount = validateSource(source, budget);
const chunkCount = Math.max(1, Math.ceil(totalPointCount / budget.maxChunkPoints));
const curveCount = Math.max(0, (source.curveOffsets?.length ?? 1) - 1);
const chunks: NonMeshGeometryChunk[] = [];
let totalBytes = 0;
for (let chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++) {
const pointOffset = chunkIndex * budget.maxChunkPoints;
const pointCount = Math.min(budget.maxChunkPoints, totalPointCount - pointOffset);
const positions = copyRange(source.positions, pointOffset * 3, pointCount * 3);
const radii = source.radii ? copyRange(source.radii, pointOffset, pointCount) : undefined;
const curveOffsets = chunkIndex === 0 && source.curveOffsets ? copyRange(source.curveOffsets, 0, source.curveOffsets.length) : undefined;
const attributes = (source.attributes ?? []).flatMap((attribute): NonMeshAttributeChunk[] => {
const storage = storageFor(attribute.dataType);
const isPoint = attribute.domain === "POINT";
if (!isPoint && chunkIndex !== 0) return [];
const elementOffset = isPoint ? pointOffset : 0;
const elementCount = isPoint ? pointCount : domainSize(attribute.domain, totalPointCount, curveCount);
return [{
name: attribute.name,
domain: attribute.domain,
dataType: attribute.dataType,
components: attribute.components,
storage,
elementOffset,
elementCount,
data: copyRange(attribute.values, elementOffset * attribute.components, elementCount * attribute.components),
}];
});
const byteLength = positions.byteLength + (radii?.byteLength ?? 0) + (curveOffsets?.byteLength ?? 0) + attributes.reduce((total, attribute) => total + attribute.data.byteLength, 0);
totalBytes += byteLength;
if (totalBytes > budget.maxSceneBytes) fail(`${source.dataId} exceeds the ${budget.maxSceneBytes} byte scene budget`);
const unsigned = { schemaVersion: NON_MESH_BINARY_SCHEMA, dataId: source.dataId, chunkIndex, chunkCount, pointOffset, pointCount, totalPointCount, byteLength, positions, radii, curveOffsets, attributes };
chunks.push({ ...unsigned, sha256: await digestChunk(unsigned) });
}
return chunks;
}
export async function validateNonMeshGeometryChunks(
chunks: readonly unknown[],
options: Partial<NonMeshBinaryBudget> = {},
): Promise<void> {
const budget = { ...defaultBudget, ...options };
if (!Array.isArray(chunks) || chunks.length === 0) fail("at least one chunk is required");
if (!Number.isSafeInteger(budget.maxPoints) || budget.maxPoints <= 0 ||
!Number.isSafeInteger(budget.maxChunkPoints) || budget.maxChunkPoints <= 0 ||
!Number.isSafeInteger(budget.maxSceneBytes) || budget.maxSceneBytes <= 0) fail("binary budget is invalid");
const first = chunks[0];
if (!isRecord(first) || typeof first.dataId !== "string" || first.dataId.length === 0 || first.dataId.length > 256) fail("chunk dataId is invalid");
const dataId = first.dataId;
const totalPointCount = first.totalPointCount;
if (!isSafeNonNegativeInteger(totalPointCount) || totalPointCount > budget.maxPoints) fail(`${dataId} total point count exceeds its declared budget`);
let expectedPointOffset = 0;
let totalBytes = 0;
for (const [index, rawChunk] of chunks.entries()) {
if (!isRecord(rawChunk)) fail(`${dataId} chunk ${index} is invalid`);
const chunk = rawChunk;
if (chunk.schemaVersion !== NON_MESH_BINARY_SCHEMA || chunk.dataId !== dataId ||
!isSafeNonNegativeInteger(chunk.chunkIndex) || chunk.chunkIndex !== index ||
!isSafeNonNegativeInteger(chunk.chunkCount) || chunk.chunkCount !== chunks.length ||
chunk.chunkCount === 0 || !isSafeNonNegativeInteger(chunk.totalPointCount) ||
chunk.totalPointCount !== totalPointCount || !isSafeNonNegativeInteger(chunk.pointOffset) ||
chunk.pointOffset !== expectedPointOffset) fail(`${dataId} chunk sequence is inconsistent`);
if (!isSafeNonNegativeInteger(chunk.pointCount) || chunk.pointCount > budget.maxChunkPoints ||
chunk.pointOffset > Number.MAX_SAFE_INTEGER - chunk.pointCount ||
!isArrayBuffer(chunk.positions) || chunk.positions.byteLength !== chunk.pointCount * 3 * 4) fail(`${dataId} chunk ${index} point payload is invalid`);
const positions = chunk.positions;
const radii = chunk.radii;
if (radii !== undefined && (!isArrayBuffer(radii) || radii.byteLength !== chunk.pointCount * 4)) fail(`${dataId} chunk ${index} radius payload is invalid`);
const curveOffsets = chunk.curveOffsets;
if (curveOffsets !== undefined) {
if (!isArrayBuffer(curveOffsets) || index !== 0 || curveOffsets.byteLength < 8 || curveOffsets.byteLength % 4 !== 0) fail(`${dataId} curve offsets are only allowed in the first chunk`);
const offsets = new Uint32Array(curveOffsets);
if (offsets[0] !== 0 || offsets[offsets.length - 1] !== totalPointCount) fail(`${dataId} curve offsets do not cover the point domain`);
for (let offsetIndex = 1; offsetIndex < offsets.length; offsetIndex++) {
if (offsets[offsetIndex] <= offsets[offsetIndex - 1] || offsets[offsetIndex] > totalPointCount) fail(`${dataId} curve offsets are invalid`);
}
}
if (!Array.isArray(chunk.attributes)) fail(`${dataId} chunk ${index} attributes must be an array`);
const attributes = chunk.attributes;
const attributeNames = new Set<string>();
for (const rawAttribute of attributes) {
if (!isRecord(rawAttribute)) fail(`${dataId} chunk ${index} has an invalid attribute`);
const attribute = rawAttribute;
const attributeName = attribute.name;
if (typeof attributeName !== "string" || attributeName.length === 0 || attributeNames.has(attributeName)) fail(`${dataId} chunk ${index} has a duplicate or invalid attribute`);
attributeNames.add(attributeName);
if (!(["POINT", "CURVE", "INSTANCE"].includes(attribute.domain as string)) ||
!(["FLOAT", "FLOAT2", "FLOAT3", "FLOAT_COLOR", "INT", "BOOL", "BYTE_COLOR"].includes(attribute.dataType as string)) ||
!(["FLOAT32", "INT32", "UINT8"].includes(attribute.storage as string)) ||
attribute.storage !== storageFor(attribute.dataType as NonMeshAttributeDataType) ||
!isSafeNonNegativeInteger(attribute.components) || attribute.components < 1 || attribute.components > 4 ||
!isSafeNonNegativeInteger(attribute.elementOffset) || !isSafeNonNegativeInteger(attribute.elementCount) ||
!isArrayBuffer(attribute.data)) fail(`${dataId}.${attributeName} chunk metadata is invalid`);
const scalarBytes = bytesPerScalar(attribute.storage as NonMeshAttributeChunk["storage"]);
if (attribute.elementCount > Math.floor(Number.MAX_SAFE_INTEGER / attribute.components / scalarBytes)) fail(`${dataId}.${attributeName} chunk scalar count overflows the safe integer range`);
const expectedBytes = attribute.elementCount * attribute.components * scalarBytes;
if (attribute.data.byteLength !== expectedBytes) fail(`${dataId}.${attributeName} chunk payload is invalid`);
if (attribute.domain === "POINT" && (attribute.elementOffset !== chunk.pointOffset || attribute.elementCount !== chunk.pointCount)) fail(`${dataId}.${attributeName} point attribute range is invalid`);
if (attribute.domain !== "POINT" && index !== 0) fail(`${dataId}.${attributeName} curve/instance attributes must be transferred once`);
}
const measuredBytes = positions.byteLength + (radii?.byteLength ?? 0) + (curveOffsets?.byteLength ?? 0) + attributes.reduce((total, attribute) => total + (attribute as Record<string, ArrayBuffer>).data.byteLength, 0);
if (!isSafeNonNegativeInteger(chunk.byteLength) || !isSafeNonNegativeInteger(measuredBytes) || chunk.byteLength !== measuredBytes || typeof chunk.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(chunk.sha256) || await digestChunk(chunk as unknown as NonMeshGeometryChunk) !== chunk.sha256) fail(`${dataId} chunk ${index} hash or length is invalid`);
totalBytes += measuredBytes;
if (!Number.isSafeInteger(totalBytes)) fail(`${dataId} chunk bytes overflow the safe integer range`);
expectedPointOffset += chunk.pointCount;
}
if (expectedPointOffset !== totalPointCount || !Number.isSafeInteger(expectedPointOffset) || totalBytes > budget.maxSceneBytes) fail(`${dataId} chunk set exceeds its declared budget`);
}
export async function chunkNonMeshScene(
sources: readonly NonMeshGeometrySource[],
options: Partial<NonMeshBinaryBudget> = {},
): Promise<NonMeshGeometryChunk[]> {
const budget = { ...defaultBudget, ...options };
const ids = new Set<string>();
let sceneBytes = 0;
const chunks: NonMeshGeometryChunk[] = [];
for (const source of sources) {
if (ids.has(source.dataId)) fail(`duplicate dataId ${source.dataId}`);
ids.add(source.dataId);
const next = await chunkNonMeshGeometry(source, budget);
await validateNonMeshGeometryChunks(next, budget);
sceneBytes += next.reduce((total, chunk) => total + chunk.byteLength, 0);
if (sceneBytes > budget.maxSceneBytes) fail(`scene exceeds the ${budget.maxSceneBytes} byte scene budget`);
chunks.push(...next);
}
return chunks;
}
export async function benchmarkNonMeshTransfer(
source: NonMeshGeometrySource,
options: Partial<NonMeshBinaryBudget> = {},
): Promise<NonMeshPerformanceGate> {
const start = globalThis.performance?.now() ?? Date.now();
const chunks = await chunkNonMeshGeometry(source, options);
await validateNonMeshGeometryChunks(chunks, options);
const elapsedMs = (globalThis.performance?.now() ?? Date.now()) - start;
const transferredBytes = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
const sourceBytes = source.positions.byteLength + (source.radii?.byteLength ?? 0) + (source.curveOffsets?.byteLength ?? 0) +
(source.attributes ?? []).reduce((total, attribute) => total + attribute.values.byteLength, 0);
return evaluateNonMeshPerformanceGate({
pointCount: source.positions.length / 3,
chunkCount: chunks.length,
transferredBytes,
elapsedMs,
peakBytes: sourceBytes + transferredBytes,
});
}
export function nonMeshChunkTransferables(chunks: readonly NonMeshGeometryChunk[]): Transferable[] {
return chunks.flatMap((chunk) => [chunk.positions, chunk.radii, chunk.curveOffsets, ...chunk.attributes.map((attribute) => attribute.data)].filter((value): value is ArrayBuffer => value instanceof ArrayBuffer));
}
function attributeArray(storage: NonMeshAttributeChunk["storage"], length: number): NonMeshAttributeArray {
return storage === "FLOAT32" ? new Float32Array(length) : storage === "INT32" ? new Int32Array(length) : new Uint8Array(length);
}
function chunkAttributeValues(attribute: NonMeshAttributeChunk): NonMeshAttributeArray {
return attribute.storage === "FLOAT32" ? new Float32Array(attribute.data) : attribute.storage === "INT32" ? new Int32Array(attribute.data) : new Uint8Array(attribute.data);
}
/** Reassembles already-transferred WNM chunks while rechecking all cross-chunk invariants synchronously. */
export function reassembleNonMeshGeometry(dataId: string, chunks: readonly NonMeshGeometryChunk[]): ReassembledNonMeshGeometry {
const matching = chunks.filter((chunk) => chunk.dataId === dataId).sort((left, right) => left.chunkIndex - right.chunkIndex);
if (matching.length === 0) fail(`${dataId} has no geometry chunks`);
const first = matching[0];
if (first.totalPointCount > NON_MESH_MAX_POINTS || matching.length !== first.chunkCount) fail(`${dataId} chunk set is incomplete`);
const positions = new Float32Array(first.totalPointCount * 3);
const radii = matching.some((chunk) => chunk.radii) ? new Float32Array(first.totalPointCount) : undefined;
const descriptors = new Map<string, ReassembledNonMeshAttribute>();
let expectedPointAttributes: Set<string> | undefined;
let pointOffset = 0;
let totalBytes = 0;
for (const [chunkIndex, chunk] of matching.entries()) {
if (chunk.schemaVersion !== NON_MESH_BINARY_SCHEMA || chunk.chunkIndex !== chunkIndex || chunk.chunkCount !== matching.length ||
chunk.totalPointCount !== first.totalPointCount || chunk.pointOffset !== pointOffset || chunk.positions.byteLength !== chunk.pointCount * 12) fail(`${dataId} chunk sequence is inconsistent`);
positions.set(new Float32Array(chunk.positions), chunk.pointOffset * 3);
if (radii) {
if (!chunk.radii || chunk.radii.byteLength !== chunk.pointCount * 4) fail(`${dataId} radius chunks are incomplete`);
radii.set(new Float32Array(chunk.radii), chunk.pointOffset);
}
const pointAttributes = new Set(chunk.attributes.filter((attribute) => attribute.domain === "POINT").map((attribute) => `${attribute.domain}\0${attribute.name}`));
if (!expectedPointAttributes) expectedPointAttributes = pointAttributes;
else if (pointAttributes.size !== expectedPointAttributes.size || [...expectedPointAttributes].some((key) => !pointAttributes.has(key))) fail(`${dataId} point attribute chunks are incomplete`);
for (const attribute of chunk.attributes) {
const key = `${attribute.domain}\0${attribute.name}`;
let target = descriptors.get(key);
const scalarCount = attribute.elementCount * attribute.components;
if (!target) {
const totalElements = attribute.domain === "POINT" ? first.totalPointCount : attribute.elementCount;
target = { name: attribute.name, domain: attribute.domain, dataType: attribute.dataType, components: attribute.components, storage: attribute.storage, values: attributeArray(attribute.storage, totalElements * attribute.components) };
descriptors.set(key, target);
}
if (target.storage !== attribute.storage || target.dataType !== attribute.dataType || target.components !== attribute.components ||
attribute.data.byteLength !== scalarCount * bytesPerScalar(attribute.storage)) fail(`${dataId}.${attribute.name} chunks are inconsistent`);
target.values.set(chunkAttributeValues(attribute), attribute.elementOffset * attribute.components);
}
totalBytes += chunk.byteLength;
pointOffset += chunk.pointCount;
}
if (pointOffset !== first.totalPointCount || totalBytes > NON_MESH_MAX_SCENE_BYTES || positions.some((value) => !Number.isFinite(value)) || radii?.some((value) => !Number.isFinite(value) || value < 0)) fail(`${dataId} reassembled geometry is invalid`);
const curveOffsets = first.curveOffsets ? new Uint32Array(first.curveOffsets.slice(0)) : undefined;
if (curveOffsets) {
if (curveOffsets.length < 2 || curveOffsets[0] !== 0 || curveOffsets.at(-1) !== first.totalPointCount) fail(`${dataId} curve offsets do not cover the point domain`);
for (let index = 1; index < curveOffsets.length; index++) if (curveOffsets[index] <= curveOffsets[index - 1]) fail(`${dataId} curve offsets are invalid`);
}
return { dataId, positions, radii, curveOffsets, attributes: [...descriptors.values()] };
}
export function evaluateNonMeshPerformanceGate(metrics: NonMeshPerformanceMetrics): NonMeshPerformanceGate {
const valid = Object.values(metrics).every((value) => Number.isFinite(value) && value >= 0) &&
Number.isSafeInteger(metrics.pointCount) && Number.isSafeInteger(metrics.chunkCount) && Number.isSafeInteger(metrics.transferredBytes) && Number.isSafeInteger(metrics.peakBytes);
if (!valid || metrics.pointCount > NON_MESH_MAX_POINTS || metrics.transferredBytes > NON_MESH_MAX_SCENE_BYTES) return { status: "BLOCKED", code: "NON_MESH_DATA_BUDGET_EXCEEDED", metrics };
if (metrics.pointCount === NON_MESH_MAX_POINTS && (metrics.elapsedMs > 2_000 || metrics.peakBytes > 512 * 1024 * 1024 || metrics.chunkCount > Math.ceil(NON_MESH_MAX_POINTS / NON_MESH_DEFAULT_CHUNK_POINTS))) {
return { status: "BLOCKED", code: "NON_MESH_PERFORMANCE_BUDGET_EXCEEDED", metrics };
}
return { status: "READY", metrics };
}