Files
digital-me-history/digital-me-backend/test/office5d.test.js
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

102 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)), '★★ 品第绝无百分比/游戏禁词(合规红线)');
});