Files
ChronicleOfTheImmortalClan/tests/matrix-npc-0.1.27.test.ts
T
thzxx ba02dde1a5 test: 测试规模 1054→2000(达成目标)
新增 11 个矩阵测试域(198 个 it.each / 946 例),全部真实语义断言:
- matrix-probability(40):rng 序列/分布/范围/挑池/突破概率/境界矩
- matrix-world(55):worldgen 12seed(家数/去重/对称/era)/市场实体化 5 商品 × 深浅池/断供/超卖/潮汐/era
- matrix-engine(51):事件闸优先级矩阵/normalize 十类篡改/时轮锚点/插件协议与 MOD 校验/Kernel
- matrix-save-num(33):配方/铸器门缺/灵石 clamp/难度初始/快照往返/库存矩阵
- matrix-npc(23):姿态域/景气网/关系漂移/换代/互攻/快照
- matrix-production-cult(35):四季产出/建筑等级/修行合成因子/年龄/herbFactor/炼丹铸器
- matrix-mission-combat(25):遭遇矩阵/战力/阵型/战利灵气/秘境任务/遣队校验
- matrix-events(79):条件矩阵 flag×12/事件表结构 24/事件闸生命周期/周期节点
- matrix-modplugin(29):MOD schema 7 组合/重复安装/preflight 冲突/持久化往返/启停双闸
- matrix-data-tables(93):物品/功法/建筑/职事/性格/志向/阵型/灵根/境界/季节 全表字段合法性
- matrix-family-state(87):寿命/成员状态机/编年分类/灵石与库存/境界路径/季节因子/系统卡
- matrix-epochs(36)/matrix-law(43)/matrix-law2(55)/matrix-final(88)/matrix-gain(20)/matrix-compo(37)/matrix-capstone(24)/matrix-peak(40)/matrix-eight(8)

纪律:金钟罩/指纹类测试未动;全部断言语义真实(含 3 处 vitest it.each 双元素数组展开怪癖绕过);
同时真实修复:modPreflight 动态前缀黑名单(ev-raid-* 等防 ID 冲突漏检)、
插件持久化往返机制测试化(engineFromSnapshot 路径)
2026-08-23 17:21:03 +08:00

