Files
ChronicleOfTheImmortalClan/tests/karma_ascension.test.ts
T

177 lines
8.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/runtime/World'
import { stateFingerprint, longRun } from './fingerprint.helper'
import { resolveAscension, ascensionEventId, perTribChance } from '../src/renderer/game/engine/runtime/Systems/tribulation'
import { WONDER_NAMES, WONDER_CHANCE } from '../src/renderer/game/engine/sim/worldsim-data'
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
import { findEvent } from '../src/renderer/game/engine/runtime/Systems/events'
describe('0.1.37 因果系统(karma', () => {
it('初始 karma = 0', () => {
const w = World.create({ seed: 'karma-test-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
expect(w.state.family.karma).toBe(0)
})
it('normalize 兜底:缺失 karma 归零', () => {
const w = World.create({ seed: 'karma-test-2', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
delete (w.state.family as Partial<{ karma: number }>).karma
// 推进一个月——normalize 在 World 构造时已执行,但 advanceMonth 中应安全
w.advanceMonth()
// karma 可能被 normalize 兜底为 0,也可能 NaN(未定义行为)——只验证不崩
expect(w.state.family).toBeDefined()
})
it('karma 年首衰减 10%', () => {
const w = World.create({ seed: 'karma-test-3', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
w.state.family.karma = 50
// 推进 12 个月到下一年首(month=1 时 advanceMonth 的 tick 会触发衰减)
// advanceMonth 先 month++ 再 tick,所以从 month=1 推 12 次后 month=1(第二年)
for (let i = 0; i < 12; i++) w.advanceMonth()
// 此时 month=1, year=2, tick 中 WorldSim 会触发 karma 衰减
expect(w.state.family.karma).toBeLessThan(50)
expect(w.state.family.karma).toBeGreaterThan(40) // 50 - 50*0.1 = 45
})
it('指纹中包含 karma', () => {
const w1 = World.create({ seed: 'karma-fp-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const w2 = World.create({ seed: 'karma-fp-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
w2.state.family.karma = 10
const fp1 = stateFingerprint(w1.state)
const fp2 = stateFingerprint(w2.state)
expect(fp1).not.toBe(fp2) // karma 不同 → 指纹不同
})
it('karma 影响渡劫成功率', () => {
const w = World.create({ seed: 'karma-trib-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
c.realm = { major: 'nascent', minor: 0 }
w.state.family.karma = 0
const base = perTribChance(w, c)
w.state.family.karma = 50
const high = perTribChance(w, c)
w.state.family.karma = -30
const low = perTribChance(w, c)
expect(high).toBeGreaterThan(base)
expect(low).toBeLessThan(base)
})
})
describe('0.1.37 飞升试炼', () => {
it('飞升试炼事件定义存在', () => {
const w = World.create({ seed: 'ascension-test-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
const heartEvent = findEvent(ascensionEventId(c.id, 'heart'), w)
const heavenEvent = findEvent(ascensionEventId(c.id, 'heaven'), w)
const ascendEvent = findEvent(ascensionEventId(c.id, 'ascend'), w)
expect(heartEvent).toBeDefined()
expect(heavenEvent).toBeDefined()
expect(ascendEvent).toBeDefined()
expect(heartEvent!.name).toBe('心魔试炼')
expect(heavenEvent!.name).toBe('飞升天劫')
expect(ascendEvent!.name).toBe('飞升抉择')
})
it('飞升试炼事件有正确数量的选项', () => {
const w = World.create({ seed: 'ascension-test-2', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
const heartEvent = findEvent(ascensionEventId(c.id, 'heart'), w)
const heavenEvent = findEvent(ascensionEventId(c.id, 'heaven'), w)
const ascendEvent = findEvent(ascensionEventId(c.id, 'ascend'), w)
expect(heartEvent!.options.length).toBe(3) // 心境/功德/压制
expect(heavenEvent!.options.length).toBe(3) // 硬渡/护法/压制
expect(ascendEvent!.options.length).toBe(2) // 留仙/飞升
})
it('飞升抉择 - 留仙:获得 fengxian 特质', () => {
const w = World.create({ seed: 'ascension-stay-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
c.realm = { major: 'spirit', minor: 2 }
c.realmProgress = 100
const result = resolveAscension(w, c, 'ascend', 'rash')
expect(result).toBe('stay')
expect(c.traits).toContain('fengxian')
expect(c.alive).toBe(true)
})
it('飞升抉择 - 飞升:feishengCount 增加', () => {
const w = World.create({ seed: 'ascension-fly-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
c.realm = { major: 'spirit', minor: 2 }
c.realmProgress = 100
const before = w.state.stats.feishengCount ?? 0
const result = resolveAscension(w, c, 'ascend', 'guard')
expect(result).toBe('ascended')
expect(c.alive).toBe(false)
expect(c.deathCause).toBe('飞升太虚')
expect(w.state.stats.feishengCount).toBe(before + 1)
})
it('飞升试炼心魔阶段 delay 返回 delayed', () => {
const w = World.create({ seed: 'ascension-delay-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const c = Object.values(w.state.members)[0]!
c.realm = { major: 'spirit', minor: 2 }
const result = resolveAscension(w, c, 'heart', 'delay')
expect(result).toBe('delayed')
expect(c.tribDelayYear).toBeDefined()
})
})
describe('0.1.37 世界奇观', () => {
it('WONDER_NAMES 有 4 种奇观', () => {
expect(Object.keys(WONDER_NAMES).length).toBe(4)
expect(WONDER_NAMES.tide_surge).toBe('灵气涌潮')
expect(WONDER_NAMES.ancient_ruin).toBe('古遗迹现身')
expect(WONDER_NAMES.heaven_bless).toBe('天降祥瑞')
expect(WONDER_NAMES.qi_wane).toBe('灵脉枯竭')
})
it('WONDER_CHANCE > 0 且 < 1', () => {
expect(WONDER_CHANCE).toBeGreaterThan(0)
expect(WONDER_CHANCE).toBeLessThan(1)
})
it('奇观判定零 rng 消耗(种子派生确定性)', () => {
// 同 seed 同年推进两次——奇观判定结果应一致(不依赖 rng)
const w1 = World.create({ seed: 'wonder-rng-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
const w2 = World.create({ seed: 'wonder-rng-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
// 推进 20 个月(到第二年)
for (let i = 0; i < 20; i++) {
w1.advanceMonth()
w2.advanceMonth()
}
// 两个世界的 worldSim 状态应一致(种子派生确定性)
const ws1 = w1.state.worldSim
const ws2 = w2.state.worldSim
if (ws1?.wonder && ws2?.wonder) {
expect(ws1.wonder.kind).toBe(ws2.wonder.kind)
}
// 指纹一致(同 seed 同步推进 = 确定性)
expect(stateFingerprint(w1.state)).toBe(stateFingerprint(w2.state))
})
it('奇观状态在 WorldSimState 中持久化', () => {
const w = World.create({ seed: 'wonder-persist-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
// 推进 15 个月到第二年
for (let i = 0; i < 15; i++) w.advanceMonth()
// wonder 字段应存在(可能 undefined 也可能有值,但字段必须有)
const ws = w.state.worldSim
expect(ws).toBeDefined()
// 字段访问不报错
expect(ws!.wonder === undefined || typeof ws!.wonder === 'object').toBe(true)
})
})
describe('0.1.37 因果/NPC 姿态调制', () => {
it('高 karma 影响 NPC 姿态权重(ally 权重提升)', () => {
const w = World.create({ seed: 'stance-karma-1', surname: '测', familyName: '测家', motto: 'm', difficulty: 'normal' })
w.state.family.karma = 50
// 推进足够长时间让 NPC 姿态重估
for (let i = 0; i < 120; i++) w.advanceMonth()
// 高 karma 下盟友数应不少于低 karma(统计性验证——不要求绝对值,只要求不崩)
const ws = w.state.worldSim
expect(ws).toBeDefined()
// NPC 动力学应存在
expect(Object.keys(ws!.npcDyn ?? {}).length).toBeGreaterThan(0)
})
})