card harness dynamic changes

This commit is contained in:
2026-09-11 21:19:08 -07:00
parent 8fcda5f2b7
commit 56f010a71e
25 changed files with 1055 additions and 126 deletions

View File

@@ -5,6 +5,7 @@ import { installDOM, TestCanvas, geometryHash, wrapperCases } from './test-suppo
const { createFoilWrapper } = await import('../src/foilWrapper.ts')
const { PackOpening } = await import('../src/packOpening.ts')
const { packStyles, defaultPackStyle, createPackTexture } = await import('../src/packDesigns.ts')
// Captured from the pre-optimization implementation, including normals, UVs and bounds.
const originalHashes = [
@@ -56,6 +57,49 @@ test('only changed surfaces recompute normals, bounds and upload buffers', (t) =
assert.deepEqual(boundsSpies.map((spy) => spy.mock.callCount()), [2, 1, 1])
})
test('three cached pack designs change all printed surfaces without changing geometry or materials', () => {
installDOM()
const wrapper = createFoilWrapper()
assert.equal(packStyles.length, 3)
assert.equal(wrapper.style, defaultPackStyle)
assert.equal(wrapper.textures.length, 6)
const materials = wrapper.root.children.map(mesh => mesh.material)
const maps = new Set()
for (const style of packStyles) {
wrapper.setStyle(style)
assert.equal(wrapper.style, style)
const [front, back, strip] = wrapper.root.children.map(mesh => mesh.material.map)
assert.equal(front, strip, 'ribbon must use the same printed sheet as the front')
assert.notEqual(front, back)
maps.add(front)
maps.add(back)
for (const texture of [front, back]) {
assert.equal(texture.image.width, 1024)
assert.equal(texture.image.height, 1400)
assert.equal(texture.colorSpace, THREE.SRGBColorSpace)
}
assert.deepEqual(wrapper.root.children.map(mesh => mesh.material), materials)
wrapperCases.forEach(([tear, detach, mouth, touch, exit], index) => {
wrapper.deform(tear, detach, mouth, new THREE.Vector3(...touch), exit)
assert.equal(geometryHash(wrapper), originalHashes[index], `${style}, pose ${index}`)
})
wrapper.setStyle(style)
assert.equal(wrapper.root.children[0].material.map, front, 'selecting the same style reuses its texture')
}
assert.equal(maps.size, 6)
const before = wrapper.root.children.map(mesh => mesh.material.map)
assert.throws(() => wrapper.setStyle('unknown'), /Unknown pack style/)
assert.deepEqual(wrapper.root.children.map(mesh => mesh.material.map), before)
wrapper.dispose()
})
test('pack textures reject invalid styles and unavailable drawing contexts', (t) => {
installDOM()
assert.throws(() => createPackTexture('unknown'))
t.mock.method(TestCanvas.prototype, 'getContext', () => null)
assert.throws(() => createPackTexture(defaultPackStyle))
})
function setupPack(t, reducedMotion = false) {
installDOM(reducedMotion)
let now = 1000
@@ -158,6 +202,39 @@ test('interruption and mode changes preserve progress without deforming in point
assert.equal(pack.state, 'stackReady')
})
test('style switching preserves partial tears, card order, restart selection and retained mode state', (t) => {
const { pack, canvas, step } = setupPack(t)
let reveals = 0
canvas.addEventListener('packreveal', () => { reveals++ })
pack.primary()
step(600)
pack.pointerDown(pointer(1, 0, 0))
pack.pointerUp(pointer(1, 0, 0))
const progress = pack.tearProgress
const geometry = geometryHash(pack.wrapper)
const cardSpecs = pack.cards.map(card => card.material.uuid)
for (const style of packStyles) {
pack.setStyle(style)
assert.equal(pack.style, style)
assert.equal(pack.tearProgress, progress)
assert.equal(pack.paused, true)
assert.equal(pack.state, 'opening')
assert.equal(geometryHash(pack.wrapper), geometry)
assert.deepEqual(pack.cards.map(card => card.material.uuid), cardSpecs)
assert.equal(reveals, 0)
}
pack.setStyle(packStyles[0])
pack.setActive(false)
pack.setActive(true)
assert.equal(pack.style, packStyles[0])
assert.equal(pack.tearProgress, progress)
pack.restart()
assert.equal(pack.style, packStyles[0])
assert.equal(pack.state, 'sealed')
assert.equal(pack.tearProgress, 0)
pack.dispose()
})
test('a second finger cancels seam dragging for pinch without losing the partial tear', (t) => {
const { pack, canvas, step } = setupPack(t)
const seam = pack.seamScreenBounds()
@@ -267,7 +344,7 @@ test('GPU preparation uploads unique textures and compiles hidden fronts and bot
finishCompile()
await preparation
assert.equal(uploaded.length, new Set(uploaded).size)
assert.equal(uploaded.length, 9, '4 artwork/mask textures, normal, environment, back and 2 wrapper maps')
assert.equal(uploaded.length, 13, '4 artwork/mask textures, normal, environment, back and 6 wrapper maps')
for (const { artwork, mask } of Object.values(pack.options.textures)) {
assert.ok(uploaded.includes(artwork))
assert.ok(uploaded.includes(mask))
@@ -275,6 +352,7 @@ test('GPU preparation uploads unique textures and compiles hidden fronts and bot
assert.ok(uploaded.includes(backTexture))
assert.ok(uploaded.includes(pack.options.normalMap))
assert.ok(uploaded.includes(scene.environment))
for (const texture of pack.wrapper.textures) assert.ok(uploaded.includes(texture), 'inactive pack styles must also be GPU-ready')
assert.deepEqual(compiledEdges, [[0.02, 0.02, 0.02], [0, 0.02, 0.18]])
assert.deepEqual(pack.cards.map((card) => card.edge.clearcoat), [0.02, 0.02, 0.02])
assert.equal(geometryHash(pack.wrapper), before)
@@ -333,8 +411,7 @@ test('pack cleanup disposes owned resources without disposing shared or caller-o
materials.add(object.material)
})
materials.delete(pack.options.backMaterial)
const resources = [...geometries, ...materials,
...new Set(pack.wrapper.root.children.map((mesh) => mesh.material.map))]
const resources = [...geometries, ...materials, ...pack.wrapper.textures]
const ownedSpies = resources.map((resource) => t.mock.method(resource, 'dispose'))
pack.dispose()
assert.equal(pack.root.parent, null)