import { test } from 'node:test'; import assert from 'node:assert/strict'; import { BufferGeometry, Float32BufferAttribute } from 'three'; import { encodeSummitGeometry, decodeSummitGeometry, loadSummitAssets } from '../src/scene/summitAssets.ts'; import { gzipSync } from 'node:zlib'; import { buildSummitGeometry,stitchSummitGeometry } from '../src/scene/summitGeometry.ts'; import { createSummitWalking } from '../src/controls/summitWalking.ts'; import { createTerrainGeometry,terrainGeometryHeightAt } from '../src/scene/terrainGeometry.ts'; import { applySummitTerrain } from '../src/scene/summitTerrain.ts'; import { readFileSync } from 'node:fs'; import { SUMMIT_ASSETS } from '../src/constants/summitAssets.ts'; import { SUMMIT_MATERIALS } from '../src/constants/summitMaterials.ts'; test('baked summit shares vertices and preserves every triangle attribute exactly',()=>{ const geometry=new BufferGeometry(); const order=[0,1,2,2,1,3]; for(const [name,size] of [['position',3],['normal',3],['uv',2],['color',3],['trailDistance',1],['rockDistance',1]] as const) { geometry.setAttribute(name,new Float32BufferAttribute(order.flatMap(i=>Array.from({length:size},(_,j)=>i*.123+j*.35)),size)); } const bytes=encodeSummitGeometry(geometry),restored=decodeSummitGeometry(bytes); assert.equal(restored.getAttribute('position').count,4); assert.equal(restored.getIndex()!.count,6); for(const name of Object.keys(geometry.attributes)) { const a=geometry.getAttribute(name),b=restored.getAttribute(name); for(let i=0;i<6;i++) for(let j=0;jdecodeSummitGeometry(bytes.slice(0,-4))); geometry.dispose();restored.dispose(); }); test('summit loader accepts both browser-decoded gzip and raw gzip hosting',async()=>{ const geometry=new BufferGeometry(); for(const [name,size] of [['position',3],['normal',3],['uv',2],['color',3],['trailDistance',1],['rockDistance',1]] as const) geometry.setAttribute(name,new Float32BufferAttribute(new Float32Array(size*3),size)); const bytes=encodeSummitGeometry(geometry),originalFetch=globalThis.fetch; try { for(const automatic of [true,false]) { globalThis.fetch=async input=>String(input).includes('.json') ? Response.json({version:1,bufferHash:'test',byteLength:bytes.byteLength}) : new Response(automatic?bytes:gzipSync(new Uint8Array(bytes)),{headers:automatic?{'Content-Encoding':'gzip'}:{}}); const loaded=await loadSummitAssets();assert.equal(loaded.getIndex()!.count,3);loaded.dispose(); } } finally {globalThis.fetch=originalFetch;geometry.dispose();} }); test('adaptive interior triangles follow terrain and never shortcut material transition bands',()=>{ const meta=JSON.parse(readFileSync(new URL('../public/terrain/gwanaksan.json',import.meta.url),'utf8')); const raw=readFileSync(new URL('../public/terrain/gwanaksan.f32',import.meta.url)); const terrain=createTerrainGeometry(meta,new Float32Array(raw.buffer,raw.byteOffset,raw.byteLength/4),true); applySummitTerrain(terrain,meta); const walking=createSummitWalking(meta.origin),mesh=buildSummitGeometry(walking,terrain,true); const p=mesh.getAttribute('position'); let large=0; for(let i=0;iMath.hypot(p.getX(i+j)-p.getX(i+(j+1)%3),p.getZ(i+j)-p.getZ(i+(j+1)%3)))); if(edge<=1.4)continue; large++; assert.ok(edge<=SUMMIT_ASSETS.interiorMaxEdge+.0001); const r=SUMMIT_MATERIALS.rockBlend,t=SUMMIT_MATERIALS.edgeBlend; for(let j=0;j<3;j++) { const rock=walking.rockDistance(p.getX(i+j),p.getZ(i+j)),path=walking.pathDistance(p.getX(i+j),p.getZ(i+j)); assert.ok(rockr.outerMeters+r.irregularityMeters && (patht.outerMeters+t.irregularityMeters)); } } assert.ok(large>1000);terrain.dispose();mesh.dispose(); }); test('coarse triangles reuse adjoining fine-edge vertices and attributes',()=>{ const g=new BufferGeometry(); const points=[[0,0,0],[4,0,0],[0,0,4],[0,0,0],[2,0,-1],[2,0,0],[2,0,0],[2,0,-1],[4,0,0]]; g.setAttribute('position',new Float32BufferAttribute(points.flat(),3)); g.setAttribute('color',new Float32BufferAttribute(points.flatMap(p=>p[0]===2&&p[2]===0?[.2,.3,.4]:[1,1,1]),3)); const stitched=stitchSummitGeometry(g),p=stitched.getAttribute('position'),color=stitched.getAttribute('color'); assert.equal(p.count,12); let midpointUses=0; for(let i=0;i