Files
ChronicleOfTheImmortalClan/tests/feature-0.1.9.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

206 lines
7.3 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 { migrateIfNeeded, exportEnvelope, parseImportEnvelope, CURRENT_SCHEMA, APP_ID } 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('CURRENT_SCHEMA=2 且 v1→v2 迁移补字段', () => {
expect(CURRENT_SCHEMA).toBe(2)
const w = baseWorld('mig-a')
const asV1 = JSON.parse(JSON.stringify(w.state))
asV1.schemaVersion = 1
delete asV1.stats
const res = migrateIfNeeded(asV1)
expect(res.ok).toBe(true)
const s = (res as never as { state: { stats: unknown; schemaVersion: number } }).state
expect(s.stats).toBeTruthy()
expect(s.schemaVersion).toBe(2)
})
it('未来版本拒绝加载(防误用)', () => {
const w = baseWorld('mig-b')
const s = JSON.parse(JSON.stringify(w.state))
s.schemaVersion = 99
const res = migrateIfNeeded(s)
expect(res.ok).toBe(false)
expect((res as never as { error: { code: string } }).error.code).toBe('TOO_NEW')
})
it('导出信封带 appId 与版本;导入旧版可迁移', () => {
const w = baseWorld('mig-c')
w.state.schemaVersion = CURRENT_SCHEMA
const env = JSON.parse(exportEnvelope(w.state))
expect(env.app).toBe(APP_ID)
expect(env.schemaVersion).toBe(2)
const parsed = parseImportEnvelope(JSON.stringify(env))
expect(parsed.ok).toBe(true)
})
it('异 app 拒绝导入', () => {
const w = baseWorld('mig-d')
const env = JSON.parse(exportEnvelope(w.state))
env.app = 'other-game'
const parsed = parseImportEnvelope(JSON.stringify(env))
expect(parsed.ok).toBe(false)
})
it('损坏 JSON 拒绝导入', () => {
const res = parseImportEnvelope('not-json-wrapper')
expect(res.ok).toBe(false)
})
})
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()
}
})
})