import fs from 'node:fs';import path from 'node:path';import {fileURLToPath} from 'node:url';import {definitions} from './definitions.mjs'; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');const result=[];let total=0; for(const def of definitions){const folder=path.join(root,'modes',def.id),rules=await import('../modes/'+def.id+'/rules.mjs'),pack=JSON.parse(fs.readFileSync(path.join(folder,'levels.json'),'utf8')),notes=JSON.parse(fs.readFileSync(path.join(folder,'design.json'),'utf8'));if(pack.levels.length!==8)throw Error('必须8关');const ids=new Set(); for(let i=0;i<8;i++){const l=pack.levels[i];if(ids.has(l.id))throw Error('重复关id');ids.add(l.id);rules.assertStructure(l);if(l.difficulty!==['入门','普通','进阶','难'][Math.floor(i/2)])throw Error('难度顺序'); const paths=rules.solve(l,{limit:2}),author=notes[i].author;if(paths.length!==1)throw Error('非唯一 '+def.id+' '+l.id);if(paths[0].join()!==author.join())throw Error('作者解不匹配'); const necessary=[];for(const r of rules.constraints(l)){const alternatives=rules.solve(l,{limit:2,omit:r.key});if(alternatives.length<2)throw Error('冗余机制 '+def.id+' '+l.id+' '+r.key);const saved=notes[i].witnesses.find(w=>w.constraint===r.key)?.path;if(!saved||rules.violations(l,saved,{omit:r.key}).length||!rules.violations(l,saved).length)throw Error('必要性证据无效');necessary.push({constraint:r.key,solutionsAfterRemoval:'至少2',witness:saved});} const digital=[];for(const c of l.cells.filter(c=>c.clue!==undefined&&c.clue!==1)){const copy=structuredClone(l);delete copy.cells.find(x=>x.id===c.id).clue;const minimal=rules.solve(copy,{limit:2}).length>=2;if(!minimal)throw Error('冗余辅助数字');digital.push({cell:c.id,step:c.clue,necessary:minimal});} if(i<7&&!l.cells.some(c=>c.clue===1)||i===7&&l.cells.some(c=>c.clue===1))throw Error('起点教学策略不符'); for(let k=0;k]+src=|fetch\(|type="module"/.test(html))throw Error('非单文件离线页面');if(html.includes('"author":')||html.includes('"witnesses":'))throw Error('作者资料进入试玩包'); } const report={generatedAt:new Date().toISOString(),levels:total,passed:total,modeCount:definitions.length,uniqueAll:true,mechanismsNecessaryAll:true,humanDifficultyCalibrated:false,results:result}; fs.writeFileSync(path.join(root,'reports','validation.json'),JSON.stringify(report,null,2)); fs.writeFileSync(path.join(root,'reports','VALIDATION.md'),'# 关卡自动校验报告\n\n'+`通过 ${total}/${total} 关,7 个玩法,每档两关。每关完整有序解唯一,逐项解除新机制后均至少出现两条完整解。辅助数字除教学起点外逐项必要。\n\n`+'| 玩法 | 关 | 难度 | 格数 | 必要机制数 | 起点 |\n|---|---|---|---:|---:|---|\n'+result.map(r=>`| ${r.title} | ${r.level} | ${r.difficulty} | ${r.n} | ${r.mechanisms.length} | ${r.unknownStart?'未知':'给定'} |`).join('\n')+'\n\n详细作者解与机制解除反例见 validation.json。自动通过不代表真人难度或推理品质已校准。\n'); console.log(`PASS ${total}/${total} levels; all mechanisms necessary; numeric clues minimized.`);