import fs from "node:fs"; const scenePaths = [ ["map1", "k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-1/9ccaa5dabe08947e/scene.json"], ["map248", "k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-248/b27ea0d8d2a1a391/scene.json"] ]; function distance(left, right) { return Math.hypot(left.world.x - right.world.x, left.world.y - right.world.y); } for (const [label, scenePath] of scenePaths) { const scene = JSON.parse(fs.readFileSync(scenePath, "utf8")); const spawners = scene.items.filter((item) => item.shapeDefId === "shape:1232"); const rows = []; for (const item of spawners) { if (item.frame !== 0) { continue; } const qlo = item.quality & 0xff; const pairCandidates = spawners .filter((candidate) => candidate.id !== item.id && candidate.frame === 1 && ((candidate.quality & 0xff) === qlo) && distance(item, candidate) <= 512) .sort((left, right) => distance(item, left) - distance(item, right)); const pair = pairCandidates[0] ?? null; if (!pair) { continue; } rows.push({ controllerId: item.id, pairId: pair.id, auto: (item.mapNum & 0x08) === 0, controllerNpc: item.npcPreview?.name ?? null, controllerNpcNum: item.npcNum, pairNpc: pair.npcPreview?.name ?? null, pairNpcNum: pair.npcNum, qlo, distance: Math.round(distance(item, pair)), pairCount: pairCandidates.length }); } console.log(`\n=== ${label} ===`); const autoMismatched = rows.filter((row) => row.auto && row.controllerNpc && row.pairNpc && row.controllerNpc !== row.pairNpc); const blockedMismatched = rows.filter((row) => !row.auto && row.controllerNpc && row.pairNpc && row.controllerNpc !== row.pairNpc); const autoControllerMissingPairResolved = rows.filter((row) => row.auto && !row.controllerNpc && row.pairNpc); console.log(`auto mismatched valid pairs: ${autoMismatched.length}`); console.log(`blocked mismatched valid pairs: ${blockedMismatched.length}`); console.log(`auto unresolved-controller / resolved-pair: ${autoControllerMissingPairResolved.length}`); for (const row of autoMismatched.slice(0, 12)) { console.log(`AUTO ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`); } for (const row of autoControllerMissingPairResolved.slice(0, 12)) { console.log(`AUTO-UNRESOLVED ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`); } for (const row of blockedMismatched.slice(0, 12)) { console.log(`BLOCKED ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`); } }