Files
thzxx 9b0f36c70a v0.1.35b: 兼容层铲除——迁移链/版本戳/死表死代码/normalize 兼容批/死字段
【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 全绿——金钟罩零漂(新档结构全字段,删除不涉世界数值)
2026-08-23 22:35:17 +08:00

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()
}
})
})