// Static LOD gate: validates spatial cells, REPLACE hierarchy, nonempty content and LOD byte/triangle reduction. import { readFileSync, statSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' const dir = resolve(process.argv[2] || 'tools/city-massing/district/lod-preview'), tile = JSON.parse(readFileSync(join(dir, 'tileset.json')), 'utf8'), manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')) const fail = [] const root = tile.root || {} if (!Array.isArray(root.children) || root.children.length < 2) fail.push('root has fewer than two spatial children') if (!root.content?.uri) fail.push('root must provide a valid GLB anchor for runtime traversal') else { const anchor = readFileSync(join(dir, root.content.uri)) if (anchor.toString('ascii', 0, 4) !== 'glTF' || anchor.readUInt32LE(4) !== 2 || anchor.readUInt32LE(8) !== anchor.length || anchor.toString('ascii', 16, 20) !== 'JSON') { fail.push('root anchor GLB has invalid header') } else { try { JSON.parse(anchor.toString('utf8', 20, 20 + anchor.readUInt32LE(12))) } catch { fail.push('root anchor GLB has invalid JSON chunk') } } } const cellUris = [] for (const cell of root.children || []) { if (cell.refine !== 'REPLACE') fail.push('cell lacks REPLACE refinement') if (!cell.content?.uri) fail.push('cell lacks LOD1 content') else cellUris.push(cell.content.uri) if (cell.geometricError <= 0) fail.push('LOD1 cell geometricError must be positive') if (!Array.isArray(cell.children) || cell.children.length < 1) { fail.push('cell lacks LOD0 children') continue } for (const leaf of cell.children) { if (leaf.geometricError !== 0) fail.push('LOD0 leaf geometricError must be zero') if (!leaf.content?.uri) fail.push('LOD0 leaf lacks content') else cellUris.push(leaf.content.uri) } } for (const uri of cellUris) { if (!statSync(join(dir, uri), { throwIfNoEntry: false })) fail.push(`missing content ${uri}`) } const totals = manifest.totals || {} if (!(totals.lod1?.bytes < totals.lod0?.bytes)) fail.push('LOD1 bytes are not below LOD0') if (!(totals.lod1?.triangles < totals.lod0?.triangles)) fail.push('LOD1 triangles are not below LOD0') if (manifest.count <= 0 || manifest.cellCount < 2) fail.push('manifest is empty') if (fail.length) throw new Error(fail.join('\n')) console.log( JSON.stringify({ passed: true, scope: manifest.scope, cells: manifest.cellCount, count: manifest.count, low: totals.lod1, high: totals.lod0, }) )