Files
Sanctification/card-harness/scripts/pack-performance.test.mjs

600 lines
24 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import * as THREE from 'three'
import { installDOM, TestCanvas, geometryHash, wrapperCases } from './test-support.mjs'
const { createFoilWrapper } = await import('../src/foilWrapper.ts')
const { PackOpening, packSize, rollPackContents, packFinishes, packSubstrates } = 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 = [
'f20984ff506a2b6ac557e30592b4d3b990efdd5e9f335a2655fe78567e240d1a',
'2ae0cf1d2ba9fcc29dde5cd6c5cca0862e22466ed5a8c4257a3d0d0075b9d500',
'f8c912718e192139b6d33843c2c5527e765372f339897033c864909929b59bbe',
'1f70db18f24f8a629a0765e21cb1d73f377ffb816e2e7893edaabe9616053441',
'64e019c59fa6fe120ac044a01b2ba852ce09e86cd4abe6d286bb18d456bd8700',
'140e2ad4fdc339d671a8b19da32c89b74c98e785b05250f4a4c491edf23a460a',
'cacbbd787ff9f47acddf0227d31fe8051adf06d6e401a0f53a69a7d81c3c12fc',
'7a76c0eeea7cf22a3f6d888c43df76a2dcdeaad418d91c9cee5e5102a09ebf49',
'e5625218a4d89f58bb8a935d30fa6aee80613b41868bad334079fca6da33e397',
'f20984ff506a2b6ac557e30592b4d3b990efdd5e9f335a2655fe78567e240d1a',
'a6f746d8d729cf77068a05a24269024cd30c1a807828b907c559c140645a3b86',
'91fd70b90a7a40976ccfe9ad6727af5f12db6e8f20ac414503f5fe10aaee4d8b',
]
test('wrapper geometry exactly matches the original across tear, dent, mouth and restart poses', () => {
installDOM()
const wrapper = createFoilWrapper()
wrapperCases.forEach(([tear, detach, mouth, touch, exit], index) => {
wrapper.deform(tear, detach, mouth, new THREE.Vector3(...touch), exit)
assert.equal(geometryHash(wrapper), originalHashes[index], `pose ${index}`)
})
})
test('only changed surfaces recompute normals, bounds and upload buffers', (t) => {
installDOM()
const wrapper = createFoilWrapper()
const meshes = wrapper.root.children
const normalSpies = meshes.map((mesh) => t.mock.method(mesh.geometry, 'computeVertexNormals'))
const boundsSpies = meshes.map((mesh) => t.mock.method(mesh.geometry, 'computeBoundingSphere'))
const versions = () => meshes.map((mesh) => [
mesh.geometry.getAttribute('position').version, mesh.geometry.getAttribute('normal').version,
])
const before = versions()
const touch = new THREE.Vector3()
wrapper.deform(0, 0, 0, touch)
assert.deepEqual(versions(), before)
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [0, 0, 0])
wrapper.deform(0.5, 0, 0, touch)
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [0, 0, 1])
assert.deepEqual(versions().slice(0, 2), before.slice(0, 2))
wrapper.deform(0.5, 0, 0, touch.set(0.4, 1.7, 0.5))
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [1, 0, 1])
wrapper.deform(0.5, 0, 0.5, touch)
assert.deepEqual(normalSpies.map((spy) => spy.mock.callCount()), [2, 1, 1])
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, contents = [
{ fixture: 'David', finish: 'Printed ink', substrate: 'Linen' },
{ fixture: 'Timothy', finish: 'Foil', substrate: 'Paper' },
{ fixture: 'David', finish: 'Holographic', substrate: 'Metal' },
], textures = new Map([
['David', { artwork: new THREE.Texture(), mask: new THREE.Texture() }],
['Timothy', { artwork: new THREE.Texture(), mask: new THREE.Texture() }],
])) {
installDOM(reducedMotion)
let now = 1000
t.mock.method(performance, 'now', () => now)
const canvas = new TestCanvas()
const pack = new PackOpening({
canvas,
contents,
textures,
backMaterial: new THREE.MeshPhysicalMaterial(),
normalMap: new THREE.Texture(),
environment: new THREE.CubeTexture(),
lightPosition: new THREE.Vector3(2.2, 2.5, 4.4),
onChange() {},
})
pack.resize(canvas.width, canvas.height)
pack.setActive(true)
pack.tick(now)
const deform = t.mock.method(pack.wrapper, 'deform')
return {
pack, canvas, deform,
step(ms = 16) { now += ms; pack.tick(now) },
}
}
function pointer(id, x, y) {
return { pointerId: id, clientX: x, clientY: y, button: 0 }
}
test('Leather pack cards start at 0.45 detail while other substrates retain 0.14', (t) => {
const { pack } = setupPack(t, false, [
{ fixture: 'David', finish: 'Printed ink', substrate: 'Leather' },
{ fixture: 'David', finish: 'Foil', substrate: 'Paper' },
{ fixture: 'Timothy', finish: 'Holographic', substrate: 'Metal' },
])
assert.deepEqual(pack.cards.map(card => card.material.uniforms.normalStrength.value), [0.45, 0.14, 0.14])
assert.deepEqual(pack.cards.map(card => card.material.uniforms.finishMode.value), [0, 1, 2])
assert.ok(pack.cards.every(card => !card.front.visible), 'material defaults must not reveal hidden cards')
pack.dispose()
})
test('Metal pack cards use vertical grain across wrapper changes and restart', (t) => {
const { pack } = setupPack(t, false, [
{ fixture: 'David', finish: 'Printed ink', substrate: 'Metal' },
{ fixture: 'Timothy', finish: 'Foil', substrate: 'Metal' },
{ fixture: 'David', finish: 'Holographic', substrate: 'Metal' },
])
const check = () => {
assert.deepEqual(pack.cards.map(card => card.material.uniforms.metalBrushHorizontal.value), [false, false, false])
assert.ok(pack.cards.every(card => !card.front.visible))
}
check()
pack.setStyle(packStyles.find(style => style !== defaultPackStyle))
pack.restart()
check()
pack.dispose()
})
test('pack optional text masks are shared, GPU-prepared once, and remain tied to their own card', async (t) => {
const textMask = new THREE.Texture()
const textures = new Map([
['David', { artwork: new THREE.Texture(), mask: new THREE.Texture(), textMask }],
['Timothy', { artwork: new THREE.Texture(), mask: new THREE.Texture() }],
])
const { pack } = setupPack(t, false, undefined, textures)
pack.setActive(false)
const uploaded = []
await pack.prepareGPU({ initTexture(texture) { uploaded.push(texture) }, async compileAsync() {} }, new THREE.Scene())
assert.equal(uploaded.filter(texture => texture === textMask).length, 1)
assert.deepEqual(pack.cards.map(card => card.material.uniforms.hasTextMask.value), [true, false, true])
assert.equal(pack.cards[0].material.uniforms.textMask.value, pack.cards[2].material.uniforms.textMask.value)
assert.ok(pack.cards.every(card => !card.front.visible))
pack.syncLighting(pack.cards[1].material)
assert.deepEqual(pack.cards.map(card => card.material.uniforms.hasTextMask.value), [true, false, true])
const dispose = t.mock.method(textMask, 'dispose')
pack.dispose()
assert.equal(dispose.mock.callCount(), 0, 'caller owns optional card textures')
})
test('a burst of tear events and the touch dent deform at most once per rendered frame', (t) => {
const { pack, deform, step } = setupPack(t)
const seam = pack.seamScreenBounds()
const start = pointer(1, seam.left + 10, seam.y)
pack.pointerDown(start)
for (let offset = 10; offset <= 100; offset += 10) {
pack.pointerMove(pointer(1, start.clientX + offset, start.clientY))
}
const progress = pack.tearProgress
assert.ok(progress > 0 && progress < 1)
assert.equal(deform.mock.callCount(), 0)
step()
assert.equal(deform.mock.callCount(), 1)
assert.equal(deform.mock.calls[0].arguments[0], progress)
assert.ok(deform.mock.calls[0].arguments[3].z > 0)
pack.pointerMove(pointer(1, start.clientX + 20, start.clientY))
assert.equal(pack.tearProgress, progress, 'pulling backward must not reseal')
pack.pointerUp(pointer(1, start.clientX + 20, start.clientY), true)
step()
assert.equal(pack.tearProgress, progress)
assert.equal(pack.paused, true)
pack.primary()
step(3200)
assert.equal(pack.state, 'stackReady')
})
test('body dragging batches dent changes without updating the back or ribbon', (t) => {
const { pack, deform, step } = setupPack(t)
pack.pointerDown(pointer(1, 195, 368))
for (let x = 200; x <= 220; x += 5) pack.pointerMove(pointer(1, x, 368))
assert.equal(deform.mock.callCount(), 0)
const [front, back, strip] = pack.wrapper.root.children
const backVersion = back.geometry.getAttribute('position').version
const stripVersion = strip.geometry.getAttribute('position').version
const frontVersion = front.geometry.getAttribute('position').version
step()
assert.equal(deform.mock.callCount(), 1)
assert.ok(front.geometry.getAttribute('position').version > frontVersion)
assert.equal(back.geometry.getAttribute('position').version, backVersion)
assert.equal(strip.geometry.getAttribute('position').version, stripVersion)
})
test('interruption and mode changes preserve progress without deforming in pointer handlers', (t) => {
const { pack, deform, step } = setupPack(t)
pack.primary()
step(600)
const calls = deform.mock.callCount()
const versions = pack.wrapper.root.children.map((mesh) => mesh.geometry.getAttribute('position').version)
pack.pointerDown(pointer(1, 0, 0))
const progress = pack.tearProgress
assert.equal(deform.mock.callCount(), calls)
assert.equal(pack.paused, true)
pack.pointerUp(pointer(1, 0, 0))
step(600)
assert.equal(pack.tearProgress, progress)
assert.deepEqual(
pack.wrapper.root.children.map((mesh) => mesh.geometry.getAttribute('position').version), versions,
)
const pausedCalls = deform.mock.callCount()
step()
assert.equal(deform.mock.callCount(), pausedCalls)
pack.setActive(false)
step(500)
pack.setActive(true)
assert.equal(pack.tearProgress, progress)
assert.equal(pack.view.action, 'Continue')
pack.primary()
step(3200)
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()
const start = pointer(1, seam.left + 10, seam.y)
pack.pointerDown(start)
pack.pointerMove(pointer(1, start.clientX + 70, seam.y))
step()
const progress = pack.tearProgress
pack.pointerDown(pointer(2, start.clientX + 170, seam.y))
pack.pointerMove(pointer(2, start.clientX + 200, seam.y))
assert.equal(pack.tearProgress, progress)
assert.ok(pack.zoom < 1)
pack.restart()
step()
assert.equal(canvas.captures.size, 0)
assert.equal(pack.tearProgress, 0)
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
})
test('full-pull release continues opening and hidden wrappers do no geometry work', (t) => {
const { pack, deform, step } = setupPack(t)
const seam = pack.seamScreenBounds()
pack.pointerDown(pointer(1, seam.left + 10, seam.y))
pack.pointerMove(pointer(1, seam.right + 10, seam.y))
pack.pointerUp(pointer(1, seam.right + 10, seam.y))
assert.equal(pack.tearProgress, 1)
step(3200)
assert.equal(pack.state, 'stackReady')
assert.equal(pack.wrapper.root.visible, false)
assert.equal(deform.mock.callCount(), 0)
pack.restart()
step()
assert.equal(pack.wrapper.root.visible, true)
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
})
test('skip retains ordered lift/reveal states and emits each reveal and completion once', (t) => {
const { pack, canvas, step } = setupPack(t)
const reveals = []
let completions = 0
canvas.addEventListener('packreveal', (event) => reveals.push(event.detail.index))
canvas.addEventListener('packcomplete', () => completions++)
pack.skip()
step()
for (let index = 1; index <= 3; index++) {
assert.equal(pack.state, 'stackReady')
assert.equal(pack.cards.some((card) => card.front.visible), false)
pack.skip()
assert.equal(pack.state, 'lifted')
assert.equal(pack.cards.some((card) => card.front.visible), false)
pack.skip()
assert.equal(pack.state, 'inspecting')
step()
pack.skip()
}
assert.equal(pack.state, 'complete')
pack.skip()
assert.deepEqual(reveals, [1, 2, 3])
assert.equal(completions, 1)
pack.restart()
step()
assert.equal(pack.state, 'sealed')
assert.equal(geometryHash(pack.wrapper), originalHashes[0])
})
test('reduced-motion opening keeps the existing 70 ms transition duration', (t) => {
const { pack, step } = setupPack(t, true)
pack.primary()
step(69)
assert.equal(pack.state, 'opening')
step(1)
assert.equal(pack.state, 'stackReady')
})
test('GPU preparation uploads unique textures and compiles hidden fronts and both edge looks', async (t) => {
const { pack, canvas } = setupPack(t)
pack.setActive(false)
const scene = new THREE.Scene()
scene.environment = pack.options.environment
const backTexture = new THREE.Texture()
pack.options.backMaterial.map = backTexture
const uploaded = []
const compiledEdges = []
let reveals = 0
canvas.addEventListener('packreveal', () => reveals++)
const before = geometryHash(pack.wrapper)
let finishCompile
const firstCompile = new Promise((resolve) => { finishCompile = resolve })
const renderer = {
initTexture(texture) { uploaded.push(texture) },
async compileAsync(root, camera, targetScene) {
assert.equal(root, pack.root)
assert.equal(camera, pack.camera)
assert.equal(targetScene, scene)
assert.equal(root.visible, false)
assert.equal(pack.state, 'sealed')
assert.equal(pack.cards.some((card) => card.front.visible), false)
assert.ok(pack.cards.every((card) => root.getObjectById(card.front.id)))
compiledEdges.push(pack.cards.map((card) => card.edge.clearcoat))
if (compiledEdges.length === 1) await firstCompile
},
}
let ready = false
const preparation = pack.prepareGPU(renderer, scene).then(() => { ready = true })
await Promise.resolve()
assert.equal(ready, false)
finishCompile()
await preparation
assert.equal(uploaded.length, new Set(uploaded).size)
assert.equal(uploaded.length, 13, '4 artwork/mask textures, normal, environment, back and 6 wrapper maps')
for (const { artwork, mask } of [...pack.options.textures.values()]) {
assert.ok(uploaded.includes(artwork))
assert.ok(uploaded.includes(mask))
}
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)
assert.equal(reveals, 0)
})
test('failed reveal-material warmup restores neutral edges and can be retried', async (t) => {
const { pack } = setupPack(t)
pack.setActive(false)
let compilations = 0
const failure = new Error('GPU compilation failed')
const renderer = {
initTexture() {},
async compileAsync() {
if (++compilations === 2) throw failure
},
}
const scene = new THREE.Scene()
await assert.rejects(pack.prepareGPU(renderer, scene), (error) => error === failure)
assert.deepEqual(pack.cards.map((card) => card.edge.clearcoat), [0.02, 0.02, 0.02])
assert.equal(pack.root.visible, false)
assert.equal(pack.state, 'sealed')
await pack.prepareGPU(renderer, scene)
assert.equal(compilations, 4)
})
test('GPU upload errors propagate and preparation cannot mutate an active pack', async (t) => {
const { pack } = setupPack(t)
const failure = new Error('GPU upload failed')
let uploads = 0
const renderer = {
initTexture() { uploads++; throw failure },
async compileAsync() { assert.fail('Compilation must not run after upload failure') },
}
const scene = new THREE.Scene()
await assert.rejects(pack.prepareGPU(renderer, scene), /before activating/)
assert.equal(uploads, 0)
pack.setActive(false)
await assert.rejects(pack.prepareGPU(renderer, scene), (error) => error === failure)
})
test('pack cleanup disposes owned resources without disposing shared or caller-owned textures', (t) => {
const { pack } = setupPack(t)
const scene = new THREE.Scene()
scene.add(pack.root)
const shared = [
pack.options.backMaterial, pack.options.normalMap, pack.options.environment,
...[...pack.options.textures.values()].flatMap(({ artwork, mask }) => [artwork, mask]),
]
const sharedSpies = shared.map((resource) => t.mock.method(resource, 'dispose'))
const geometries = new Set()
const materials = new Set()
pack.root.traverse((object) => {
if (!(object instanceof THREE.Mesh)) return
geometries.add(object.geometry)
materials.add(object.material)
})
materials.delete(pack.options.backMaterial)
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)
assert.equal(pack.root.visible, false)
assert.ok(ownedSpies.every((spy) => spy.mock.callCount() === 1))
assert.ok(sharedSpies.every((spy) => spy.mock.callCount() === 0))
})
test('current ten-card pool uses available choices and completes every reveal once', (t) => {
const contents = rollPackContents(['David', 'Timothy'], () => 0.5)
assert.equal(packSize, 10)
assert.equal(contents.length, packSize)
assert.ok(contents.every(card => card.fixture === 'Timothy'
&& packFinishes.includes(card.finish) && packSubstrates.includes(card.substrate)))
assert.throws(() => rollPackContents([]))
assert.throws(() => rollPackContents(['David'], () => 1))
const { pack, canvas, step } = setupPack(t, false, contents)
const reveals = []
let completions = 0
canvas.addEventListener('packreveal', event => reveals.push(event.detail))
canvas.addEventListener('packcomplete', () => completions++)
pack.skip()
step()
for (let index = 0; index < packSize; index++) {
assert.equal(pack.state, 'stackReady')
pack.skip()
assert.equal(pack.state, 'lifted')
pack.skip()
assert.equal(pack.state, 'inspecting')
pack.skip()
}
assert.equal(pack.state, 'complete')
assert.equal(completions, 1)
assert.deepEqual(reveals.map(({ fixture, finish, substrate }) => ({ fixture, finish, substrate })), contents)
assert.deepEqual(reveals.map(card => card.index), Array.from({ length: packSize }, (_, i) => i + 1))
pack.skip()
assert.equal(completions, 1)
})
test('ten pack cards share two geometry buffers and release each exactly once', (t) => {
const { pack } = setupPack(t, false, rollPackContents(['David'], () => 0))
const geometries = new Set()
for (const card of pack.cards) card.object.traverse(object => {
if (object instanceof THREE.Mesh) geometries.add(object.geometry)
})
assert.equal(geometries.size, 2)
const disposal = [...geometries].map(geometry => t.mock.method(geometry, 'dispose'))
pack.dispose()
assert.ok(disposal.every(spy => spy.mock.callCount() === 1))
})
test('pack frames report motion and touch settling but settled and hidden scenes stay idle', (t) => {
const { pack, step } = setupPack(t)
step()
assert.equal(pack.tick(performance.now()), false)
pack.primary()
assert.equal(pack.tick(performance.now()), true)
pack.pause(performance.now())
pack.tick(performance.now()) // Flush the pose/deformation at the pause point.
assert.equal(pack.tick(performance.now()), false)
pack.primary()
step(3201)
assert.equal(pack.state, 'stackReady')
assert.equal(pack.tick(performance.now()), false)
pack.touchTarget = 0.5
assert.equal(pack.tick(performance.now()), true)
for (let frame = 0; frame < 100; frame++) step()
assert.equal(pack.tick(performance.now()), false)
pack.wrapperDirty = true
assert.equal(pack.tick(performance.now()), false)
pack.setActive(false)
assert.equal(pack.tick(performance.now()), false)
})
test('streamed packs acquire current and next fronts and release outgoing leases', async (t) => {
installDOM()
let now = 1000
t.mock.method(performance, 'now', () => now)
const canvas = new TestCanvas()
const acquired = []
const released = []
const placeholder = { artwork: new THREE.Texture(), mask: new THREE.Texture() }
const contents = [
{ fixture: 'one', finish: 'Holographic', substrate: 'Linen' },
{ fixture: 'two', finish: 'Foil', substrate: 'Metal' },
{ fixture: 'three', finish: 'Printed ink', substrate: 'Paper' },
]
const pack = new PackOpening({
canvas, contents, placeholderTextures: placeholder,
async acquireTextures(spec) {
acquired.push(spec.fixture)
return { textures: { artwork: new THREE.Texture(), mask: new THREE.Texture() },
release: () => released.push(spec.fixture) }
},
resolveSurfaceDefaults(finish, substrate) {
return { finishStrength: finish === 'Holographic' ? 0.33 : 1,
roughness: 0.23, normalStrength: substrate === 'Linen' ? 0.05 : 1,
metalBrushHorizontal: false }
},
backMaterial: new THREE.MeshPhysicalMaterial(), normalMap: new THREE.Texture(),
environment: new THREE.CubeTexture(), lightPosition: new THREE.Vector3(), onChange() {},
})
pack.setActive(true)
pack.skip()
now += 3200
pack.tick(now)
await Promise.resolve()
await Promise.resolve()
assert.deepEqual(acquired, ['one'])
assert.ok(pack.cards.every(card => !card.front.visible))
assert.equal(pack.cards[0].material.uniforms.finishStrength.value, 0.33)
assert.equal(pack.cards[0].material.uniforms.normalStrength.value, 0.05)
pack.skip()
assert.equal(pack.state, 'lifted')
pack.skip()
assert.equal(pack.state, 'inspecting')
await Promise.resolve()
await Promise.resolve()
assert.deepEqual(acquired, ['one', 'two'])
assert.equal(pack.cards[0].front.visible, true)
pack.skip()
assert.equal(pack.state, 'stackReady')
assert.deepEqual(released, ['one'])
pack.dispose()
assert.deepEqual(released, ['one', 'two'])
})