import assert from 'node:assert/strict' import { writeFile } from 'node:fs/promises' import { performance } from 'node:perf_hooks' import { validateBooleanCutInput, validateBooleanUnionInput, validateBoxInput, validateChamferInput, validateConeInput, validateCylinderInput, validateFilletInput, validateGeometryFileImport, validateGrooveInput, validateLoftInput, validateMirrorInput, validatePadInput, validatePipeInput, validatePlacementInput, validatePocketInput, validateRevolutionInput, validateTorusInput, } from '../src/facade/geometryRuntime' import type { PlanarProfile, ShapeHandle } from '../src/facade/types' type GeometryFuzzCase = { name: string; expected: 'accept' | 'reject'; invoke: () => void } let state = 0x9e3779b9 const random = () => { state ^= state << 13 state ^= state >>> 17 state ^= state << 5 return state >>> 0 } const positive = () => (random() % 100_000 + 1) / 1000 const context = () => ({ documentId: `geometry-fuzz-${random() % 7}`, documentVersion: random() % 100 }) const shape = (documentId: string, documentVersion: number, suffix: string): ShapeHandle => ({ id: `shape-${suffix}`, kernel: 'bitbybit-occt', kind: 'solid', documentId, documentVersion }) const square = (size = positive()): PlanarProfile => ({ outer: [[0, 0, 0], [size, 0, 0], [size, size, 0], [0, size, 0]] }) const elevatedSquare = (): PlanarProfile => { const profile = square() const z = positive() return { outer: profile.outer.map(([x, y]): [number, number, number] => [x, y, z]) } } const invalidScalar = () => [0, -positive(), Number.NaN, Number.POSITIVE_INFINITY][random() % 4] const createCase = (index: number): GeometryFuzzCase => { const owner = context() const base = shape(owner.documentId, Math.max(0, owner.documentVersion - 1), 'base') const tool = shape(owner.documentId, owner.documentVersion, 'tool') switch (index % 30) { case 0: return { name: 'valid-box', expected: 'accept', invoke: () => validateBoxInput({ ...owner, width: positive(), length: positive(), height: positive(), center: [positive(), -positive(), 0] }) } case 1: return { name: 'invalid-box-scalar', expected: 'reject', invoke: () => validateBoxInput({ ...owner, width: invalidScalar(), length: positive(), height: positive() }) } case 2: return { name: 'valid-cylinder', expected: 'accept', invoke: () => validateCylinderInput({ ...owner, radius: positive(), height: positive(), direction: [positive(), positive(), positive()], angle: positive() % 360 || 360 }) } case 3: return { name: 'zero-cylinder-axis', expected: 'reject', invoke: () => validateCylinderInput({ ...owner, radius: positive(), height: positive(), direction: [0, 0, 0] }) } case 4: return { name: 'zero-cone-radii', expected: 'reject', invoke: () => validateConeInput({ ...owner, radius1: 0, radius2: 0, height: positive() }) } case 5: return { name: 'valid-placement', expected: 'accept', invoke: () => validatePlacementInput({ ...owner, shape: base, placement: { translation: [positive(), 0, -positive()], rotationAxis: [0, 0, 1], rotationAngle: random() % 361 } }) } case 6: return { name: 'foreign-placement-shape', expected: 'reject', invoke: () => validatePlacementInput({ ...owner, shape: { ...base, documentId: 'foreign-document' }, placement: { translation: [0, 0, 0], rotationAxis: [0, 1, 0], rotationAngle: 0 } }) } case 7: return { name: 'valid-union', expected: 'accept', invoke: () => validateBooleanUnionInput({ ...owner, shapes: [base, tool] }) } case 8: return { name: 'short-union', expected: 'reject', invoke: () => validateBooleanUnionInput({ ...owner, shapes: [base] }) } case 9: return { name: 'empty-cut-tools', expected: 'reject', invoke: () => validateBooleanCutInput({ ...owner, base, tools: [] }) } case 10: return { name: 'fractional-fillet-index', expected: 'reject', invoke: () => validateFilletInput({ ...owner, base, radius: positive(), indexes: [random() % 10 + 0.5] }) } case 11: return { name: 'valid-chamfer', expected: 'accept', invoke: () => validateChamferInput({ ...owner, base, distance: positive(), indexes: [random() % 20] }) } case 12: return { name: 'valid-pad', expected: 'accept', invoke: () => validatePadInput({ ...owner, profile: square(), length: positive(), direction: [0, 0, 1] }) } case 13: return { name: 'self-intersecting-pad', expected: 'reject', invoke: () => validatePadInput({ ...owner, profile: { outer: [[0, 0, 0], [2, 2, 0], [0, 2, 0], [2, 0, 0]] }, length: positive() }) } case 14: return { name: 'future-pocket-base', expected: 'reject', invoke: () => validatePocketInput({ ...owner, base: { ...base, documentVersion: owner.documentVersion + 1 }, profile: square(), length: positive() }) } case 15: return { name: 'zero-revolution-axis', expected: 'reject', invoke: () => validateRevolutionInput({ ...owner, profile: square(), axisDirection: [0, 0, 0], angle: 360 }) } case 16: return { name: 'invalid-groove-angle', expected: 'reject', invoke: () => validateGrooveInput({ ...owner, base, profile: square(), axisDirection: [0, 1, 0], angle: 361 }) } case 17: return { name: 'valid-step-import', expected: 'accept', invoke: () => validateGeometryFileImport({ ...owner, format: 'step', text: `ISO-10303-21;\n/* ${random().toString(16)} */\nEND-ISO-10303-21;` }) } case 18: return { name: 'unsupported-import-format', expected: 'reject', invoke: () => validateGeometryFileImport({ ...owner, format: 'stl', text: 'solid fuzz' } as never) } case 19: return { name: 'zero-mirror-normal', expected: 'reject', invoke: () => validateMirrorInput({ ...owner, shape: base, origin: [0, 0, 0], normal: [0, 0, 0] }) } case 20: return { name: 'valid-standalone-loft', expected: 'accept', invoke: () => validateLoftInput({ ...owner, sections: [square(), elevatedSquare()] }) } case 21: return { name: 'valid-additive-loft', expected: 'accept', invoke: () => validateLoftInput({ ...owner, sections: [square(), elevatedSquare()], mode: 'additive', base }) } case 22: return { name: 'short-loft-sections', expected: 'reject', invoke: () => validateLoftInput({ ...owner, sections: [square()] }) } case 23: return { name: 'loft-hole-unsupported', expected: 'reject', invoke: () => validateLoftInput({ ...owner, sections: [{ ...square(), holes: [square(0.1).outer] }, square()] }) } case 24: return { name: 'valid-standalone-pipe', expected: 'accept', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [positive(), 0, positive()], [positive(), positive(), positive()]] }) } case 25: return { name: 'valid-subtractive-pipe', expected: 'accept', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [0, positive(), positive()]], mode: 'subtractive', base }) } case 26: return { name: 'duplicate-pipe-path-point', expected: 'reject', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [0, 0, 0]] }) } case 27: return { name: 'pipe-hole-unsupported', expected: 'reject', invoke: () => validatePipeInput({ ...owner, profile: { ...square(), holes: [square(0.1).outer] }, path: [[0, 0, 0], [0, 0, positive()]] }) } case 28: return { name: 'valid-torus', expected: 'accept', invoke: () => validateTorusInput({ ...owner, majorRadius: positive(), minorRadius: positive(), direction: [0, 0, 1], angle: random() % 360 + 1 }) } default: return { name: 'invalid-torus-radius', expected: 'reject', invoke: () => validateTorusInput({ ...owner, majorRadius: positive(), minorRadius: invalidScalar() }) } } } const durations: number[] = [] const categories = new Map() const counts = { accepted: 0, rejected: 0, validCases: 0, invalidCases: 0 } for (let index = 0; index < 2000; index += 1) { const fuzzCase = createCase(index) categories.set(fuzzCase.name, (categories.get(fuzzCase.name) ?? 0) + 1) const started = performance.now() let outcome: 'accept' | 'reject' = 'accept' let caught: unknown try { fuzzCase.invoke() } catch (error) { outcome = 'reject' caught = error } durations.push(performance.now() - started) counts[outcome === 'accept' ? 'accepted' : 'rejected'] += 1 counts[fuzzCase.expected === 'accept' ? 'validCases' : 'invalidCases'] += 1 assert.equal(outcome, fuzzCase.expected, `Geometry fuzz category ${fuzzCase.name} produced ${outcome}, expected ${fuzzCase.expected}.`) if (outcome === 'reject') assert.ok(caught instanceof Error, `Geometry fuzz category ${fuzzCase.name} rejected with a non-Error value.`) } durations.sort((left, right) => left - right) const p95Ms = durations[Math.floor(durations.length * 0.95)] assert.ok(p95Ms < 10, `Geometry input fuzz p95 exceeded 10 ms: ${p95Ms.toFixed(3)} ms.`) const report = { status: 'geometry-input-fuzz-pass', seed: '0x9e3779b9', cases: durations.length, counts, categories: Object.fromEntries([...categories].sort(([left], [right]) => left.localeCompare(right))), timing: { p95Ms: Number(p95Ms.toFixed(3)), maxMs: Number(durations.at(-1)?.toFixed(3)) }, } await writeFile(new URL('../config/qa04-geometry-fuzz-verification.json', import.meta.url), `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify(report, null, 2))