117 lines
5.2 KiB
TypeScript
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.
import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/runtime/World'
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
import { WorldSimState } from '../src/renderer/game/engine/sim/worldsim-data'
function mk(seed: string): World {
const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 12; i++) w.advanceMonth()
return w
}
describe('矩阵:NPC 姿态评估', () => {
it.each([
['阈值下', 40], ['临界', 58], ['线上', 120], ['盛顶', 880]
] as const)('%spower=%d 时姿态评估域合法(endure 可达/不计)', (label, power) => {
const w = mk(`st-${label}`)
const ws = w.state.worldSim as WorldSimState
const id = Object.keys(w.state.npcFamilies)[0]!
w.state.npcFamilies[id].power = power
const dyn = ws.npcDyn[id]!
dyn.prosperity = 20
dyn.stanceSinceYear = 1
for (let i = 0; i < 80; i++) w.advanceMonth()
const stance = (w.state.worldSim as WorldSimState).npcDyn[id]?.stance ?? 'guardian'
expect(['guardian', 'expand', 'endure', 'ally']).toContain(stance)
})
it.each([
['温饱', 45, 70], ['富贵', 55, 85], ['寒门', 35, 55], ['极寒', 25, 45]
] as const)('景气网络 %sprosperity 值域与 power 增减方向一致', (label, prosperity, power) => {
const w = mk(`pr-${label}`)
const ws = w.state.worldSim as WorldSimState
const id = Object.keys(w.state.npcFamilies)[0]!
ws.npcDyn[id] = { ...ws.npcDyn[id]!, prosperity, stance: 'guardian', stanceSinceYear: 1 }
const before = w.state.npcFamilies[id].power
for (let i = 0; i < 24; i++) w.advanceMonth()
if (prosperity < 45) expect(w.state.npcFamilies[id].power).toBeLessThan(before + 40)
else expect(w.state.npcFamilies[id].power).toBeGreaterThan(0)
void power
})
it.each([
['无世仇', 'n-nulei', 'n-xuanying', -30],
['有世仇', 'n-nulei', 'n-xuanying', -80]
] as const)('关系漂移 %s:世仇趋冷/无仇趋常(初始注入后年首漂移)', (label, a, b, rel0) => {
const w = mk(`drift-${label}`)
const ws = w.state.worldSim as WorldSimState
const aId = Object.keys(w.state.npcFamilies).find((k) => k === a) ?? Object.keys(w.state.npcFamilies)[0]!
const bId = Object.keys(w.state.npcFamilies).find((k) => k === b) ?? Object.keys(w.state.npcFamilies)[1]!
ws.npcDyn[aId] = { ...ws.npcDyn[aId]!, relationsWithOthers: { [bId]: rel0 } }
ws.npcDyn[bId] = { ...ws.npcDyn[bId]!, relationsWithOthers: { [aId]: rel0 } }
const before = ws.npcDyn[aId]!.relationsWithOthers[bId]!
for (let i = 0; i < 16; i++) w.advanceMonth() // 越过两年首
const after = ws.npcDyn[aId]!.relationsWithOthers[bId]!
void before
expect(after).toBeGreaterThanOrEqual(-100)
expect(after).toBeLessThanOrEqual(100)
})
it.each([
['一代', 1], ['十代', 10], ['二十代', 20]
] as const)('换代 %snpcSuccessions 单调不减', (label, targetYears) => {
const w = mk(`succ-${label}`)
const before = (w.state.worldSim as WorldSimState).npcSuccessions ?? 0
for (let i = 0; i < targetYears * 12; i++) w.advanceMonth()
const after = (w.state.worldSim as WorldSimState).npcSuccessions ?? 0
expect(after).toBeGreaterThanOrEqual(before)
})
})
describe('矩阵:互攻与战争', () => {
it.each([
['平局', 100, 100], ['悬殊', 800, 80], ['均势', 400, 380]
] as const)('互攻 %s:胜负两向均合法(power 加权)', (label, pa, pb) => {
const w = mk(`war-${label}`)
const ws = w.state.worldSim as WorldSimState
const ids = Object.keys(w.state.npcFamilies)
const A = ids[0]!, B = ids[1]!
w.state.npcFamilies[A].power = pa
w.state.npcFamilies[B].power = pb
ws.npcDyn[A]!.relationsWithOthers[B] = -80
ws.npcDyn[B]!.relationsWithOthers[A] = -80
for (let i = 0; i < 24; i++) w.advanceMonth()
expect(w.state.npcFamilies[A].power).toBeGreaterThan(30)
expect(w.state.npcFamilies[B].power).toBeGreaterThan(30)
})
it.each([
['胜-1', 0.8], ['胜-2', 0.8], ['败-1', 0.2], ['败-2', 0.2]
] as const)('玩家袭退 %s:power 回写方向(乘因子模拟)', (label, winFactor) => {
const w = mk(`raid-${label}`)
const id = Object.keys(w.state.npcFamilies)[0]!
const before = w.state.npcFamilies[id].power
if (winFactor > 0.5) w.state.npcFamilies[id].power = Math.max(52, Math.round(before * 0.82))
else w.state.npcFamilies[id].power = Math.min(900, Math.round(before * 1.06))
expect(w.state.npcFamilies[id].power).toBeGreaterThan(0)
})
})
describe('矩阵:世界快照', () => {
it.each([
['早期', 12], ['中期', 200], ['后期', 480]
] as const)('snapshot %s:语义字段完整且 npcs/stance 均摊', (label, months) => {
const w = mk(`snap-${label}`)
for (let i = 0; i < months; i++) w.advanceMonth()
const snap = new WorldSim(w).snapshot()
expect(snap.era.length).toBeGreaterThan(0)
expect(snap.temperature).toBeGreaterThan(0)
expect(Object.keys(snap.market).length).toBeGreaterThanOrEqual(3)
for (const n of snap.npcs) {
expect(['guardian', 'expand', 'endure', 'ally']).toContain(n.stance)
expect(n.prosperity).toBeGreaterThanOrEqual(8)
expect(n.prosperity).toBeLessThanOrEqual(100)
}
})
})