// 可达性求解器:验证每关终点 / 存档点 / 苹果能否到达 // DJ=0,12,24 收紧二段跳时机;REL=0 禁用短跳 —— 检验是否只有"某一帧"能过 import { setupGlobals, reach, makeTargets } from './lib-reach.mjs'; setupGlobals(); const { LEVELS } = await import('../js/levels/index.js'); const DJSET = (process.env.DJ || '0,6,12,18,24,30').split(',').map(Number); const RELSET = (process.env.REL || '0,4,8,14').split(',').map(Number); let fail = 0; console.log(`=== 可达性求解 (DJ=${DJSET} REL=${RELSET}) ===`); for (const d of LEVELS) { if (d.type === 'boss') { console.log(` ${d.id.padEnd(3)} ${d.name.padEnd(17)} (BOSS 关跳过)`); continue; } let r; try { const { Level } = await import('../js/level.js'); const L = new Level(d); const targets = makeTargets(L); r = reach(L, { DJSET, RELSET, targets }); r.targets = targets; } catch (e) { console.log(` ✗ ${d.id} 求解异常: ${e.message}`); fail++; continue; } if (r.err) { console.log(` ✗ ${d.id.padEnd(3)} ${d.name.padEnd(17)} ${r.err}`); fail++; continue; } const goal = r.targets.find(t => t.type === 'goal'); const saves = r.targets.filter(t => t.type === 'save'); const apples = r.targets.filter(t => t.type === 'apple'); const okSave = saves.filter(s => s.hit).length; const okApple = apples.filter(a => a.hit).length; const badSave = saves.filter(s => !s.hit).map(s => `#${s.no ?? s.idx}`); const flag = goal && goal.hit ? ' ' : '✗'; if (!goal || !goal.hit) fail++; if (badSave.length) fail++; console.log(` ${flag} ${d.id.padEnd(3)} ${d.name.padEnd(17)} 终点=${goal ? (goal.hit ? 'OK' : '不可达') : '无'} 存档=${okSave}/${saves.length}${badSave.length ? ' 缺:' + badSave.join(',') : ''}${apples.length ? ` 苹果=${okApple}/${apples.length}` : ''} (状态${r.states})`); } console.log(fail ? `\n❌ ${fail} 项不可达` : '\n✅ 所有关卡终点与存档点均可达'); process.exit(fail ? 1 : 0);