import { MONSTER_PRESETS, FAMOUS_ITEMS_META, classifyItemCategory, loadCanonicalTreasureClasses, calculateAnalyticProbabilities, runMonteCarloSimulation, buildReverseLookupIndex, reverseLookup, } from '../src/loot'; async function main() { console.log('=== [1] 验证 MONSTER_PRESETS 与 TC 完整性 ==='); const { tcTable, autoTcs } = loadCanonicalTreasureClasses(); console.log(`载入系统 TC 节点数: ${tcTable.all.length}, 自动生成 TC 节点数: ${autoTcs.size}`); let missingTcCount = 0; for (const mon of MONSTER_PRESETS) { for (const diff of ['normal', 'nightmare', 'hell'] as const) { const tcName = mon.tc[diff]; if (!tcTable.get(tcName) && !autoTcs.has(tcName)) { console.error(`❌ [缺失] 怪物 ${mon.nameCn} 在 ${diff} 难度的 TC 不存在: ${tcName}`); missingTcCount++; } if (mon.questTc) { const questTcName = mon.questTc[diff]; if (!tcTable.get(questTcName) && !autoTcs.has(questTcName)) { console.error(`❌ [缺失] 怪物 ${mon.nameCn} 在 ${diff} 难度的 首杀TC 不存在: ${questTcName}`); missingTcCount++; } } } } if (missingTcCount === 0) { console.log(`✅ 所有 ${MONSTER_PRESETS.length} 个怪物预设在全难度的基础 TC 与首杀 TC 校验 100% 存在!`); } else { throw new Error(`存在 ${missingTcCount} 个无效的 TC 引用`); } console.log('\n=== [2] 验证 FAMOUS_ITEMS_META 与物品分类器 ==='); const famousCount = Object.keys(FAMOUS_ITEMS_META).length; console.log(`已收录核心暗金与极品底模元数据条目: ${famousCount} 条`); // Test classifyItemCategory const r30Cat = classifyItemCategory('r30'); const shakoCat = classifyItemCategory('uap', 'unique'); const phaseBladeCat = classifyItemCategory('7cr'); const sojCat = classifyItemCategory('rin', 'unique'); const tokenCat = classifyItemCategory('tes'); console.log(`- 30# 贝 (r30) 分类: ${r30Cat} (期望: rune)`); console.log(`- 军帽 (uap, unique) 分类: ${shakoCat} (期望: unique)`); console.log(`- 幻化之刃 (7cr) 分类: ${phaseBladeCat} (期望: base)`); console.log(`- 戒指/护身符 (rin) 分类: ${sojCat} (期望: charm_gem)`); console.log(`- 洗点徽章 (tes) 分类: ${tokenCat} (期望: misc)`); if (r30Cat !== 'rune' || shakoCat !== 'unique' || phaseBladeCat !== 'base' || sojCat !== 'charm_gem' || tokenCat !== 'misc') { throw new Error('分类器逻辑测试失败'); } console.log('✅ 物品多维分类器运行正确!'); console.log('\n=== [3] 验证精确概率表解析 (Baal H 与 Andariel H) ==='); const baalOdds = calculateAnalyticProbabilities('Baal (H)', { tcTable, autoTcs, gamePlayers: 1, partyPlayers: 1 }); console.log(`Baal (H) - 叶子节点数: ${baalOdds.leaves.length}, Picks: ${baalOdds.picks}, NoDrop: ${(baalOdds.noDropCompoundProb * 100).toFixed(2)}%`); // Verify Shako / uap odds for Baal const uapLeaf = baalOdds.leaves.find(l => l.item === 'uap'); if (uapLeaf) { console.log(`- 军帽底模 (uap) 单次概率: ${(uapLeaf.singleProbability * 100).toFixed(4)}%, 综合概率: ${(uapLeaf.compoundProbability * 100).toFixed(4)}%, 几率比: 1 : ${uapLeaf.oneInOdds.toFixed(1)}`); } // Verify Quest Drop for Andariel const andyNormalOdds = calculateAnalyticProbabilities('Andariel (H)', { tcTable, autoTcs, gamePlayers: 1, partyPlayers: 1 }); const andyQuestOdds = calculateAnalyticProbabilities('Andarielq (H)', { tcTable, autoTcs, gamePlayers: 1, partyPlayers: 1 }); console.log(`Andariel (H) 普通杀 NoDrop: ${(andyNormalOdds.noDropCompoundProb * 100).toFixed(2)}%`); console.log(`Andarielq (H) Bug首杀 NoDrop: ${(andyQuestOdds.noDropCompoundProb * 100).toFixed(2)}%`); const andyNormalUap = andyNormalOdds.leaves.find(l => l.item === 'uap')?.compoundProbability || 0; const andyQuestUap = andyQuestOdds.leaves.find(l => l.item === 'uap')?.compoundProbability || 0; console.log(`- 安姐普通杀 军帽(uap) 几率: ${(andyNormalUap * 100).toFixed(4)}%`); console.log(`- 安姐Bug首杀 军帽(uap) 几率: ${(andyQuestUap * 100).toFixed(4)}% (提升 +${(((andyQuestUap - andyNormalUap) / andyNormalUap) * 100).toFixed(1)}%)`); const andyQuestHasJunk = andyQuestOdds.leaves.some(l => l.item === 'tsc' || l.item === 'isc' || l.item === 'gld'); if (andyQuestHasJunk) { throw new Error('安姐首杀 TC 不应包含卷轴或金币等垃圾杂项!'); } if (andyQuestUap <= andyNormalUap) { throw new Error('安姐首杀装备出货率应当显著高于普通击杀!'); } console.log('✅ 安姐首杀剔除杂质与装备出货率暴增验证通过!'); console.log('\n=== [4] 验证 1,000 次蒙特卡洛掉落模拟 (实测出货与品质分布) ==='); const simRes = await runMonteCarloSimulation({ tcName: 'Baal (H)', runs: 1000, gamePlayers: 3, partyPlayers: 1, magicFind: 250, difficulty: 'hell', tcTable, autoTcs, }); console.log(`模拟总轮数: ${simRes.totalRuns}`); console.log(`掉落总件数: ${simRes.itemsDropped} 件 (平均每轮 ${(simRes.itemsDropped / simRes.totalRuns).toFixed(2)} 件)`); console.log(`品质分布:`); console.log(` - Unique (暗金): ${simRes.qualityCounts.unique} 件`); console.log(` - Set (套装): ${simRes.qualityCounts.set} 件`); console.log(` - Rare (稀有): ${simRes.qualityCounts.rare} 件`); console.log(` - Magic (魔法): ${simRes.qualityCounts.magic} 件`); console.log(` - Rune (符文): ${simRes.qualityCounts.rune} 件`); console.log(` - Normal/Sup (底模): ${simRes.qualityCounts.normal + simRes.qualityCounts.superior} 件`); console.log(` - Gold (金币): ${simRes.qualityCounts.gold} 堆`); if (simRes.itemsDropped === 0) { throw new Error('1000次模拟依然为0件掉落!'); } console.log(`✅ 1,000 次模拟正常出货,共 ${simRes.itemSummaries.length} 种不同物品!`); console.log('\n=== [5] 验证反查索引 (Reverse Lookup) ==='); const revIndex = buildReverseLookupIndex(tcTable, autoTcs); const shakoRev = reverseLookup(revIndex, 'shako'); console.log(`搜索 "shako" 匹配到的掉落源数量: ${shakoRev[0]?.entries.length || 0} 个 TC`); if (shakoRev.length > 0 && shakoRev[0]!.entries.length > 0) { console.log(`- 最佳掉落源 TOP 1: ${shakoRev[0]!.entries[0]!.tcName}, 综合几率: ${(shakoRev[0]!.entries[0]!.compoundProbability * 100).toFixed(4)}%`); } else { throw new Error('反查 shako 失败'); } console.log('✅ 反查索引测试通过!'); console.log('\n========================================'); console.log('🎉 全部 Maxroll 怪物选择器与掉落列表核心功能实测 100% 成功!'); console.log('========================================'); } main().catch(err => { console.error(err); process.exit(1); });