Files
ChronicleOfTheImmortalClan/tests/aspiration-trib-bio.test.ts
T
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 game/core+engine → 新 engine/ 域):
- engine/kernel/  时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core)
- engine/narrative/  legacy/报告/列传/谱系/年轴(原 core 叙事族)
- engine/runtime/   World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统)
- engine/sim/     Market(未来 WorldSim 同行)
- 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层)

验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化)
typecheck 0 error
2026-08-23 13:23:25 +08:00

224 lines
7.4 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 { monthlyRate, resolveBreakthrough } from '../src/renderer/game/engine/runtime/Systems/cultivation'
import { combatPowerOf } from '../src/renderer/game/engine/runtime/Systems/combat'
import { isFit } from '../src/renderer/game/data/aspirations'
import { needsTribulation, resolveTribulation, tribulationEventId } from '../src/renderer/game/engine/runtime/Systems/tribulation'
import { buildBiography } from '../src/renderer/game/engine/narrative/biography'
import { applyEventChoice } from '../src/renderer/game/engine/runtime/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
}