// 制度级关口单测(回响·涟漪追溯 B)—— 2026-07-20 立。 // 守:①合本表全部不变量(assertInvariants) ②无手游机制 ③加试挂了双证 impactChain、守旧没挂(事层不涟漪) // ④反相关不破(制层加试 fate<守旧) ⑤链端点双证/中段标推演(查不实标待补,不编)。 // 跑:node --test test/guankou-zhidu.test.js import test from 'node:test'; import assert from 'node:assert/strict'; import { assertInvariants, assertNoGameMechanics, ZHIDU_DECISIONS } from '../src/decisions.js'; test('①制度关口合本表全部不变量(assertInvariants CLEAN)', () => { assert.deepEqual(assertInvariants(ZHIDU_DECISIONS), [], '制度关口必须合全部不变量'); }); test('②无手游机制', () => { assert.deepEqual(assertNoGameMechanics(ZHIDU_DECISIONS), []); }); test('③加试=制层挂双证 impactChain;守旧=事层不挂(不涟漪)', () => { const d = ZHIDU_DECISIONS.find((x) => x.id === 'han-zhidu-chaju-shi'); assert.ok(d, '察举加试那道题在'); const jiashi = d.options.find((o) => o.id === 'jiashi'); const shoujiu = d.options.find((o) => o.id === 'shoujiu'); assert.equal(jiashi.layer, 'zhi', '加试=制层(真改制度)'); assert.ok(jiashi.impactChain?.nodes?.length >= 3, '加试挂了一条链'); assert.equal(shoujiu.layer, 'shi', '守旧=事层'); assert.ok(!shoujiu.impactChain, '守旧不涟漪 → 不挂链'); }); test('④反相关不破:制层加试 fate < 事层守旧(所念愈深其身愈危)', () => { const d = ZHIDU_DECISIONS[0]; const jiashi = d.options.find((o) => o.id === 'jiashi'); const shoujiu = d.options.find((o) => o.id === 'shoujiu'); assert.ok(jiashi.fate.score < shoujiu.fate.score, '制层动了世家命根子,下场更差'); }); test('⑤链:端点双证(kind yuanwen带cite)、中段标推演(kind tuiyan);一路通到1905', () => { const chain = ZHIDU_DECISIONS[0].options.find((o) => o.id === 'jiashi').impactChain; assert.equal(chain.nodes[0].kind, 'yuanwen', '起点(察举加试132)=原文双证'); assert.ok(chain.nodes.every((n) => n.cite?.source), '每一环都挂出处(端点双证/待补,中段标推演)'); assert.ok(chain.nodes.some((n) => n.kind === 'tuiyan'), '中段科举先声=制度史通说,标推演'); assert.equal(chain.nodes[chain.nodes.length - 1].year, 1905, '涟漪一路亮到科举废(1905)'); });