prepare-negative-estate-export.mjs 2.6 KB

1234567891011121314151617181920212223242526272829
  1. // Export-ready four-ID subset: preserve seated geometry and remap only sampling references.
  2. import assert from 'node:assert/strict'
  3. import {createHash} from 'node:crypto'
  4. import {readFileSync,writeFileSync,mkdirSync,existsSync} from 'node:fs'
  5. import {resolve,join} from 'node:path'
  6. const root=resolve('tools/city-massing/city/cad-bim')
  7. const dir=resolve('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/negative-estate-pilot/input-v2')
  8. assert.ok(!existsSync(dir),'immutable input already exists')
  9. const ids=['cad-25612','cad-25467','cad-25968','cad-25957']
  10. const parentBytes=readFileSync(join(root,'seated-plan.json')),seat=JSON.parse(parentBytes),plan=JSON.parse(readFileSync(join(root,'plan.json')))
  11. assert.deepEqual(seat.transform,plan.transform)
  12. const samples=[],sourceById=new Map(plan.buildings.map(b=>[b.id,b]))
  13. const buildings=ids.map(id=>{
  14. const s=seat.buildings.find(b=>b.id===id),b=sourceById.get(id);assert.ok(s&&b)
  15. assert.ok(s.terrainRelief<=3.2&&s.terrainRelief<=s.heightM*.4)
  16. const indices=b.sampleIndices.map(i=>{assert.ok(plan.samples[i]);samples.push(structuredClone(plan.samples[i]));return samples.length-1})
  17. return {...s,sourceKind:s.kind,sourceArchetype:s.archetype,kind:'massing',sampleIndices:indices,sourceSampleIndices:b.sampleIndices,
  18. renderingPolicy:'exact surveyed seated shell at both LODs; near facade details estimated; source asset not stretched to impersonate floor count'}
  19. })
  20. const result={version:2,source:plan.source,origin:seat.origin,transform:seat.transform,bounds:seat.bounds||plan.bounds,
  21. notice:'Local offline candidate based on maintainer estate feedback. Footprints/seating preserved; heights and facades remain estimates. Not configured or visually accepted.',
  22. parentPlanSha256:createHash('sha256').update(parentBytes).digest('hex'),buildings,samples,
  23. selection:{ids,reason:'specific user-requested estate, source rural tag retained; no blanket rural loading',configured:false},
  24. caps:{maxRegions:1,maxContents:8,maxDecodedBytes:32*1024*1024}}
  25. assert.equal(result.transform.length,16)
  26. for(const b of buildings){const s=seat.buildings.find(s=>s.id===b.id);for(const k of ['footprintENU','positionENU','heightM','foundationBottom','terrainMin','terrainMax','terrainRelief','floors','construction'])assert.deepEqual(b[k],s[k],b.id+' '+k)
  27. assert.ok(b.sampleIndices.every(i=>Number.isInteger(i)&&i>=0&&i<samples.length))}
  28. mkdirSync(dir,{recursive:true});writeFileSync(join(dir,'seated-plan.json'),JSON.stringify(result,null,2))
  29. console.log(JSON.stringify({passed:true,buildings:ids,localSampleCount:samples.length,coordinateTransform:true,seatedFieldsPreserved:true,output:dir,configured:false}))