Files
ChronicleOfTheImmortalClan/tests/core-extra.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

126 lines
4.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/runtime/World'
import { applyEventChoice, applyEffect } from '../src/renderer/game/engine/runtime/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)
})
})