import assert from 'node:assert/strict';
import {test} from 'node:test';
import * as THREE from 'three';
import {loadTypescript} from './support/load-typescript.mjs';
import {loadFrameGeometry} from './support/load-frame-geometry.mjs';
const {prepareHouseAsset}=await loadTypescript(new URL('../src/objects/BlenderHouseSurround.ts',import.meta.url));
const {createPatternedGlassGeometry}=await loadTypescript(new URL('../src/rendering/PatternedGlassGeometry.ts',import.meta.url));
const room=()=>loadFrameGeometry(new URL('../public/assets/window/grandma-house-surround.glb',import.meta.url));
// Regression: the lining's BACK face must clear even the highest translated
// pane vertex, not merely put its front face in front of the pane's base plane.
test('fixed pocket lining clears the actual bumpy pane envelope across shapes and compositions',()=>{
const root=room(),asset=prepareHouseAsset(root);
for(const [width,height] of [[.76,.95],[.15,1.5],[2,.1]])for(const grid of Array.from({length:15},(_,i)=>10+i))for(const curvature of [-1,0,1])for(const wall of [false,true]){
const shape={width,height,grid,curvature},built=createPatternedGlassGeometry(shape);
built.geometry.computeBoundingBox();const front=built.geometry.boundingBox.max.z;
asset.resize(width,height,.6,Math.min(width,height),wall,shape);
const p=root.getObjectByName('HouseRoomShell').geometry.attributes.position;
// Four chamber boxes precede the two pocket-side lining boxes (24 vertices each).
// The top/bottom strips retain their depth: their Y range never overlaps the pane.
let back=Infinity;for(let i=96;i<144;i++)back=Math.min(back,p.getZ(i));
assert.ok(back>front+1e-6,`lining back ${back} must clear pane front ${front}: ${JSON.stringify(shape)}, wall=${wall}`);
const s=Math.min(width,height);assert.ok(Math.abs(p.getX(96)+width/2+.002*s)<1e-6,'inner aperture X unchanged');
for(let i=144;i
height/2,'top/bottom lining stays outside pane Y extent');assert.ok(p.getZ(i)>=-1e-8&&p.getZ(i)<=.002*s+1e-8,'top/bottom reveal seal retains its depth');}
built.geometry.dispose();
}
asset.dispose();
});
test('lining wins pocket rays while leaving the aperture clear through door travel in both room/photo modes',async()=>{
const {fitWindowScale,prepareFrameAsset}=await loadTypescript(new URL('../src/objects/BlenderWindowFrame.ts',import.meta.url));
const {peekingLayout}=await loadTypescript(new URL('../src/objects/PeekingComposition.ts',import.meta.url));
let checked=0;
for(const aspect of [.46,2.16])for(const photoMode of ['original','courtyard'])for(const wall of [false,true]){
const fit=fitWindowScale(aspect,.8)*.82,layout=photoMode==='courtyard'?peekingLayout(aspect,.8):{width:fit*.8*.8,height:fit*.8};
const {width:w,height:h}=layout,s=Math.min(w,h),root=room(),asset=prepareHouseAsset(root),frame=loadFrameGeometry(),sash=prepareFrameAsset(frame);
const shape={width:w,height:h,grid:10,curvature:1},built=createPatternedGlassGeometry(shape),pane=new THREE.Mesh(built.geometry,new THREE.MeshBasicMaterial());pane.name='pane';pane.position.z=.6;
asset.resize(w,h,.6,layout.clearanceScale,wall,shape);sash.resize(w,h,.6);
const shell=root.getObjectByName('HouseRoomShell');
for(const open of [0,.5,1]){
sash.setOpen(open);pane.position.x=-w*open;pane.updateMatrixWorld(true);frame.updateMatrixWorld(true);
for(const oblique of [-.5,0,.5])for(let i=0;i<48;i++){
const target=new THREE.Vector3(-w/2-.02*s,(-.46+i/47*.92)*h,.6+.005*s);
const origin=target.clone().add(new THREE.Vector3(oblique*s,0,s));
const ray=new THREE.Raycaster(origin,target.clone().sub(origin).normalize(),0,2*s);
const lining=ray.intersectObject(shell)[0];assert.ok(lining,'pocket lining remains solid');
const glass=ray.intersectObject(pane).find(hit=>hit.point.x>=-w/2-.05*s);
assert.ok(!glass||lining.distance!(h.object.material.clippingPlanes??[]).some(p=>p.distanceToPoint(h.point)<0));
assert.ok(hit,'fixed front wood retains ordinary occlusion');
}
pane.geometry.dispose();pane.material.dispose();asset.dispose();sash.dispose();
}
console.log('Pocket lining CPU rays:',checked);
});