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) })