结构(旧 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
202 lines
6.7 KiB
TypeScript
202 lines
6.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
|
import { combatPowerOf, resolveEncounter, resolveRaid, rollWarbooty } from '../src/renderer/game/engine/runtime/Systems/combat'
|
|
import { ENEMIES } from '../src/renderer/game/data/secrets'
|
|
import { sendMission, recallAll } from '../src/renderer/game/engine/runtime/Systems/missions'
|
|
import { missionById } from '../src/renderer/game/data/secrets'
|
|
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('combat 战斗结算', () => {
|
|
it('战力复数化:高装备>低装备,闭关者加成累计', () => {
|
|
const w = baseWorld('cb-a')
|
|
const c = w.state.members['x4']
|
|
const p0 = combatPowerOf(w, c)
|
|
w.state.family.inventory['weapon-qi'] = 3
|
|
w.equip(c.id, 'weapon-qi')
|
|
const p1 = combatPowerOf(w, c)
|
|
expect(p1).toBeGreaterThan(p0)
|
|
c.techniqueRank = 2
|
|
expect(combatPowerOf(w, c)).toBeGreaterThan(p1)
|
|
})
|
|
|
|
it('重伤员战力折减(health 因子)', () => {
|
|
const w = baseWorld('cb-b')
|
|
const c = w.state.members['x3']
|
|
const p0 = combatPowerOf(w, c)
|
|
c.health = 20
|
|
const p1 = combatPowerOf(w, c)
|
|
expect(p1).toBeLessThan(p0)
|
|
expect(p1).toBeGreaterThan(p0 * 0.3)
|
|
})
|
|
|
|
it('家族战力随供奉职事提升', () => {
|
|
const w = baseWorld('cb-c')
|
|
const before = w.familyPower()
|
|
w.assignPost('x3', 'guardian')
|
|
w.assignPost('x4', 'guardian')
|
|
expect(w.familyPower()).toBeGreaterThan(before)
|
|
})
|
|
|
|
it('遭遇战必胜路径产出战利并写入战报', () => {
|
|
const w = baseWorld('cb-d')
|
|
const c = w.state.members['x4']
|
|
c.realm = { major: 'foundation', minor: 0 }
|
|
c.health = 100
|
|
const res = resolveEncounter(w, {
|
|
title: '模拟遭遇',
|
|
enemy: ENEMIES.find((e) => e.id === 'e-huiyuan')!,
|
|
risk: 0.2,
|
|
team: [c],
|
|
kind: 'scout',
|
|
year: w.state.year,
|
|
month: w.state.month
|
|
})
|
|
expect(res.win).toBe(true)
|
|
expect(res.loot).toBeTruthy()
|
|
expect(w.state.battles.length).toBe(1)
|
|
expect(w.state.battles[0].lines.length).toBeGreaterThan(2)
|
|
})
|
|
|
|
it('遭遇战必败路径全体重伤/损失且不崩塌', () => {
|
|
const w = baseWorld('cb-e')
|
|
const c = w.state.members['x3']
|
|
c.realm = { major: 'mortal', minor: 0 }
|
|
c.health = 50
|
|
const res = resolveEncounter(w, {
|
|
title: '必败模拟',
|
|
enemy: ENEMIES.find((e) => e.id === 'e-kuangzun')!,
|
|
risk: 1,
|
|
team: [c],
|
|
kind: 'scout',
|
|
year: w.state.year,
|
|
month: w.state.month
|
|
})
|
|
expect(res.win).toBe(false)
|
|
expect(c.health).toBeLessThanOrEqual(50)
|
|
expect(w.state.battles[w.state.battles.length - 1].winner).toBe('enemy')
|
|
})
|
|
|
|
it('raid 后关系变化方向正确', () => {
|
|
const w = baseWorld('cb-f')
|
|
const npc = w.state.npcFamilies['n-nulei']
|
|
npc.relation = -60
|
|
const pre = npc.power
|
|
const team = [w.state.members['x4']]
|
|
team[0].realm = { major: 'core', minor: 0 }
|
|
const res = resolveRaid(w, 'n-nulei', team)
|
|
if (res.win) {
|
|
expect(npc.relation).toBeGreaterThan(-60)
|
|
} else {
|
|
expect(npc.relation).toBeLessThan(-60)
|
|
expect(w.state.family.stones).toBeLessThanOrEqual(1000)
|
|
}
|
|
void pre
|
|
})
|
|
|
|
it('rollWarbooty 概率性产物写入库存或功法阁', () => {
|
|
const w = baseWorld('cb-g')
|
|
const loot = rollWarbooty(w, {
|
|
resources: { lingcao: [5, 5] },
|
|
artifactChance: 1,
|
|
techniqueChance: 1
|
|
})
|
|
expect(loot.lingcao).toBe(5)
|
|
const arti = Object.keys(loot).find((k) => k.startsWith('weapon'))
|
|
expect(arti).toBeTruthy()
|
|
expect(w.state.family.techniques.length).toBeGreaterThan(2)
|
|
})
|
|
|
|
it('战斗人数攻击力线性叠加(阵型抖动内保持单调)', () => {
|
|
const w = baseWorld('cb-h')
|
|
const a = w.state.members['x3']
|
|
const b = w.state.members['x4']
|
|
const solo = combatPowerOf(w, a)
|
|
const duo = combatPowerOf(w, a) + combatPowerOf(w, b)
|
|
expect(duo).toBeGreaterThan(solo)
|
|
})
|
|
})
|
|
|
|
describe('missions 探索任务', () => {
|
|
it('派遣属性校验:人数不足/重复派遣/并发上限', () => {
|
|
const w = baseWorld('ms-a')
|
|
expect(sendMission(w, 'm-guzhan', ['x1'])).toBe(false) // 不足 min 2
|
|
expect(sendMission(w, 'm-guzhan', ['x3', 'x4'])).toBe(true)
|
|
expect(sendMission(w, 'm-guzhan', ['x4', 'x5'])).toBe(false) // x4 已在途
|
|
expect(sendMission(w, 'm-anmoku', ['x4'])).toBe(false) // 在职者二次征召被拒
|
|
})
|
|
|
|
it('队伍出发后成员状态为出探,无法再被派', () => {
|
|
const w = baseWorld('ms-b')
|
|
sendMission(w, 'm-anmoku', ['x3'])
|
|
expect(w.state.members['x3'].state).toBe('expedition')
|
|
expect(sendMission(w, 'm-anmoku', ['x3', 'x5'])).toBe(false)
|
|
})
|
|
|
|
it('任务按期推进并最终完成,战利入库且状态归位', () => {
|
|
const w = baseWorld('ms-c')
|
|
const c = w.state.members['x5']
|
|
c.realm = { major: 'qi', minor: 5 } // 保证能打
|
|
const squad = [c, w.state.members['x3']]
|
|
sendMission(w, 'm-anmoku', squad.map((x) => x.id))
|
|
const m = w.state.missions[0]
|
|
let guard = 0
|
|
while (!m.done && guard < 40) {
|
|
if (w.state.gameOver) break
|
|
w.advanceMonth()
|
|
guard++
|
|
}
|
|
expect(m.done).toBe(true)
|
|
expect(['success', 'retreat', 'disband']).toContain(m.result)
|
|
// 若成功则状态归位闲居
|
|
if (m.result === 'success') {
|
|
expect(w.state.members['x3'].state).not.toBe('expedition')
|
|
expect(w.state.members['x5'].state).not.toBe('expedition')
|
|
}
|
|
})
|
|
|
|
it('召回立刻释放队员', () => {
|
|
const w = baseWorld('ms-d')
|
|
sendMission(w, 'm-anmoku', ['x3'])
|
|
const m = w.state.missions[0]
|
|
recallAll(w, m.id)
|
|
expect(m.done).toBe(true)
|
|
expect(w.state.members['x3'].state).toBe('idle')
|
|
})
|
|
|
|
it('全员告急:队员尽亡任务仍完成不挂死(保护)', () => {
|
|
const w = baseWorld('ms-e')
|
|
sendMission(w, 'm-anmoku', ['x3'])
|
|
const m = w.state.missions[0]
|
|
w.state.members['x3'].alive = false
|
|
w.state.members['x3'].deathYear = w.state.year
|
|
w.advanceMonth()
|
|
expect(m.done).toBe(true)
|
|
})
|
|
|
|
it('秘境阶段索引不会越界', () => {
|
|
const w = baseWorld('ms-f')
|
|
const def = missionById('m-anmoku')
|
|
const c = w.state.members['x5']
|
|
c.realm = { major: 'foundation', minor: 0 }
|
|
sendMission(w, 'm-anmoku', [c.id])
|
|
const m = w.state.missions[0]
|
|
let guard = 0
|
|
while (!m.done && guard < 60) {
|
|
w.advanceMonth()
|
|
guard++
|
|
if (m.stage >= def.stages.length + 1 && !m.done) {
|
|
// 不允许无限加厚
|
|
break
|
|
}
|
|
}
|
|
expect(m.stage).toBeLessThanOrEqual(def.stages.length)
|
|
})
|
|
})
|