// 采【加权求和版】引擎的产出 —— 这一刀的「旧版」参照。 // // ★★ 这个脚本【只在改 dynasties.js 之前跑过一次】,跑完它采下来的 dynasties-weighted-sum.json // 就是死的。★ 绝不要在改完引擎之后再跑它来「更新」参照 —— 那等于把被告改成原告。 // // ⚠ 它自证真伪:采下来的五个单轨朝代,指纹必须与 dynasties-pre-multitrack.json 里 // 【旧代码采的那份】逐字相同。对得上 ⇒ 这份快照确实是旧引擎的产出,不是我编的。 // (本项目的铁律:最难发现的错是「假订正」。一份自己造的「旧版」参照,就是假订正的温床。) // // 跑:node scripts/capture-weighted-sum.mjs // // ⚠ 它住在 scripts/ 而不是 test/,是【故意的】:`node --test` 会把 test/ 底下的【每一个】文件 // 都当测试跑(不只是 *.test.js)。而本脚本的自证在引擎改完之后【必然抛异常】—— // 那是它设计上的正确行为,却会把 npm test 弄红。放在 scripts/ 下,它才是它该是的东西: // 一次性的采集工具,不是测试。 import crypto from 'node:crypto'; import fs from 'node:fs'; import { DYNASTIES, computeRank, profileFromSubs } from '../src/dynasties.js'; import { SUB_REGISTRY } from '../src/talents.js'; // ⚠ 必须与 dynasties-tracks.test.js 的 matrix() 【完全同构】,否则指纹对不上。 function matrix() { const ALL = SUB_REGISTRY.map((s) => s.id); const combos = [[]]; for (const s of ALL) combos.push([s]); for (let i = 0; i < ALL.length; i++) for (let j = i + 1; j < ALL.length; j++) combos.push([ALL[i], ALL[j]]); combos.push(['rigorous', 'datadriven', 'concise', 'shortfirst', 'patient', 'calm']); combos.push(['poetic', 'story', 'vivid']); combos.push(['socratic', 'humor', 'poetic', 'story', 'vivid']); combos.push(['blunt', 'motivate']); combos.push(ALL); const out = new Map(); for (const subs of combos) { for (const wang of [0, 30, 55, 60, 75, 80, 90, 100]) { out.set(`${subs.join('+') || '∅'}|w${wang}`, profileFromSubs(subs, { wang })); } } return [...out.entries()]; } // ── 自证:五个单轨朝代的指纹必须与【旧代码采的】 pre-multitrack fixture 对得上 ── const PRE = JSON.parse(fs.readFileSync(new URL('../test/fixtures/dynasties-pre-multitrack.json', import.meta.url), 'utf8')); const rows = matrix(); if (rows.length !== PRE._matrix.count) throw new Error(`矩阵变了:${rows.length} ≠ ${PRE._matrix.count}`); const sorted = rows.slice().sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)); for (const d of ['han', 'weijin', 'nanchao', 'tang', 'ming']) { const h = crypto.createHash('sha256'); for (const [k, p] of sorted) h.update(k + '|' + JSON.stringify(computeRank(p, d))); const got = h.digest('hex'); if (got !== PRE.hashes[d]) { throw new Error( `✗ ${d} 的指纹与 pre-multitrack 对不上 —— 当前代码【已经不是】加权求和版了。\n` + ' 这个脚本只能在改引擎【之前】跑。现在跑它,采到的是新引擎的产出,' + '整个「新旧对照」就成了自己跟自己比。' ); } } // ── 采:七朝 × 1128 profile 的【判定投影】── // ⚠ 只采判定相关的格子,不采 reasons/cites(那是文案,几十 MB,且不参与判定)。 const proj = (r) => (r === null ? null : { ch: r.channel ? r.channel.id : null, tier: r.rank.tier, title: r.rank.title, grade: r.rank.grade, unit: r.rank.unit, ceil: r.ceiling ? r.ceiling.tier : null, cat: r.category || '', stig: !!r.stigma, blk: r.blocked.length, }); const out = { _what: '【加权求和版】引擎(score += val(k)*weight → tier = round(score/100*5))的产出,七朝 × 1128 profile。', _why: '★ 这一刀【必须】让输出变(原文里没有加权求和的原型)。所以这个文件【不是迁移锁】—— ' + '它是【被告】:新引擎每改动一格,都要在 dynasties-shape.test.js 里说清楚是哪条原文要求它这么改。', _dontRegen: '★★ 绝不要用新引擎重新生成本文件。它的全部价值在于它是【旧代码】的产出。' + '重生成 = 把被告改成原告 = 本项目铁律里的「假订正」。' + '自证:五个单轨朝代的指纹与 dynasties-pre-multitrack.json 逐字相同(采集时已校验并通过)。', _capturedBy: 'scripts/capture-weighted-sum.mjs(2026-07-16,改引擎之前)', _matrix: PRE._matrix, _selfCheck: '✓ han/weijin/nanchao/tang/ming 五朝指纹 = dynasties-pre-multitrack.json 的指纹(采集时校验通过)', rows: Object.fromEntries( sorted.map(([k, p]) => [k, Object.fromEntries(DYNASTIES.map((d) => [d.id, proj(computeRank(p, d.id))]))]) ), }; fs.writeFileSync(new URL('../test/fixtures/dynasties-weighted-sum.json', import.meta.url), JSON.stringify(out)); console.log(`✓ 采到 ${sorted.length} 个 profile × ${DYNASTIES.length} 朝;五朝指纹自证通过。`);