【A+I】migrate.ts 迁移链(CURRENT_SCHEMA/MIGRATIONS/migrateIfNeeded/MigrationError)整链铲除→validateLoadedState 结构校验(seed/family/members 断言);信封统一 payload 单格式(去双格式兜底);APP_ID 保留 【F】schemaVersion 字段全删(domain/creation/save 8 处);SaveMeta.version 记游戏版本字符串 【C】chronicle 死表+saveChronicle 死方法(首行 return+unreachable legacy 僵尸)全删 【D/E】GameEngine __state globalThis 泄漏口+seed 三元(恒不可达)删除;engineFromSnapshot/noAutoCreate 保留(测试便利——非兼容机器) 【B/H】normalizeGameState 兼容批删:老档字段补(28行)/worldGen 重放(18行)/P0-2 占位 def(9行)/worldSim 字段批删;保留 npcs 重注册/headId 修复/d distress 防护;initSim 补 0.1.34 四键(唯一初始源) 【G】domain 收紧 5 兼容 optional(techniqueRank/Progress/tribBoost/worldGen 必填)+pcgen 必写 【测试】删 9 个兼容验证体(audit 0.1.0-era/全残缺矩阵/空 worldSim 兜底);5989 全绿——金钟罩零漂(新档结构全字段,删除不涉世界数值)
159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { World } from '../src/renderer/game/engine/runtime/World'
|
|
import { buildReport, verdictLine } from '../src/renderer/game/engine/narrative/legacyreport'
|
|
import { exportEnvelope, parseImportEnvelope, validateLoadedState } from '../src/renderer/game/storage/migrate'
|
|
import { FORMATIONS, formationById, FormationId } from '../src/renderer/game/data/formations'
|
|
import { resolveEncounter } from '../src/renderer/game/engine/runtime/Systems/combat'
|
|
import { ENEMIES } from '../src/renderer/game/data/secrets'
|
|
import { applyEventChoice } from '../src/renderer/game/engine/runtime/Systems/events'
|
|
import { sendMission } from '../src/renderer/game/engine/runtime/Systems/missions'
|
|
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('legacyreport 百年报告', () => {
|
|
it('报告含完整序列与死因榜', () => {
|
|
const w = baseWorld('rep-a')
|
|
for (let i = 0; i < 30; i++) w.advanceMonth()
|
|
const r = buildReport(w.state)
|
|
expect(r.years.length).toBeGreaterThan(0)
|
|
expect(r.population.length).toBe(r.years.length)
|
|
expect(r.reputation.length).toBe(r.years.length)
|
|
expect(r.totalYears).toBe(w.state.year)
|
|
})
|
|
|
|
it('短家族得到诚实结语', () => {
|
|
const w = baseWorld('rep-b')
|
|
const r = buildReport(w.state)
|
|
expect(verdictLine(r)).toContain('晨雾')
|
|
})
|
|
|
|
it('声望峰值触发盛世措辞', () => {
|
|
const w = baseWorld('rep-c')
|
|
w.state.year = 60
|
|
w.state.stats.repPeak = 75
|
|
for (let y = 1; y <= 12; y++) {
|
|
w.state.yearlyReports.push({ year: y, nets: 10, births: 1, deaths: 0, rep: 5 + (y >= 8 ? 70 : y), power: 100 })
|
|
}
|
|
const r = buildReport(w.state)
|
|
// 峰值 75 → 描写盛世
|
|
expect(verdictLine(r)).toMatch(/四方来贺/)
|
|
})
|
|
|
|
it('太虚榜首写入结语', () => {
|
|
const w = baseWorld('rep-d')
|
|
w.state.stats.tourneyHistory = [{ year: 40, rank: 1 }]
|
|
for (let y = 1; y <= 8; y++) {
|
|
w.state.yearlyReports.push({ year: y, nets: 0, births: 1, deaths: 0, rep: 5, power: 50 })
|
|
}
|
|
const r = buildReport(w.state)
|
|
expect(verdictLine(r)).toContain('第1名')
|
|
})
|
|
})
|
|
|
|
describe('战斗阵型', () => {
|
|
it.each(Object.keys(FORMATIONS).map((k) => [k as FormationId] as const))('%s 阵型定义合法', (id) => {
|
|
const f = FORMATIONS[id]
|
|
expect(f.atk).toBeGreaterThan(0.9)
|
|
expect(f.atk).toBeLessThanOrEqual(1.1)
|
|
expect(f.name.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('未知阵型回退锋阵', () => {
|
|
expect(formationById('xxx').id).toBe('vanguard')
|
|
expect(formationById(undefined).id).toBe('vanguard')
|
|
})
|
|
|
|
it('锋阵强攻:胜率期望更高(多次模拟占比)', () => {
|
|
let vanguardWins = 0
|
|
let serpentWins = 0
|
|
const enemy = ENEMIES.find((e) => e.id === 'e-muche')!
|
|
for (let trial = 0; trial < 40; trial++) {
|
|
const w = baseWorld(`form-a-${trial}`)
|
|
const c = w.state.members['x4']
|
|
c.realm = { major: 'foundation', minor: 1 }
|
|
const r1 = resolveEncounter(w, { title: 't', enemy, risk: 0.78, team: [c], kind: 'scout', year: 1, month: 1, formation: 'vanguard' })
|
|
if (r1.win) vanguardWins++
|
|
const w2 = baseWorld(`form-b-${trial}`)
|
|
const c2 = w2.state.members['x4']
|
|
c2.realm = { major: 'foundation', minor: 1 }
|
|
const r2 = resolveEncounter(w2, { title: 't', enemy, risk: 0.78, team: [c2], kind: 'scout', year: 1, month: 1, formation: 'serpent' })
|
|
if (r2.win) serpentWins++
|
|
}
|
|
expect(vanguardWins).toBeGreaterThan(serpentWins)
|
|
})
|
|
|
|
it('战报首行包含阵名', () => {
|
|
const w = baseWorld('form-c')
|
|
const c = w.state.members['x4']
|
|
c.realm = { major: 'core', minor: 0 }
|
|
const res = resolveEncounter(w, {
|
|
title: '阅兵',
|
|
enemy: ENEMIES.find((e) => e.id === 'e-huiyuan')!,
|
|
risk: 0.1,
|
|
team: [c],
|
|
kind: 'scout',
|
|
year: 1,
|
|
month: 1,
|
|
formation: 'echelon'
|
|
})
|
|
expect(res.lines.join(' ')).toContain('雁阵')
|
|
})
|
|
|
|
it('squad formation 传递到 mission 记录', () => {
|
|
const w = baseWorld('form-d')
|
|
const ok = sendMission(w, 'm-anmoku', ['x1'], 'serpent')
|
|
expect(ok).toBe(true)
|
|
expect(w.state.missions[0].formation).toBe('serpent')
|
|
})
|
|
})
|
|
|
|
describe('冬至岁祷', () => {
|
|
it('11 月触发岁祷(60% 概率下抽样命中或封缄)', () => {
|
|
const w = baseWorld('win-a')
|
|
w.state.year = 3
|
|
w.state.month = 10
|
|
let fired = false
|
|
for (let i = 0; i < 12; i++) {
|
|
w.advanceMonth()
|
|
if (w.state.pendingEvent === 'ev-winterprayer') {
|
|
fired = true
|
|
break
|
|
}
|
|
w.state.pendingEvent = undefined
|
|
}
|
|
expect(fired).toBe(true) // 0.6 概率在 12 次尝试下几乎必中(月每月 11 一次,12次意味着 years 3-4)
|
|
})
|
|
|
|
it('观星:声望+3', () => {
|
|
const w = baseWorld('win-b')
|
|
applyEventChoice(w, 'ev-winterprayer', 0)
|
|
expect(w.state.family.reputation).toBeGreaterThan(5)
|
|
})
|
|
|
|
it('祈福:全族修为+2', () => {
|
|
const w = baseWorld('win-c')
|
|
const c = w.state.members['x5']
|
|
c.realm = { major: 'qi', minor: 1 }
|
|
const p0 = c.realmProgress
|
|
c.realmProgress = 20
|
|
applyEventChoice(w, 'ev-winterprayer', 1)
|
|
expect(c.realmProgress).toBeGreaterThanOrEqual(p0)
|
|
})
|
|
|
|
it('问卜:吉凶均有确定性分支(连测无越界)', () => {
|
|
for (let i = 0; i < 10; i++) {
|
|
const w = baseWorld(`win-d-${i}`)
|
|
applyEventChoice(w, 'ev-winterprayer', 2)
|
|
expect(w.state.family.stones).toBeGreaterThanOrEqual(760) // 800±60 → ≥740? 800-40=760
|
|
expect(w.state.family.stones).toBeLessThanOrEqual(860)
|
|
expect(w.state.family.flag['prayerAsk']).toBeUndefined()
|
|
}
|
|
})
|
|
})
|