Files
ChronicleOfTheImmortalClan/tests/aspiration-trib-bio.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

224 lines
7.3 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/world'
import { monthlyRate, resolveBreakthrough } from '../src/renderer/game/engine/systems/cultivation'
import { combatPowerOf } from '../src/renderer/game/engine/systems/combat'
import { isFit } from '../src/renderer/game/data/aspirations'
import { needsTribulation, resolveTribulation, tribulationEventId } from '../src/renderer/game/engine/systems/tribulation'
import { buildBiography } from '../src/renderer/game/core/biography'
import { applyEventChoice } from '../src/renderer/game/engine/systems/events'
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
}
describe('aspirations 志向', () => {
it('成年礼自动定志(16-19 岁补充)', () => {
const w = baseWorld('asp-a')
const c = w.state.members['x3'] // 16 岁
w.advanceMonth()
expect(c.aspiration).toBeTruthy()
})
it('求道志向修炼加快,云游稍缓', () => {
const w = baseWorld('asp-b')
const c = w.state.members['x5']
c.realm = { major: 'qi', minor: 1 }
c.aspiration = 'dao'
const dao = monthlyRate(w, c)
c.aspiration = 'yun'
const yun = monthlyRate(w, c)
expect(dao).toBeGreaterThan(yun)
c.aspiration = undefined
const none = monthlyRate(w, c)
expect(dao).toBeGreaterThan(none)
})
it('兵略志向提升战力,商略不影响战力', () => {
const w = baseWorld('asp-c')
const c = w.state.members['x4']
c.aspiration = 'bing'
const pb = combatPowerOf(w, c)
c.aspiration = 'shang'
const ps = combatPowerOf(w, c)
expect(pb).toBeGreaterThan(ps)
})
it('耕读志向提升灵田产出', () => {
const w = baseWorld('asp-d')
w.state.family.buildings = { lingtian: 1 }
w.state.members['x3'].aspiration = 'geng'
const c0 = w.state.family.inventory['lingcao'] ?? 0
w.advanceMonth()
const gained = (w.state.family.inventory['lingcao'] ?? 0) - c0
// 春季(月210 ×(1+耕读5%+春10%) ≈ 11~12
expect(gained).toBeGreaterThanOrEqual(11)
})
it('承宗志向提升家族添丁率', () => {
const w = baseWorld('asp-e')
let births = 0
for (const c of Object.values(w.state.members)) {
c.aspiration = 'cheng'
if (c.gender === 'male' && c.id !== 'x3') c.state = 'idle'
}
for (let i = 0; i < 120; i++) w.advanceMonth()
for (const c of Object.values(w.state.members)) {
if (c.bornYear > 1) births++
}
expect(births).toBeGreaterThanOrEqual(1)
})
})
describe('灵根契合', () => {
it('本命判定按主属性', () => {
expect(isFit('火', '火')).toBe(true)
expect(isFit('水', '火')).toBe(false)
})
it('契合者修炼更快', () => {
const w = baseWorld('fit-a')
const c = w.state.members['x5']
c.realm = { major: 'qi', minor: 1 }
c.techniqueId = 't-liehuo' // 火
c.roots = { grade: 3, primary: '火', secondary: [] }
const fit = monthlyRate(w, c)
c.roots = { grade: 3, primary: '水', secondary: [] }
const nofit = monthlyRate(w, c)
expect(fit).toBeGreaterThan(nofit)
})
it('契合者战力更高', () => {
const w = baseWorld('fit-b')
const c = w.state.members['x4']
c.techniqueId = 't-liehuo'
c.roots = { grade: 3, primary: '火', secondary: [] }
const fit = combatPowerOf(w, c)
c.roots = { grade: 3, primary: '水', secondary: [] }
const nofit = combatPowerOf(w, c)
expect(fit).toBeGreaterThan(nofit)
})
})
describe('渡劫', () => {
it('仅大境界晋升触发', () => {
const w = baseWorld('trb-a')
const c = w.state.members['x3']
c.realm = { major: 'qi', minor: 1 }
expect(needsTribulation(c)).toBe(false)
c.realm = { major: 'qi', minor: 8 }
expect(needsTribulation(c)).toBe(true)
c.realm = { major: 'foundation', minor: 2 }
expect(needsTribulation(c)).toBe(true)
c.realm = { major: 'spirit', minor: 2 }
expect(needsTribulation(c)).toBe(false)
})
it('触发后事件 id 唯一且可解析', () => {
const w = baseWorld('trb-b')
const c = w.state.members['x3']
c.realm = { major: 'foundation', minor: 2 }
c.realmProgress = 100
w.advanceMonth()
// 冷却 6 月保护下每月 chance 尝试——长跑 40 月要么事件出现要么仍在等待
let found = false
for (let i = 0; i < 40 && !found; i++) {
w.advanceMonth()
if (w.state.pendingEvent?.startsWith('ev-trib-')) found = true
}
expect(found).toBe(true)
})
it('硬渡成功晋升大境界', () => {
const w = baseWorld('trb-c')
let success = false
for (let attempt = 0; attempt < 30 && !success; attempt++) {
const c = w.state.members['x3']
c.realm = { major: 'qi', minor: 9 }
c.realmProgress = 100
c.mind = 9
c.health = 100
const r = resolveTribulation(w, c, 'rash')
if (r === 'success') {
expect(c.realm.major).toBe('foundation')
success = true
} else {
// 失败后被折断,复置后重试(保持独立样本)
c.realmProgress = 100
c.health = 100
}
}
expect(success).toBe(true)
})
it('压制一年延迟后恢复尝试通道', () => {
const w = baseWorld('trb-d')
const c = w.state.members['x3']
c.realm = { major: 'qi', minor: 9 }
c.realmProgress = 100
resolveTribulation(w, c, 'delay')
expect(c.tribDelayYear).toBe(w.state.year + 1)
// 未到年份时 cultivationTick 锁定 => 不会立动
w.advanceMonth()
expect(c.realmProgress).toBe(100)
})
it('护法失败时护法可能受伤但不会陨落', () => {
const w = baseWorld('trb-e')
const c = w.state.members['x3']
c.realm = { major: 'qi', minor: 9 }
c.realmProgress = 100
c.mind = 1 // 低心跳高失败
c.health = 100
const guardian = w.state.members['x4']
guardian.health = 100
const r = resolveTribulation(w, c, 'guard')
if (r === 'fail') {
expect(guardian.alive).toBe(true)
}
expect(w.state.family.stones).toBeLessThanOrEqual(800)
})
})
describe('biography 列传', () => {
it('活在生人:生平/修行脉络/婚育齐全', () => {
const w = baseWorld('bio-a')
const lines = buildBiography(w.state, 'x1')
const text = lines.map((l) => l.text).join('')
expect(text).toContain('第1代')
expect(text).toContain('生于-34年')
expect(lines.length).toBeGreaterThan(0)
})
it('逝者带墓志铭且包含死因', () => {
const w = baseWorld('bio-b')
const c = w.state.members['x5']
c.alive = false
c.deathYear = 40
c.deathCause = '渡劫陨落'
const lines = buildBiography(w.state, x5id(w))
const last = lines[lines.length - 1]
expect(last.label).toBe('墓志')
expect(last.text).toContain('雷霆') // 渡劫陨落专属纹样
})
it('突破脉络随史书记录增长', () => {
const w = baseWorld('bio-c')
const c = w.state.members['x4']
c.realm = { major: 'qi', minor: 8 }
c.realmProgress = 100
c.mind = 9
resolveBreakthrough(w, c, 0.9)
const lines = buildBiography(w.state, c.id)
expect(lines.some((l) => l.label === '修行')).toBe(true)
})
})
function x5id(w: World): string {
const c = Object.values(w.state.members).find((m) => m.id === 'x5')!
return c.id
}