import assert from 'node:assert/strict'; import {test} from 'node:test'; import * as THREE from 'three'; import {loadTypescript} from './support/load-typescript.mjs'; const {CameraController}=await loadTypescript(new URL('../src/input/CameraController.ts',import.meta.url)); const {readCameraComposition,saveCameraComposition}=await loadTypescript(new URL('../src/input/CameraComposition.ts',import.meta.url)); test('wall experiment defaults locally, persists rollback and tolerates unavailable storage',()=>{ const items=new Map(),storage={getItem:k=>items.get(k),setItem:(k,v)=>items.set(k,v)}; assert.equal(readCameraComposition(storage),'wall');saveCameraComposition(storage,'legacy');assert.equal(readCameraComposition(storage),'legacy');saveCameraComposition(storage,'wall');assert.equal(readCameraComposition(storage),'wall'); assert.equal(readCameraComposition({getItem(){throw Error('blocked');}}),'wall');assert.equal(saveCameraComposition({setItem(){throw Error('blocked');}},'legacy'),false); }); test('composition switch preserves input and projection state, exact rollback and neutral',()=>{ const camera=new THREE.PerspectiveCamera(40,.46,.1,100),control=new CameraController(camera); control.setRoomBounds(.76,.95,.6);control.setView(1,.25);control.update(10,1);camera.updateMatrixWorld(true);const legacy={position:camera.position.toArray(),quaternion:camera.quaternion.toArray(),fov:camera.fov}; control.setCompositionMode('wall');control.update(10,1);assert.ok(camera.fov>40&&camera.fov<=65);assert.deepEqual(control.target.toArray(),[1,.25]);assert.deepEqual(control.current.toArray(),[1,.25]);assert.ok(camera.position.z{ const poses=[]; for(const aspect of [1,1.5,2.164])for(const side of [-1,1]){ const c=new THREE.PerspectiveCamera(40,aspect,.1,100),ctrl=new CameraController(c);ctrl.setRoomBounds(.76096,.9512,.6);ctrl.setCompositionMode('wall');ctrl.setView(side,0);ctrl.update(10,1); const half=.76096/2+.95*.76096,unit=half/1.45; assert.ok(Math.abs(c.position.x-side*.65*half)<1e-10);assert.ok(Math.abs(c.position.z-(.6+.6*unit))<1e-10);assert.equal(c.fov,40); const dir=c.getWorldDirection(new THREE.Vector3());assert.ok(Math.abs(Math.atan2(-dir.z,Math.abs(dir.x))*180/Math.PI-25)<1e-9);poses.push({side,p:c.position.toArray(),q:c.quaternion.toArray()}); } for(let i=2;i{ const {loadFrameGeometry}=await import('./support/load-frame-geometry.mjs'); const {prepareHouseAsset}=await loadTypescript(new URL('../src/objects/BlenderHouseSurround.ts',import.meta.url)); const root=loadFrameGeometry(new URL('../public/assets/window/grandma-house-surround.glb',import.meta.url)),house=prepareHouseAsset(root); const snapshot=()=>{const rows=[];root.traverse(o=>{if(o.isMesh)rows.push([o.name,...o.geometry.attributes.position.array]);});return rows;}; house.resize(.76,.95,.6);const before=snapshot();house.resize(.76,.95,.6,.76,true);assert.notDeepEqual(snapshot(),before);house.resize(.76,.95,.6,.76,false);assert.deepEqual(snapshot(),before);house.dispose(); }); test('real startup wiring restores saved legacy mode and switches both camera and physical room',async()=>{ const {readFile}=await import('node:fs/promises'),{default:ts}=await import('typescript'),{runInNewContext}=await import('node:vm'); const main=await readFile(new URL('../src/main.ts',import.meta.url),'utf8'); const code=main.slice(main.indexOf('let cameraCompositionStorage'),main.indexOf('const sceneZoom =')); const elements=new Map(),callbacks=new Map();for(const id of ['#camera-composition','#camera-composition-note'])elements.set(id,{value:'',textContent:'',addEventListener:(_type,fn)=>callbacks.set(id,fn)}); const items=new Map([['behind-glass-camera-composition-v32','legacy']]),camera=new THREE.PerspectiveCamera(40,.46,.1,100),cameraController=new CameraController(camera);cameraController.setRoomBounds(.76,.95,.6);cameraController.setView(1,0);cameraController.update(10,1);const legacy=camera.position.toArray(); let roomMode,invalidations=0; runInNewContext(ts.transpileModule(code,{compilerOptions:{target:ts.ScriptTarget.ES2022}}).outputText,{window:{localStorage:{getItem:key=>items.get(key),setItem:(key,value)=>items.set(key,value)}},readCameraComposition,saveCameraComposition,camera,cameraController,houseSurround:{setCompositionMode:mode=>roomMode=mode},document:{querySelector:id=>elements.get(id)},listenerAbort:new AbortController(),sensitivity:1,renderDemand:{invalidate(){invalidations++;}}}); assert.equal(elements.get('#camera-composition').value,'legacy');assert.equal(roomMode,'legacy');cameraController.update(10,1);assert.deepEqual(camera.position.toArray(),legacy); elements.get('#camera-composition').value='wall';callbacks.get('#camera-composition')();assert.equal(roomMode,'wall');assert.equal(items.get('behind-glass-camera-composition-v32'),'wall');assert.ok(camera.fov>40&&camera.fov<=65); elements.get('#camera-composition').value='legacy';callbacks.get('#camera-composition')();assert.equal(roomMode,'legacy');assert.deepEqual(camera.position.toArray(),legacy);assert.equal(invalidations,2); });