102 lines
7.2 KiB
JavaScript
102 lines
7.2 KiB
JavaScript
// office5d 定官模型 单测 —— 2026-07-21 立、07-23 老板「5 维加权铨选、每维可见影响、家世也认科举朝、零游戏味」重写。
|
||
// 锁死:①最低=低官 ②才是主力但一选不到顶·顶要叠满 ③家世(门阀朝重·科举朝也认「能读书」非0) ④分量单项也出分·平滑
|
||
// ⑤封顶诚实(技术官不越 t3·商极低·文满配到顶) ⑥主业分路 ⑦性格不再白选 ⑧家世·科举朝省一档(寒门难出贵子)
|
||
// ⑨每维单调推动 ⑩品第档位(parts.pins·非百分比游戏词)。官名全来自双证阶梯,不新编。
|
||
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { computeOffice5D, caiRating, xingRating, fenRating, menContrib } from '../src/office5d.js';
|
||
import { computeAxes } from '../src/dynasties.js';
|
||
import { subsFromAttrs } from '../src/attributes.js';
|
||
|
||
const FULL_SC = ['poetic', 'story', 'vivid', 'socratic']; // 才尽量满(本行 wen 三项 + 一项旁才轴)
|
||
const FULL_XG = ['warm', 'humor', 'playful', 'signature']; // 性格满(行为轴)
|
||
const FULL_CRED = { zhicheng: 3, jiangxiang: 3, zhiwu: 3 }; // 分量全高(合计 9)
|
||
function prof({ lei = 'wen', sc = [], xg = [], cred = {}, jiashi = 0, wang = 0, lao = 0, knowledge = 0, zheng = 0 } = {}) {
|
||
return { professionLei: lei, axes: computeAxes(subsFromAttrs(sc, xg)), credentials: cred, jiashi, wang, lao, knowledge, zhengming: zheng };
|
||
}
|
||
const T = (opts, dyn = 'tang') => computeOffice5D(prof(opts), dyn).rank.tier;
|
||
|
||
test('①最低(只主业) → 低官 t1,不是白身也不是高官', () => {
|
||
for (const dyn of ['weijin', 'tang', 'song', 'ming']) {
|
||
const r = computeOffice5D(prof({}), dyn);
|
||
assert.equal(r.rank.tier, 1, `${dyn}: 只主业应 t1`);
|
||
assert.ok(r.rank.title && r.rank.title !== '白身', `${dyn}: 应有实职官名`);
|
||
}
|
||
});
|
||
|
||
test('②才是主力但一选不到顶,顶级要叠满', () => {
|
||
assert.equal(T({}), 1, '空 → t1');
|
||
assert.equal(T({ sc: ['poetic'] }), 2, '1擅长 → t2');
|
||
assert.equal(T({ sc: ['poetic', 'story'] }), 2, '★ 2擅长仍 t2(不跳)');
|
||
assert.equal(T({ sc: FULL_SC }), 3, '★ 才满也只到 t3(刺史级),不到顶');
|
||
assert.equal(T({ cred: FULL_CRED }), 2, '★ 分量全高单独 → 仅 t2(不叠不到高位)');
|
||
assert.equal(T({ sc: FULL_SC, cred: FULL_CRED }), 4, '才满+分量满 → t4(侍郎)');
|
||
assert.equal(T({ sc: FULL_SC, cred: FULL_CRED, wang: 50, lao: 40, knowledge: 30 }), 5, '★ 再+改命满 → t5(顶级需近满配)');
|
||
});
|
||
|
||
test('③家世→级数:门阀朝重、科举朝也认(能读书·非0)、逐档单调', () => {
|
||
assert.ok(menContrib('weijin', 3) > menContrib('weijin', 2), '门阀 > 士族');
|
||
assert.ok(menContrib('weijin', 2) > menContrib('weijin', 1), '士族 > 庶族');
|
||
assert.ok(menContrib('tang', 3) > 0, '★★ 唐(科举朝)名门也认——家世好能读书,不再恒 0(老板拍板)');
|
||
assert.ok(menContrib('weijin', 3) > menContrib('tang', 3), '★ 同名门:门阀朝(魏晋)远重于科举朝(唐)');
|
||
assert.equal(menContrib('tang', 0), 0, '寒门 → 0');
|
||
});
|
||
|
||
test('④分量→档:单项也出分(修掉旧「单项=0」)、平滑单调、满到顶档', () => {
|
||
assert.equal(fenRating({}), 0, '空 → 0');
|
||
assert.ok(fenRating({ zhicheng: 3 }) > 0, '★★ 单项职称正高 → >0(不再白选,老板拍板)');
|
||
assert.ok(fenRating({ zhicheng: 3, jiangxiang: 3 }) > fenRating({ zhicheng: 3 }), '两项 > 一项(单调)');
|
||
assert.equal(fenRating(FULL_CRED), 4, '三项全高 → 顶档 4');
|
||
});
|
||
|
||
test('⑤封顶诚实:文满配到顶,技术官(医)全叠满也不越 t3,商极低', () => {
|
||
assert.equal(T({ sc: FULL_SC, cred: FULL_CRED, wang: 50, lao: 40, knowledge: 30 }), 5, '文满配 → 唐顶 t5');
|
||
const yi = computeOffice5D(prof({ lei: 'yi', sc: FULL_SC, xg: FULL_XG, cred: FULL_CRED, jiashi: 3, wang: 50, lao: 40, knowledge: 30 }), 'tang');
|
||
assert.ok(yi.rank.tier <= 3, '★ 医全叠满也不越 t3(不许医生当宰相)');
|
||
const shang = computeOffice5D(prof({ lei: 'shang', sc: FULL_SC, xg: FULL_XG, cred: FULL_CRED, jiashi: 3, wang: 50, lao: 40 }), 'tang');
|
||
assert.ok(shang.rank.tier <= 1, '★ 商满配封 t1(古代少入仕途)');
|
||
});
|
||
|
||
test('⑥主业分路:武人在有独立武选的朝(宋)走武阶', () => {
|
||
const wu = computeOffice5D(prof({ lei: 'wu', sc: ['blunt', 'motivate'], cred: FULL_CRED }), 'song');
|
||
assert.ok(/殿直|差使|巡检|砦|武/.test(wu.rank.title + wu.rank.trackName), `武人宋走武选,得:${wu.rank.title}/${wu.rank.trackName}`);
|
||
});
|
||
|
||
// ══ 2026-07-23 新增:证明「5 维都有可见影响」(旧版性格/分量单项/科举家世白选) ══
|
||
const MID = { sc: ['poetic', 'story'] }; // 才2 → 唐 t2(中位基准,逐维在此加满看推动)
|
||
|
||
test('⑦性格不再白选:中位上性格加满 → 官阶推动(旧版纹丝不动)', () => {
|
||
assert.equal(T(MID), 2, '中位才2 → t2');
|
||
assert.ok(caiRating(computeAxes(subsFromAttrs([], FULL_XG)), 'wen') === 0, '性格不进才轴(才=0)');
|
||
assert.ok(xingRating(computeAxes(subsFromAttrs([], FULL_XG))) > 0, '性格进行为轴(>0)');
|
||
assert.ok(T({ ...MID, xg: FULL_XG }) > T(MID), '★★ 性格满 → 官阶推动');
|
||
});
|
||
|
||
test('⑧家世·科举朝也影响 + 寒门难出贵子(名门省一档才)', () => {
|
||
assert.ok(T({ ...MID, jiashi: 3 }, 'tang') > T({ ...MID, jiashi: 0 }, 'tang'), '★ 唐(科举朝):名门 > 寒门');
|
||
const mingCai2 = T({ sc: ['poetic', 'story'], jiashi: 3 }, 'tang'); // 名门 + 才2
|
||
const hanCai2 = T({ sc: ['poetic', 'story'], jiashi: 0 }, 'tang'); // 寒门 + 才2
|
||
const hanCai3 = T({ sc: ['poetic', 'story', 'vivid'], jiashi: 0 }, 'tang'); // 寒门 + 才3
|
||
assert.ok(mingCai2 > hanCai2, '同才2:名门高于寒门');
|
||
assert.equal(mingCai2, hanCai3, '★★ 名门+才2 = 寒门+才3(寒门要多攒一档才追平——寒门难出贵子)');
|
||
});
|
||
|
||
test('⑨每维单调:中位上分量/改命/问鼎加满都能推动官阶(问鼎独立第6维·非死维)', () => {
|
||
assert.ok(T({ ...MID, cred: FULL_CRED }) > T(MID), '分量满 → 推动');
|
||
assert.ok(T({ ...MID, wang: 50, lao: 40, knowledge: 30 }) > T(MID), '改命满 → 推动');
|
||
assert.ok(T({ ...MID, zheng: 3 }) > T(MID), '问鼎满 → 推动');
|
||
// ★ 问鼎独立:改命已满时,问鼎再加仍推得动(修「改命满则问鼎死」)
|
||
const gaiMax = { ...MID, wang: 50, lao: 40, knowledge: 30 };
|
||
assert.ok(T({ ...gaiMax, zheng: 3 }) > T(gaiMax), '改命满时问鼎仍推动 → 无死维');
|
||
});
|
||
|
||
test('⑩品第档位:parts.pins 六维俱全(才/性格/分量/门第/改命/问鼎)、随维变、★ 零游戏味(无百分比/进度词)', () => {
|
||
const p = computeOffice5D(prof({ sc: FULL_SC, cred: FULL_CRED, jiashi: 3 }), 'weijin').parts;
|
||
for (const k of ['cai', 'xing', 'fen', 'men', 'gai', 'zheng']) {
|
||
assert.ok(p.pins?.[k] && typeof p.pins[k].label === 'string' && p.pins[k].label, `${k} 有品第文字`);
|
||
}
|
||
assert.equal(computeOffice5D(prof({ jiashi: 0 }), 'weijin').parts.pins.men.label, '寒门', '出身寒门品第');
|
||
assert.equal(computeOffice5D(prof({ jiashi: 3 }), 'weijin').parts.pins.men.label, '门阀', '出身门阀品第');
|
||
assert.ok(!/[%%]|进度|百分|完成度|升级|通关|解锁|成就|养成/.test(JSON.stringify(p)), '★★ 品第绝无百分比/游戏禁词(合规红线)');
|
||
});
|