Files
Sanctification/card-harness/scripts/material-orientation.test.mjs

88 lines
4.0 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import * as THREE from 'three'
import './test-support.mjs'
const { createCardMaterial, applyMaterialControls } = await import('../src/cardMaterial.ts')
const {
captureExperimentMaterialControls, restoreExperimentMaterialControls, isExperimentSnapshot,
} = await import('../src/experimentSnapshot.ts')
const materialControls = {
finish: 'Foil', substrate: 'Metal', finishStrength: 0.6, roughness: 0.23,
normalStrength: 0.72, environmentIntensity: 0.7, condition: 0.8, imperfectionSeed: 81251,
}
function snapshot(controls = materialControls) {
return {
schemaVersion: 2, runtimeRevision: 'runtime-look-v4-2026-09-07',
name: 'Metal orientation', savedAt: '2026-09-12T00:00:00Z', fixture: 'David',
...captureExperimentMaterialControls(controls),
lightingPreset: 'Studio', environment: 'Studio', lightType: 'Point', lightColor: '#fff0d0',
lightPosition: [2.2, 2.5, 4.4], lightIntensity: 3, exposure: 1,
cardRotation: [-0.06, 0.12, 0], cameraPosition: [0, 0, 8.2], orbitTarget: [0, 0, 0],
}
}
function material() {
return createCardMaterial(new THREE.Texture(), new THREE.Texture(), new THREE.Texture(),
new THREE.CubeTexture(), new THREE.Vector3())
}
test('Metal defaults to vertical; toggling orientation updates uniforms without rebuilding the shader', () => {
const card = material()
try {
assert.equal(card.uniforms.metalBrushHorizontal.value, false)
applyMaterialControls(card, materialControls)
const version = card.version
const shader = card.fragmentShader
for (const horizontal of [true, false, true]) {
applyMaterialControls(card, { ...materialControls, metalBrushHorizontal: horizontal })
assert.equal(card.uniforms.metalBrushHorizontal.value, horizontal)
assert.equal(card.uniforms.finishMode.value, 1)
assert.equal(card.uniforms.substrateMode.value, 3)
assert.equal(card.uniforms.normalStrength.value, 0.72)
assert.equal(card.version, version)
assert.equal(card.fragmentShader, shader)
}
applyMaterialControls(card, materialControls)
assert.equal(card.uniforms.metalBrushHorizontal.value, false, 'omitted live controls use the new default')
} finally { card.dispose() }
})
test('Both metal grain orientations survive experiment JSON save/import and material application', () => {
const card = material()
try {
for (const horizontal of [false, true]) {
const saved = JSON.parse(JSON.stringify(snapshot({ ...materialControls, metalBrushHorizontal: horizontal })))
assert.equal(isExperimentSnapshot(saved), true)
const restored = restoreExperimentMaterialControls(saved)
assert.deepEqual(restored, { ...materialControls, metalBrushHorizontal: horizontal })
applyMaterialControls(card, restored)
assert.equal(card.uniforms.metalBrushHorizontal.value, horizontal)
assert.equal(card.uniforms.normalStrength.value, 0.72, 'detail above the old 0.45 import limit survives')
}
} finally { card.dispose() }
})
test('Legacy experiments without orientation retain horizontal grain and schema 1 wear defaults', () => {
const legacy = snapshot()
delete legacy.metalBrushHorizontal
assert.equal(isExperimentSnapshot(legacy), true)
assert.equal(restoreExperimentMaterialControls(legacy).metalBrushHorizontal, true)
legacy.schemaVersion = 1
delete legacy.condition
delete legacy.imperfectionSeed
assert.equal(isExperimentSnapshot(legacy), true)
assert.deepEqual(restoreExperimentMaterialControls(legacy), {
...materialControls, metalBrushHorizontal: true, condition: 1, imperfectionSeed: 81251,
})
})
test('Experiment validation rejects malformed orientation values rather than coercing them', () => {
for (const value of ['horizontal', 'false', 0, 1, null, {}, []]) {
assert.equal(isExperimentSnapshot({ ...snapshot(), metalBrushHorizontal: value }), false)
}
const bounded = restoreExperimentMaterialControls({ ...snapshot(), normalStrength: 2, roughness: -1 })
assert.equal(bounded.normalStrength, 1)
assert.equal(bounded.roughness, 0.05)
})