feat: define versioned sketch solver protocol

This commit is contained in:
2026-08-02 23:49:35 -04:00
parent 4d94109627
commit e0b4817ec0
6 changed files with 227 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ import { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, m
import { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from '../src/facade/topologyReferences'
import { captureSignatureTopologyHistory } from '../src/facade/topologyHistory'
import { createSketch, solveSketch } from '../src/facade/sketcher'
import { BasicSketchSolverProvider, SKETCH_SOLVER_PROTOCOL_VERSION, SketchSolverCoordinator, SketchSolverUnavailableError, UnavailablePlanegcsProvider, runSketchSolverReplay, type SketchSolverProvider, type SketchSolverRequest } from '../src/facade/sketchSolverProtocol'
import { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
import { inspectFcstdArchive } from '../src/facade/fcstd'
import { assertShapeHandleIntegrity, normalizeBitbybitMesh, validateBooleanUnionInput, validateBoxInput, validateChamferInput, validateConeInput, validateCylinderInput, validateFilletInput, validatePadInput, validatePlacementInput, validatePlanarProfile, validateRevolutionInput, validateSphereInput } from '../src/facade/geometryRuntime'
@@ -254,6 +255,53 @@ test('Sketcher solver supports diameter, symmetric and tangent constraints at th
assert.equal((result.snapshot.geometry.find((geometry) => geometry.id === 'circle-1') as Extract<typeof result.snapshot.geometry[number], { type: 'circle' }>).center.y, 2)
})
test('sketch solver protocol exposes honest capabilities and deterministic replay', async () => {
const provider = new BasicSketchSolverProvider()
assert.deepEqual(provider.capabilities().engine, 'typescript-basic')
assert.equal(provider.capabilities().compatibility, 'experimental')
const fixture = createSketch('protocol-sketch', [{ id: 'line-1', type: 'line', start: { x: 0, y: 0 }, end: { x: 4, y: 2 } }], [
{ id: 'horizontal-1', type: 'horizontal', geometryId: 'line-1' },
])
const replay = await runSketchSolverReplay(provider, [{
id: 'basic-horizontal',
request: { requestId: 'replay-1', documentId: 'doc', documentVersion: 1, snapshot: fixture },
expected: { status: 'under-constrained', degreesOfFreedom: 3, maxResidual: 1e-7 },
}])
assert.deepEqual(replay, [{ id: 'basic-horizontal', passed: true, differences: [] }])
const unavailable = new UnavailablePlanegcsProvider()
assert.equal(unavailable.capabilities().availability, 'unavailable')
assert.equal(unavailable.capabilities().engine, 'planegcs-wasm')
await assert.rejects(() => unavailable.solve({} as SketchSolverRequest, new AbortController().signal), SketchSolverUnavailableError)
})
test('sketch solver coordinator rejects cancelled and stale generations', async () => {
let documentVersion = 1
let releaseFirst: (() => void) | undefined
const basic = new BasicSketchSolverProvider()
const delayed: SketchSolverProvider = {
capabilities: () => basic.capabilities(),
solve: (request, signal) => new Promise((resolve, reject) => {
const release = () => basic.solve(request, signal).then(resolve, reject)
if (request.requestId === 'first') releaseFirst = release
else release()
}),
}
const coordinator = new SketchSolverCoordinator(() => documentVersion)
const snapshot = createSketch('coordinator')
const first = coordinator.solve(delayed, { requestId: 'first', documentId: 'doc', documentVersion: 1, snapshot })
const second = coordinator.solve(delayed, { requestId: 'second', documentId: 'doc', documentVersion: 1, snapshot })
releaseFirst?.()
assert.equal((await first).status, 'cancelled')
assert.equal((await second).status, 'completed')
documentVersion = 2
const stale = await coordinator.solve(basic, { requestId: 'stale', documentId: 'doc', documentVersion: 1, snapshot })
assert.equal(stale.status, 'stale')
assert.equal(stale.generation, 3)
assert.equal(SKETCH_SOLVER_PROTOCOL_VERSION, 1)
})
test('sketcher operations are facade transactions and survive the project fallback store', async () => {
const facade = createMockFacade()
const before = facade.app.document.getActive().version