import assert from 'node:assert/strict' import {readFileSync,writeFileSync} from 'node:fs' import {resolve,join} from 'node:path' import {parseGlb} from './instance-repeated-meshes.mjs' import {unpackInstanceBuffers} from './instance-buffer-codec.mjs' import {selectDetailRegions} from '../../src/modules/cockpit/services/regionLoading.js' import {decodePrimitive} from './instance-safe-primitives.mjs' import {createHash} from 'node:crypto' const out=resolve(process.argv[2]),m=JSON.parse(readFileSync(join(out,'manifest.json'))),base=resolve(out,'../'+m.source),tree=JSON.parse(readFileSync(join(base,'tileset.json'))),source=JSON.parse(readFileSync(join(base,'manifest.json'))) assert.equal(m.regions.length,tree.root.children.length);assert.equal(m.regions.length,113) const seenFine=new Set(),seenCoarse=new Set() let coarseBytes=0,maxRegionBytes=0 const signature=(json,bin,p)=>{const g=decodePrimitive(json,bin,p);return createHash('sha256').update(JSON.stringify({positions:g.positions,normals:g.normals,indices:g.indices,material:json.materials[p.material]})).digest('hex')} for(const r of m.regions){ const path=join(out,r.url),rt=JSON.parse(readFileSync(path)),expected=[] function fine(t){if(t.content?.uri.endsWith('-lod0.glb'))expected.push(resolve(base,t.content.uri));for(const c of t.children||[])fine(c)} fine(tree.root.children[r.id]);assert.deepEqual(rt.root.transform,tree.root.transform);assert.deepEqual(rt.root.boundingVolume,r.bounds) assert.equal(r.contentCount,rt.root.children.length);assert.ok(r.contentCount<=128) const actual=rt.root.children.map(t=>resolve(path,'../'+t.content.uri)) assert.deepEqual(actual.sort(),expected.sort());for(const p of actual){assert.ok(!seenFine.has(p));seenFine.add(p)} maxRegionBytes=Math.max(maxRegionBytes,r.decodedGeometryBytes) } for(const r of m.coarseRecords){const target=unpackInstanceBuffers(readFileSync(join(out,r.uri))),old=parseGlb(readFileSync(resolve(base,r.sourceUri)));assert.ok(!seenCoarse.has(r.uri));seenCoarse.add(r.uri);coarseBytes+=readFileSync(join(out,r.uri)).length assert.deepEqual(target.json.nodes,old.json.nodes,'coarse transforms changed') const ext=target.json.extensions.EXT_structural_metadata,table=ext.propertyTables[0],regionView=target.json.bufferViews[table.properties.region.values] assert.equal(target.bin.readUInt16LE(regionView.byteOffset),r.region) assert.equal(table.count,1);assert.equal(table.class,'BuildingRegion') assert.equal(target.json.meshes[0].primitives.length,old.json.meshes[0].primitives.length) for(let i=0;ib===0),'feature ID not constant zero') } } assert.equal(seenFine.size,3023);assert.equal(seenCoarse.size,113) const samples=[] for(const pose of [[0,-1000,350],[0,-1000,50],[2000,1000,180],[0,-1000,10000]]){ const regions=m.regions.map(r=>{const s=r.bounds.sphere;return{...r,retryAt:0,active:false,distance:Math.max(0,Math.hypot(...s.slice(0,3).map((v,a)=>v-pose[a]))-s[3])}}) const ids=selectDetailRegions(regions),chosen=regions.filter(r=>ids.includes(r.id)),contents=chosen.reduce((s,r)=>s+r.contentCount,0),memory=chosen.reduce((s,r)=>s+r.decodedGeometryBytes,0) assert.ok(ids.length<=2&&contents<=180&&memory<=96*1024*1024) assert.deepEqual(selectDetailRegions(regions,{pipeline:true}),[]) samples.push({cameraENU:pose,regions:ids,detailContentCap:contents,decodedGeometryEstimate:memory,pipelineRegions:0}) } const coarseTree=JSON.parse(readFileSync(join(out,'coarse-tileset.json'))) assert.ok(coarseTree.root.children.every(t=>t.geometricError===0&&!t.children),'coarse-only dataset must have no detail descendants') const result={passed:true,regions:m.regions.length,fineFiles:seenFine.size,coarseFiles:seenCoarse.size,maxRegionContents:m.maxRegionContents,maxRegionDecodedGeometryEstimate:maxRegionBytes,coarseBytes, indexedCoarseGeometryUnchanged:true,everyCoarseVertexFeatureZero:true,allFineUrisUnchanged:true,pipelineDetailAdmission:0,samples,renderingValidated:false,fpsValidated:false} if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(result,null,2));console.log(JSON.stringify(result))