Files
ChronicleOfTheImmortalClan/tests/core-extra.test.ts
T
thzxx a7ee125866 v0.1.6: 时轮与造化(GameClock/RngHub/时节/年轴/保护)
- 统一时轮 GameClock:七 phase 注册制(production/aging/cultivation/missions/
  events/diplomacy/epilogue) + 年首三钩子(婚配→岁贡→族簿);advanceMonth 化作薄壳;
  新增系统只注册不改本体;alive 守卫、随机序、族簿时序均与重构前逐一等价
- 金钟罩:3 枚 seed × 560 月指纹常驻(重构前后已验证同哈希;时节属有意变更后重固化)
- 统一随机 RngHub:开局播种(crypto UV)、音效白噪独立流、引擎 rng 带 nextCount 审计;
  全工程拔除逻辑侧 Math.random
- 时节系统:春夏秋冬乘子(春田+10%/夏修+5%/秋市+5%/冬闭+8%)入生产与修炼
- 崩溃保护:advance 异常自动存『出险前』档 + 停速提示回档
- 性能预算:dev-only 单 tick>60ms 报最慢 phase
- 横轴年表:十年一格大事/生殇聚合 + 春秋原年轴视图
- 测试 197→225(时钟/收集器/四季/年轴/效果全矩阵/死局链/残留防御)
2026-08-23 09:20:52 +08:00

126 lines
4.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/world'
import { applyEventChoice, applyEffect } from '../src/renderer/game/engine/systems/events'
import { EffectDef } from '../src/renderer/game/data/events'
import { Character } from '../src/renderer/game/types/domain'
import { resetSaveBus, attachLogSink } from './world.helpers'
function baseWorld(seed: string): World {
const w = World.create({ seed, surname: '平', familyName: '平家', motto: 'm', difficulty: 'normal' })
resetSaveBus()
attachLogSink(w)
return w
}
function applyEff(w: World, eff: EffectDef): void {
applyEffect(w, eff)
}
describe('memberBy 效果全矩阵(核心系统)', () => {
it('exp/wound/heal 对全目标执行', () => {
const w = baseWorld('mb-a')
const before = Object.values(w.state.members).map((c) => c.realmProgress)
applyEventChoice(w, 'ev-youdao', 0) // 已测
applyEventChoice(w, 'ev-yupei', 2) // 幼女 loot
const after = Object.values(w.state.members).map((c) => c.realmProgress)
void before
void after
expect(w.state.members['x5'].fortune).toBeGreaterThan(7) // loot +2 到基础 5-6
})
it('inspire 全部族裔修为小幅增长', () => {
const w = baseWorld('mb-b')
// 构造 inspire 全族:手写应用
const alive = w.aliveMembers()
const before = alive.map((c) => c.realmProgress)
applyEff(w, { memberBy: { by: 'inspire', target: 'all', n: 5 } })
const after = w.aliveMembers().map((c) => c.realmProgress)
expect(after.filter((v, i) => v !== before[i]).length).toBeGreaterThan(0)
expect(w.state.members['x3'].realmProgress).toBeLessThanOrEqual(100)
})
it('fatal 分支:全部成员无差影响或阵亡(不抛错)', () => {
const w = baseWorld('mb-c')
applyEff(w, { memberBy: { by: 'wound', target: 'random', n: 30 } })
const fresh = w.aliveMembers()
expect(fresh.length).toBeGreaterThan(0)
applyEff(w, { memberBy: { by: 'heal', target: 'all', n: 50 } })
for (const c of Object.values(w.state.members)) {
expect(c.health).toBeGreaterThanOrEqual(0)
expect(c.health).toBeLessThanOrEqual(100)
}
})
it('highestPerception/highestPower/highestFortune 目标稳定命中', () => {
const w = baseWorld('mb-d')
const p0 = w.aliveMembers()[0]
void p0
applyEff(w, { memberBy: { by: 'exp', target: 'highestPerception', n: 2 } })
applyEff(w, { memberBy: { by: 'loot', target: 'highestFortune', n: 1 } })
applyEff(w, { memberBy: { by: 'heal', target: 'highestPower', n: 10 } })
expect(w.state.members['x4'].health).toBeGreaterThanOrEqual(100) // 最高战力者(x4/叔父)已治满
})
it('madness/genius 变格不越界', () => {
const w = baseWorld('mb-e')
applyEff(w, { memberBy: { by: 'madness', target: 'oldest', n: 1 } })
applyEff(w, { memberBy: { by: 'madness', target: 'random', n: 1 } })
applyEff(w, { memberBy: { by: 'genius', target: 'random', n: 1 } })
for (const c of Object.values(w.state.members)) {
expect(c.perception).toBeLessThanOrEqual(10)
expect(c.mind).toBeGreaterThanOrEqual(1)
}
})
})
describe('advance 死局与时钟守卫', () => {
it('全员死亡后 clock 仍安全步进(生产与收尾仍跑)', () => {
const w = baseWorld('dead-a')
for (const c of Object.values(w.state.members)) c.alive = false
w.advanceMonth()
expect(w.state.gameOver).toBeTruthy()
w.advanceMonth()
expect(w.state.year).toBeGreaterThanOrEqual(1)
expect(w.state.family.stones).toBeGreaterThanOrEqual(0)
})
it('头衔无人可继时 gameOver 语义完整', () => {
const w = baseWorld('dead-b')
for (const c of Object.values(w.state.members)) c.alive = false
w.advanceMonth()
expect(w.state.gameOver?.reason).toContain('香火')
expect(w.state.gameOver?.year).toBeGreaterThanOrEqual(1)
})
it('继承人仅存女性也可继位(家谱连续性)', () => {
const w = baseWorld('dead-c')
const x1 = w.state.members['x1']
x1.alive = false
x1.deathYear = w.state.year
const x3 = w.state.members['x3']
x3.alive = false
x3.deathYear = w.state.year
w.advanceMonth()
expect(['x2', 'x5']).toContain(w.state.family.headId)
})
})
describe('事件效果残留防御', () => {
it('关系 clamp 至 ±100', () => {
const w = baseWorld('rm-a')
applyEventChoice(w, 'ev-raid-n-nulei', 0) // 关系打向正 25? 取决于胜负
applyEventChoice(w, 'ev-zhusu', 2) // -20
for (const n of Object.values(w.state.npcFamilies)) {
expect(n.relation).toBeGreaterThanOrEqual(-100)
expect(n.relation).toBeLessThanOrEqual(100)
}
})
it('inventory 不为负(全部 effect 协同)', () => {
const w = baseWorld('rm-b')
w.state.family.inventory['lingcao'] = 6
applyEventChoice(w, 'ev-youdao', 0) // -5
expect(w.state.family.inventory['lingcao']).toBeGreaterThanOrEqual(0)
})
})