| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- // Readback gate for the assembled roof candidate, independent of the builder's rewrites.
- import assert from 'node:assert/strict'
- import {readFileSync,writeFileSync,existsSync} from 'node:fs'
- import {resolve,join,dirname} from 'node:path'
- import {createHash} from 'node:crypto'
- import {unpackInstanceBuffers} from './instance-buffer-codec.mjs'
- import {accessorValues} from './instance-repeated-meshes.mjs'
- import {decodePrimitive} from './instance-safe-primitives.mjs'
- import {verifyMaterialClosure} from './verify-material-closure.mjs'
- import {tileRasterBudget} from './raster-budget.mjs'
- import {selectDetailRegions} from '../../src/modules/cockpit/services/regionLoading.js'
- const root=resolve('tools/city-massing/city/cad-bim'),base=join(root,'city-regions-v7')
- const dir=resolve(process.argv[2]||join(root,'city-regions-roof-pilot-v1'))
- const read=p=>JSON.parse(readFileSync(p)),sha=b=>createHash('sha256').update(b).digest('hex')
- const old=read(join(base,'manifest.json')),next=read(join(dir,'manifest.json')),candidate=next.roofQualityCandidate,pilotPath=resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/roof-quality-pilot/report.json'),pilot=read(pilotPath)
- assert.equal(sha(readFileSync(pilotPath)),candidate.pilotReportSha256);assert.equal(pilot.passed,true);assert.equal(pilot.configurationChanged,false);assert.equal(pilot.pilotNotConfigured,true)
- assert.equal(sha(readFileSync(join(base,'manifest.json'))),candidate.parentManifestSha256)
- assert.equal(candidate.changedTiles.length,2);assert.equal(next.buildings,old.buildings)
- assert.deepEqual(next.transform,old.transform);assert.equal(next.regions.length,old.regions.length)
- const replacements=new Map(candidate.changedTiles.map(r=>[r.oldPath,r.newPath]))
- const used=new Set();let previews=0,fine=0,changedVerified=0
- function canonicalTree(t,from,after){const clone=structuredClone(t);function walk(node){if(node.content?.uri){let path=resolve(from,node.content.uri);if(!after)path=replacements.get(path)||path;assert.ok(existsSync(path),'missing '+path);node.content.uri=path}for(const c of node.children||[])walk(c)}walk(clone.root);return clone}
- function rasters(decoded){const {json,bin}=decoded;return tileRasterBudget((json.images||[]).map(im=>{const v=json.bufferViews[im.bufferView];return{mimeType:im.mimeType,bytes:bin.subarray(v.byteOffset||0,(v.byteOffset||0)+v.byteLength)}}),json.textures||[])}
- for(const r of next.regions){
- const before=old.regions.find(x=>x.id===r.id),aDir=dirname(resolve(base,before.url)),bDir=dirname(resolve(dir,r.url))
- assert.equal(r.contentCount,before.contentCount);assert.equal(r.previewCount,before.previewCount)
- assert.deepEqual(r.admissionBounds,before.admissionBounds);assert.deepEqual(r.bounds,before.bounds);assert.deepEqual(r.sourceCells,before.sourceCells)
- assert.deepEqual(canonicalTree(read(resolve(base,before.url)),aDir,false),canonicalTree(read(resolve(dir,r.url)),bDir,true),'hierarchy changed '+r.id)
- const expected=before.contentUrls.map(uri=>{const p=resolve(aDir,uri);return replacements.get(p)||p})
- const actual=r.contentUrls.map(uri=>resolve(bDir,uri));assert.deepEqual(actual,expected)
- assert.equal(new Set(actual).size,r.contentCount)
- assert.deepEqual(r.previewContentUrls.map(uri=>resolve(bDir,uri)),before.previewContentUrls.map(uri=>resolve(aDir,uri)))
- let delta=0
- for(let i=0;i<r.fineRecords.length;i++){
- const record=r.fineRecords[i],previous=before.fineRecords[i],oldPath=resolve(aDir,previous.uri),newPath=resolve(bDir,record.uri)
- assert.ok(!used.has(newPath),'duplicate fine');used.add(newPath);assert.ok(existsSync(newPath));fine++
- if(!replacements.has(oldPath)){assert.equal(newPath,oldPath);assert.deepEqual({...record,uri:newPath},{...previous,uri:oldPath});continue}
- const change=candidate.changedTiles.find(c=>c.oldPath===oldPath);assert.ok(change)
- const pilotRow=pilot.results.find(x=>x.region===r.id&&x.uri===previous.uri);assert.ok(pilotRow)
- assert.equal(sha(readFileSync(change.sourcePath)),change.sourceSha256);assert.equal(change.sourceSha256,pilotRow.sourceSha256);assert.equal(change.sourcePath,pilotRow.sourcePath)
- assert.equal(record.width,change.width);assert.equal(record.height,change.height);assert.equal(record.rasterSha256,change.rasterSha256);assert.equal(record.rasterBytes,change.rasterBytes);assert.deepEqual(record.processing,change.processing);assert.deepEqual(record.sourceTiles,change.sourceTiles)
- assert.equal(sha(readFileSync(oldPath)),change.currentAssetSha256,'configured source changed');assert.equal(sha(readFileSync(newPath)),change.replacementSha256,'candidate replacement changed')
- const a=unpackInstanceBuffers(readFileSync(oldPath)),b=unpackInstanceBuffers(readFileSync(newPath));verifyMaterialClosure(b.json,newPath)
- assert.deepEqual(a.json.nodes,b.json.nodes);assert.equal(a.json.meshes.length,b.json.meshes.length)
- for(let k=0;k<a.json.meshes.length;k++){
- assert.equal(a.json.meshes[k].primitives.length,b.json.meshes[k].primitives.length)
- for(let n=0;n<a.json.meshes[k].primitives.length;n++){
- const x=a.json.meshes[k].primitives[n],y=b.json.meshes[k].primitives[n]
- assert.deepEqual(Object.keys(x.attributes).sort(),Object.keys(y.attributes).sort());assert.equal(x.mode,y.mode);assert.deepEqual(x.extensions||{},y.extensions||{})
- for(const key of Object.keys(x.attributes)){const xa=a.json.accessors[x.attributes[key]],ya=b.json.accessors[y.attributes[key]];assert.deepEqual(xa?.bufferView!==undefined,ya?.bufferView!==undefined);if(xa?.bufferView!==undefined)assert.deepEqual(accessorValues(a.json,a.bin,x.attributes[key]),accessorValues(b.json,b.bin,y.attributes[key]));else assert.deepEqual(xa,ya)}
- assert.deepEqual(decodePrimitive(a.json,a.bin,x),decodePrimitive(b.json,b.bin,y))
- const m=a.json.materials[x.material],nn=b.json.materials[y.material];if(m.name!=='SatelliteRoofAtlas')assert.deepEqual(nn,m)
- }
- }
- const cost=rasters(b).decodedTextureBytes-rasters(a).decodedTextureBytes
- assert.equal(cost,change.deltaDecodedTextureEstimate);delta+=cost;changedVerified++
- for(const s of record.sourceTiles)assert.equal(sha(readFileSync(s.path)),s.sha256)
- }
- for(let i=0;i<r.previewRecords.length;i++){assert.deepEqual({...r.previewRecords[i],uri:resolve(bDir,r.previewRecords[i].uri)},{...before.previewRecords[i],uri:resolve(aDir,before.previewRecords[i].uri)});previews++}
- assert.equal(r.decodedGeometryBytes,before.decodedGeometryBytes+delta)
- assert.ok(r.decodedGeometryBytes<=96*1024*1024&&r.contentCount<=180)
- }
- assert.deepEqual(canonicalTree(read(join(base,'coarse-tileset.json')),base,false),canonicalTree(read(join(dir,'coarse-tileset.json')),dir,true))
- assert.deepEqual(next.coarseRecords.map(r=>resolve(dir,r.uri)),old.coarseRecords.map(r=>resolve(base,r.uri)))
- // Exercise all two-region choices with the changed allocation included; caps must hold.
- let pairs=0
- for(let i=0;i<next.regions.length;i++)for(let j=i+1;j<next.regions.length;j++){
- const list=[next.regions[i],next.regions[j]].map((r,k)=>({...r,distance:10+k,centerDistance:10+k,retryAt:0,active:false}))
- const ids=selectDetailRegions(list),rs=list.filter(r=>ids.includes(r.id));assert.ok(ids.length<=2)
- assert.ok(rs.reduce((s,r)=>s+r.contentCount,0)<=180&&rs.reduce((s,r)=>s+r.decodedGeometryBytes,0)<=96*1024*1024)
- assert.deepEqual(selectDetailRegions(list,{pipeline:true}),[]);pairs++
- }
- assert.equal(changedVerified,2);assert.equal(fine,3023);assert.equal(previews,631)
- const config=read('public/cockpit/config.json')
- assert.equal(config.map.tilesets[0].regionManifest,'/gis/cad-bim/city-regions-v7/manifest.json','candidate unexpectedly configured')
- const report={passed:true,regions:next.regions.length,coarseFiles:next.coarseRecords.length,previewFiles:previews,fineFiles:fine,
- changedFineContents:changedVerified,actualDecodedRasterDelta:candidate.changedTiles.reduce((s,c)=>s+c.deltaDecodedTextureEstimate,0),
- region25Estimate:next.regions.find(r=>r.id===25).decodedGeometryBytes,allPairsBudgetChecks:pairs,
- allUrisResolve:true,unchangedAssetsReuseExactPaths:true,geometryAndUvPreserved:true,sourceHashesMatch:true,
- hierarchyAndBoundsPreserved:true,pipelineRegions:0,configurationChanged:false,renderingValidated:false}
- if(process.argv[3])writeFileSync(process.argv[3],JSON.stringify(report,null,2));console.log(JSON.stringify(report))
|