0.1.37 天人感应·因果织锦:功德系统+飞升试炼+世界奇观+MOD扩展口

This commit is contained in:
2026-09-10 23:37:57 +08:00
parent 2984d05647
commit 87cd71ad21
24 changed files with 1062 additions and 43 deletions
+176
View File
@@ -0,0 +1,176 @@
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)
})
})