feat: add versioned topology reference migration

This commit is contained in:
2026-08-02 23:31:33 -04:00
parent 1ed35273a5
commit 9def4708ea
7 changed files with 180 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ import { createSqliteProjectPersistence, PersistenceWriteQueue, ProjectAutosaveS
import { DependencyGraph } from '../src/facade/dependencyGraph'
import { evaluateQuantityExpression, quantityFromNumber } from '../src/facade/units'
import { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from '../src/facade/topologyNaming'
import { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from '../src/facade/topologyReferences'
import { createSketch, solveSketch } from '../src/facade/sketcher'
import { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator, type RecomputeGeometryRuntime } from '../src/facade/recomputeEngine'
import { inspectFcstdArchive } from '../src/facade/fcstd'
@@ -148,11 +149,37 @@ test('topology signatures are independent of transient face indexes and flag amb
const duplicate = createSubshapeRefs('shape-b', 2, [square, square])
assert.equal(duplicate.refs[0].status, 'ambiguous')
assert.deepEqual(duplicate.refs[0].candidates, [duplicate.refs[0].persistentId, duplicate.refs[1].persistentId])
assert.notEqual(duplicate.refs[0].persistentId, duplicate.refs[1].persistentId)
const matches = matchSubshapes([{ ref: first.refs[0], signature: first.signatures[0] }], [{ ref: duplicate.refs[0], signature: duplicate.signatures[0] }])
assert.equal(matches[0].status, 'stable')
assert.equal(matches[0].previousId, first.refs[0].persistentId)
const deleted = matchSubshapes([{ ref: first.refs[0], signature: first.signatures[0] }, { ref: duplicate.refs[1], signature: duplicate.signatures[1] }], [{ ref: duplicate.refs[0], signature: duplicate.signatures[0] }])
assert.equal(matches[0].status, 'ambiguous')
const shifted = { ...square, vertexCoord: square.vertexCoord.map((value, index) => index % 3 === 0 ? value + 10 : value) }
const previousShifted = createSubshapeRefs('shape-shifted', 1, [shifted])
const currentSingle = createSubshapeRefs('shape-current', 2, [square])
const deleted = matchSubshapes([{ ref: first.refs[0], signature: first.signatures[0] }, { ref: previousShifted.refs[0], signature: previousShifted.signatures[0] }], [{ ref: currentSingle.refs[0], signature: currentSingle.signatures[0] }])
assert.equal(deleted.at(-1)?.status, 'deleted')
const translated = { ...square, vertexCoord: square.vertexCoord.map((value, index) => index % 3 === 0 ? value + 5 : value) }
const moved = createSubshapeRefs('shape-moved', 3, [translated])
const movedMatches = matchSubshapes([{ ref: first.refs[0], signature: first.signatures[0] }], [{ ref: moved.refs[0], signature: moved.signatures[0] }])
assert.equal(movedMatches[0].status, 'stable')
assert.equal(movedMatches[0].previousId, first.refs[0].persistentId)
})
test('versioned TopoRefs serialize without transient indexes and resolve migration states', () => {
const face = { vertexCoord: [0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0], normalCoord: [], triIndexes: [0, 1, 2, 0, 2, 3] }
const previous = createSubshapeRefs('shape-old', 4, [face])
const record = createPersistedTopoRef('pad', previous.refs[0], 7)
const restored = parseTopoRef(serializeTopoRef(record))
assert.deepEqual(restored, record)
assert.equal(resolveTopoRef(restored, previous.refs).status, 'resolved')
assert.throws(() => parseTopoRef(JSON.stringify({ ...record, faceIndex: 1 })), /must not persist transient faceIndex/)
const split = createSubshapeRefs('shape-new', 5, [face, face])
const migration = migrateTopoRefs('pad', [{ ref: previous.refs[0], signature: previous.signatures[0] }], split.refs.map((ref, index) => ({ ref, signature: split.signatures[index] })), 8)
assert.equal(migration.counts.ambiguous, 2)
assert.equal(migration.counts.deleted, 0)
assert.ok(migration.records.every((candidate) => candidate.status === 'ambiguous' && candidate.candidates && candidate.candidates.length >= 2))
assert.equal(resolveTopoRef(restored, split.refs).status, 'ambiguous')
})
test('edge and vertex topology signatures are orientation/index independent', () => {