78 lines
4.1 KiB
JavaScript
78 lines
4.1 KiB
JavaScript
// ★ 第二刀的验收读数:【打平的那些 case,tier 和 ceiling 真的都一样吗?】
|
||
// 老板逐字:「⚠ 验证我这个判断:穷举里,打平的那些 case,tier 和 ceiling 真的都一样吗?
|
||
// 有没有「tier 一样但 ceiling 不一样」的?那种不是打平,那种引擎有理由(位阶即杠杆)。
|
||
// 跑出来,别信我。」
|
||
// ⚠ 读数全部出自引擎自己的 r.kai(产物),不是复算。
|
||
import { DYNASTIES, computeRank, computeAxes } from '../src/dynasties.js';
|
||
import { SUB_REGISTRY } from '../src/talents.js';
|
||
const SUBS = SUB_REGISTRY.map((s) => s.id);
|
||
const WANGS = [0, 29, 30, 54, 55, 74, 75, 100];
|
||
|
||
// 每一条【开着但没走】的路,按 (tier 关系, ceiling 关系) × lostAt 交叉列表
|
||
const X = {}; // key: `${tierRel}|${ceilRel}` → { lostAt: n }
|
||
const bump = (tr, cr, lostAt) => {
|
||
const k = `${tr}|${cr}`;
|
||
(X[k] || (X[k] = {}))[lostAt] = ((X[k] || {})[lostAt] || 0) + 1;
|
||
};
|
||
// zelu.options 的自检
|
||
let optBad = 0, optN = 0, optTierDiff = 0, optCeilDiff = 0;
|
||
// 引擎的排序器是不是一个自洽的全序:ranked[i] 必须真的赢 ranked[j] (i<j)
|
||
let sortViol = 0;
|
||
// 「tier 一样 && ceiling 一样」的总条次,按 lostAt 分
|
||
const SAME = {};
|
||
let totalNotWalked = 0;
|
||
|
||
const t0 = Date.now();
|
||
for (let m = 0; m < (1 << 16); m++) {
|
||
const subs = []; for (let i = 0; i < 16; i++) if (m & (1 << i)) subs.push(SUBS[i]);
|
||
const axes = computeAxes(subs);
|
||
for (const wang of WANGS) for (const d of DYNASTIES) {
|
||
const r = computeRank({ axes, wang }, d.id);
|
||
if (!r.channel) continue;
|
||
const walked = r.kai.find((k) => k.walked);
|
||
for (const k of r.kai) {
|
||
if (k.walked) continue;
|
||
totalNotWalked++;
|
||
const tr = k.tier === walked.tier ? '=' : k.tier < walked.tier ? '<' : '>';
|
||
const cr = k.ceiling === walked.ceiling ? '=' : k.ceiling < walked.ceiling ? '<' : '>';
|
||
bump(tr, cr, k.lostAt);
|
||
if (tr === '=' && cr === '=') SAME[k.lostAt] = (SAME[k.lostAt] || 0) + 1;
|
||
}
|
||
// zelu.tie:里面的每一条,tier 与 ceiling 必须与走的那条【全部相同】
|
||
const opts = (r.zelu.tie || []).map((t) => t.channelId);
|
||
if (opts.length > 1) {
|
||
optN++;
|
||
for (const id of opts) {
|
||
const k = r.kai.find((x) => x.channelId === id);
|
||
if (!k) { optBad++; continue; }
|
||
if (k.tier !== walked.tier) optTierDiff++;
|
||
if (k.ceiling !== walked.ceiling) optCeilDiff++;
|
||
}
|
||
}
|
||
// 排序自洽性:kai 的次序就是 ranked 的次序
|
||
for (let i = 0; i < r.kai.length; i++)
|
||
for (let j = i + 1; j < r.kai.length; j++)
|
||
if (r.kai[j].tier > r.kai[i].tier ||
|
||
(r.kai[j].tier === r.kai[i].tier && r.kai[j].ceiling > r.kai[i].ceiling)) sortViol++;
|
||
}
|
||
}
|
||
const secs = ((Date.now() - t0) / 1000).toFixed(0);
|
||
const N = (n) => n.toLocaleString();
|
||
console.log(`\n══ 【开着但没走】的路:(tier, ceiling) × 输在哪一级 ── ${N(totalNotWalked)} 条次 / ${secs}s ══\n`);
|
||
console.log('tier ceiling │ ' + ['ceiling', 'tier', 'lex', 'idx', 'unsorted'].map((s) => s.padStart(10)).join('') + ' 合计');
|
||
console.log('─'.repeat(78));
|
||
for (const key of Object.keys(X).sort()) {
|
||
const [tr, cr] = key.split('|');
|
||
const row = X[key];
|
||
const tot = Object.values(row).reduce((a, b) => a + b, 0);
|
||
console.log(` ${tr} ${cr} │ ` +
|
||
['ceiling', 'tier', 'lex', 'idx', 'unsorted'].map((s) => N(row[s] || 0).padStart(10)).join('') +
|
||
' ' + N(tot).padStart(10));
|
||
}
|
||
console.log('\n★ 【tier 一样 && ceiling 一样】的条次,按输在哪一级分:');
|
||
const sameTot = Object.values(SAME).reduce((a, b) => a + b, 0);
|
||
for (const k of Object.keys(SAME)) console.log(` ${k.padEnd(10)} ${N(SAME[k]).padStart(11)} (${(SAME[k] / sameTot * 100).toFixed(1)}% of ${N(sameTot)})`);
|
||
console.log(`\n★ zelu.options:${N(optN)} 格有 >1 个选项;` +
|
||
`其中 tier 不同 ${N(optTierDiff)} 次、ceiling 不同 ${N(optCeilDiff)} 次、kai 里找不到 ${N(optBad)} 次`);
|
||
console.log(`★ 排序自洽性(kai 次序里出现「后面的 tier/ceiling 反而更高」):${N(sortViol)} 次`);
|