数字我 Digital Me:全量入库(大文件走 Git LFS)
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { ZHANZHEN, matchWei, roleOf, zhanzhen, listZhanzhen, zhanzhenByDyn } from '../src/zhanzhen.js';
|
||||
|
||||
// 引擎纯逻辑用 mock 战役测(真战役数据由双证考据子代理陆续装入 ZHANZHEN)。
|
||||
const MOCK = {
|
||||
id: 'mock', dyn: 'tang', chao: 'tang', name: '模拟之战', year: 621,
|
||||
side: 'A vs B', stake: '赌注', intro: '引子', situ: '处境',
|
||||
weiTiers: { '主帅': 5, '列将': 4, '裨将': 3, '甲士': 1, '民夫': 0 },
|
||||
narr: { '主帅': '你居中军', '甲士': '你在阵列里' },
|
||||
nodes: [{ label: '关键节点', cite: '《旧唐书》:「……」' }],
|
||||
choices: {
|
||||
fengkou: { prompt: '身处风口——', options: [{ id: 'go', text: '战', outcome: '胜', end: { tone: 'sheng', word: '受赏' } }] },
|
||||
juzhong: { prompt: '身在局中——', options: [{ id: 'go', text: '冲', outcome: '死', end: { tone: 'wang', word: '身死' } }] },
|
||||
boji: { prompt: '被波及——', options: [{ id: 'go', text: '躲', outcome: '幸存', end: { tone: 'xian', word: '未定' } }] },
|
||||
},
|
||||
real: '战果', cite: '《旧唐书》:「……」', note: null,
|
||||
};
|
||||
|
||||
test('matchWei:取不高于你 tier 的最高一档;全低于则 null', () => {
|
||||
assert.equal(matchWei(MOCK.weiTiers, 5), '主帅');
|
||||
assert.equal(matchWei(MOCK.weiTiers, 3), '裨将');
|
||||
assert.equal(matchWei(MOCK.weiTiers, 2), '甲士');
|
||||
assert.equal(matchWei(MOCK.weiTiers, null), null);
|
||||
});
|
||||
|
||||
test('roleOf:顶档=风口,次档=局中,再低=波及,无=旁观', () => {
|
||||
assert.equal(roleOf('主帅', MOCK.weiTiers).key, 'fengkou');
|
||||
assert.equal(roleOf('列将', MOCK.weiTiers).key, 'juzhong');
|
||||
assert.equal(roleOf('甲士', MOCK.weiTiers).key, 'boji');
|
||||
assert.equal(roleOf(null, MOCK.weiTiers).key, 'pangguan');
|
||||
});
|
||||
|
||||
test('zhanzhen:按 tier 组装处境+对应抉择+双证战果', () => {
|
||||
ZHANZHEN.mock = MOCK;
|
||||
const top = zhanzhen('mock', 5);
|
||||
assert.equal(top.role.key, 'fengkou');
|
||||
assert.equal(top.you.wei, '主帅');
|
||||
assert.equal(top.choice.options[0].end.word, '受赏');
|
||||
assert.ok(/《.+》/.test(top.cite));
|
||||
const low = zhanzhen('mock', 1);
|
||||
assert.equal(low.role.key, 'boji');
|
||||
const out = zhanzhen('mock', null);
|
||||
assert.equal(out.role.key, 'pangguan');
|
||||
assert.equal(out.choice, null, '旁观无抉择');
|
||||
assert.equal(zhanzhenByDyn('tang').includes('mock'), true);
|
||||
assert.ok(listZhanzhen().some((b) => b.id === 'mock'));
|
||||
delete ZHANZHEN.mock;
|
||||
});
|
||||
|
||||
test('未知战役→null', () => {
|
||||
assert.equal(zhanzhen('nope', 5), null);
|
||||
});
|
||||
|
||||
// ── 真实双证战役数据校验(11场·zhanzhenData.js)──
|
||||
test('11场战役全部装入,字段完整、cite可查', () => {
|
||||
const list = listZhanzhen();
|
||||
assert.ok(list.length >= 11, '至少11场战役');
|
||||
const ids = new Set(list.map((b) => b.id));
|
||||
for (const id of ['julu', 'gaixia', 'baideng', 'kunyang', 'guandu', 'chibi', 'feishui', 'hulao', 'chanyuan', 'poyang', 'tumu']) {
|
||||
assert.ok(ids.has(id), `应含 ${id}`);
|
||||
}
|
||||
for (const id of ids) {
|
||||
const b = zhanzhen(id, 5);
|
||||
assert.ok(b.name && b.side && b.stake && b.intro && b.situ, `${id} 元信息完整`);
|
||||
assert.ok(/《.+》/.test(b.cite), `${id} 战果 cite 可查`);
|
||||
assert.ok(b.real, `${id} 有真实战果`);
|
||||
assert.ok(Object.keys(b.zaichang).length >= 5, `${id} 军职档≥5`);
|
||||
// 节点≥3;cite 须么带《篇名》双证、么为 null(诚实存疑节点·如"水灌鄢城非见史记")
|
||||
assert.ok(b.nodes.length >= 3 && b.nodes.every((n) => n.cite == null || /《.+》/.test(n.cite) || n.cite.length > 6), `${id} 节点≥3且双证/存疑标注`);
|
||||
// 抉择:至少一档;存在的每档每个选项都要有 outcome + 合法 end.tone(部分战役只给单一军职档的抉择,自选身份走该档)
|
||||
const choiceRoles = Object.keys(ZHANZHEN[id].choices || {});
|
||||
assert.ok(choiceRoles.length >= 1, `${id} 至少一档抉择`);
|
||||
for (const roleKey of choiceRoles) {
|
||||
const ch = ZHANZHEN[id].choices[roleKey];
|
||||
assert.ok(ch && ch.options.length >= 2, `${id}.${roleKey} 应有≥2选项`);
|
||||
assert.ok(ch.options.every((o) => o.outcome && ['sheng', 'wang', 'xian'].includes(o.end.tone)), `${id}.${roleKey} 选项须有outcome+合法end.tone`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('引擎朝映射:能算tier的战役 dyn 落在8引擎朝内', () => {
|
||||
const ENGINE = new Set(['tang', 'xihan', 'han', 'weijin', 'song', 'ming', 'liao', 'nanchao']);
|
||||
for (const b of Object.values(ZHANZHEN)) {
|
||||
if (b.dyn != null) assert.ok(ENGINE.has(b.dyn), `${b.id} 的 dyn=${b.dyn} 应是引擎朝`);
|
||||
}
|
||||
// 引擎朝各有对应战役(扩容后数量增多,校验包含关系)
|
||||
for (const id of ['poyang', 'tumu']) assert.ok(zhanzhenByDyn('ming').includes(id), `ming 应含 ${id}`);
|
||||
for (const id of ['chibi', 'guandu']) assert.ok(zhanzhenByDyn('han').includes(id), `han 应含 ${id}`);
|
||||
for (const id of ['gaixia', 'jingxing', 'mobei']) assert.ok(zhanzhenByDyn('xihan').includes(id), `xihan 应含 ${id}`);
|
||||
for (const id of ['hulao', 'yinshan', 'suiyang']) assert.ok(zhanzhenByDyn('tang').includes(id), `tang 应含 ${id}`);
|
||||
});
|
||||
Reference in New Issue
Block a user