import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import {execFileSync} from 'node:child_process'; const assets = process.argv[2] ?? 'assets'; const output = process.argv[3]; const manifest = JSON.parse(fs.readFileSync(path.join(assets, 'TreeGeometry/manifest.json'))); const normalize = s => s.replaceAll('\\', '/').toLowerCase().replace(/^.*?ymir work\//, 'ymir work/'); const converted = new Set(Object.values(manifest.trees).map(r => normalize(r.source))); const list = pattern => execFileSync('rg', ['--files', assets, '-g', pattern], {encoding: 'utf8', maxBuffer: 8 * 1024 * 1024}).trim().split('\n').filter(Boolean); const references = new Set(); const propertyReferences = new Map(); for (const file of list('*.prt')) { const source = fs.readFileSync(file, 'utf8'); for (const match of source.matchAll(/^\s*treefile\s+"([^"]+)"/gmi)) { const reference = normalize(match[1]); references.add(reference); propertyReferences.set(source.split(/\r?\n/)[1].trim(), reference); } } const placedReferences = new Set(); for (const file of list('areadata.txt')) { const source = fs.readFileSync(file, 'utf8'); for (const match of source.matchAll(/^Start Object\d+\r?\n[^\n]*\n\s*(\d+)/gm)) { const reference = propertyReferences.get(match[1]); if (reference) placedReferences.add(reference); } } const unmatched = []; let matched = 0; for (const file of list('*.spt')) { const hash = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); if (manifest.trees[hash]) matched++; else unmatched.push({file, source_sha256: hash, referenced_by_property: references.has(normalize(file))}); } const missing_referenced = [...references].filter(r => !converted.has(r)).sort(); const missing_placed = [...placedReferences].filter(r => !converted.has(r)).sort(); const report = {matched_spt_files: matched, converted_species: converted.size, referenced_species: references.size, placed_species: placedReferences.size, missing_placed, missing_referenced, unconverted_spt: unmatched, scope: 'SPT inventory and text PRT/AreaData references; six-map runtime validation is separate'}; if (output) fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify({matched, converted: converted.size, referenced: references.size, placed: placedReferences.size, missing_placed, missing_referenced, unconverted: unmatched.length})); if (!placedReferences.size || missing_placed.length) process.exitCode = 1;