Files
ChronicleOfTheImmortalClan/tests/stance-world-0.1.19.test.ts
T
thzxx e853fbdb71 v0.1.19: 八方风雨 + 孤立系统归并(双主线,1002 全绿)
【主线一:世界自演进深化《八方风雨》】
- NPC 战略姿态 stance:守成/扩张/隐忍/结盟四态,年首评估(era>景气>power>relation)
  全行为调制:raid 概率×2/×0.4/×0、互攻阈值 -40/-70/不攻、贸量 ×1.3/×0.7、
  赠礼收益 ally×1.3、结盟门槛 ally 放宽至30——NPC 从统计物理到行为战略
- 超卖潮(谷贱伤农):池>1.8×base 世界产能回调×0.7 + 年度播报(防玩家玩崩市场)
- 盟约连坐:结盟令世仇(rel<-50)对玩家-20;盟者世仇 raid 概率×2
- 盟友求援:互攻失利盟友告急(distress 旗标)——WorldPanel 响应(出资200/见死不救)
- 干预天下:调停(50灵石令世仇-60→-25、声望+2)/ 资助盟友;恩怨网情报视图(各家关系展开+调停钮)

【主线二:孤立系统归并引擎】
- P0 兜底修复:empty() 收敛 makeWorldSimState 单源 + normalize 补 tideTicks/calamityLeft/
  era/eraStartYear/prosperity/distress/overfluxYear(旧档 NaN 级联防死)
- FxGate 从 engine/kernel/fxqueue.ts 并入 ui/fx.ts(kernel 清纯引擎;deltas 顶层导出,
  3 处引用 10 行改动);engine 侧零 UI 依赖再下一城
- 音效语义补充:onFx→blade/pulse/spark→sWar/sGong/sBell;onLog kind 兜底保留(不静音)
- store.advance drift/season 段左移 fx.ts(fxSeason 复活接管 season 检测)
- 死码清除:timesense.tideOf/tintLabel、tideDir 全链(类型/初始/写入/domain)
- domain.ts worldSim 局部类型补全 0.1.18/0.1.19 全部新字段(断言类型安全)

【测试】1002 全绿(41 套件):stance-world 7 例(兜底/姿态/raid/超卖/连坐/调停/求援)
2026-08-23 14:53:46 +08:00

110 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/runtime/World'
import { normalizeGameState } from '../src/renderer/game/engine/runtime/World'
import { WorldSimState } from '../src/renderer/game/engine/sim/worldsim-data'
describe('0.1.19 八方风雨', () => {
it('兜底:空 worldSim 的旧档 normalize 后 tideTicks 有值(P0 修复)', () => {
const w = World.create({ seed: 'p0-1', surname: '甯', familyName: '甯家', motto: 'm', difficulty: 'normal' })
const state = w.state as never
const fixed = normalizeGameState({
...(state as Record<string, unknown>),
worldSim: { marketPool: {}, npcDyn: {}, secretQi: {}, newsFeed: [] }
} as never) as { worldSim: { tideTicks: number; era: string; calamityLeft: number } }
expect(fixed.worldSim.tideTicks).toBe(0)
expect(fixed.worldSim.era).toBe('pingshi')
expect(fixed.worldSim.calamityLeft).toBe(0)
})
it('stance:四姿态值域 + 年首(约8年)可再评估', () => {
const w = World.create({ seed: 'st-1', surname: '冀', familyName: '冀家', motto: 'm', difficulty: 'normal' })
const seen = new Set<string>()
for (let i = 0; i < 1600; i++) {
if (w.state.gameOver) break
w.advanceMonth()
const dyns = (w.state.worldSim as WorldSimState).npcDyn
for (const d of Object.values(dyns)) {
expect(['guardian', 'expand', 'endure', 'ally']).toContain(d.stance ?? 'guardian')
if (d.stance) seen.add(d.stance)
}
}
expect(seen.size).toBeGreaterThanOrEqual(2)
})
it('姿态影响 raid 概率标尺(expand 双倍、endure 打折)', () => {
const w = World.create({ seed: 'st-2', surname: '邱', familyName: '邱家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const dyns = w.state.worldSim as WorldSimState
dyns.npcDyn['n-nulei'] = { ...dyns.npcDyn['n-nulei']!, stance: 'expand', stanceSinceYear: 1 }
// 关系拉到仇深 + 伪造 raidCD 前一年,跑 240 月计数
const npc = w.state.npcFamilies['n-nulei']
npc.relation = -70
npc.allied = false
let evcount = 0
for (let i = 0; i < 600; i++) {
npc.relation = -70 // 钉死(diplomacy drift 会拉回)
dyns.npcDyn['n-nulei'] = { ...dyns.npcDyn['n-nulei'], stance: 'expand', stanceSinceYear: w.state.year }
w.advanceMonth()
// 任何 pending 顶住事件闸则先清场(否则后续 raid 无法 fire)
while (w.state.pendingEvent) {
if (w.state.pendingEvent.startsWith('ev-raid-')) evcount++
w.applyEventChoice(w.state.pendingEvent, 0)
}
}
// 0.06×12月×10年 ≈ 7 次期待,≥1 稳
expect(evcount).toBeGreaterThanOrEqual(1)
})
it('超卖潮:库存过剩时世界产能乘0.7回调且播报一次/年', () => {
const w = World.create({ seed: 'st-3', surname: '盛', familyName: '盛家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const ws = w.state.worldSim as WorldSimState
ws.marketPool['lingcao'] = 2000 // 远超 600*1.8
const before = ws.marketPool['lingcao']
w.advanceMonth()
const after = ws.marketPool['lingcao']
expect(after).toBeLessThan(before)
expect(ws.newsFeed.some((r) => r.text.includes('谷贱伤农'))).toBe(true)
})
it('结盟连坐:世仇得闻而暗恨(玩家关系-20)', () => {
const w = World.create({ seed: 'st-4', surname: '方', familyName: '方家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const dyns = w.state.worldSim as WorldSimState
dyns.npcDyn['n-xuanying']!.relationsWithOthers['n-nulei'] = -70
w.state.npcFamilies['n-xuanying'].relation = 60
const before = w.state.npcFamilies['n-nulei'].relation
expect(w.setAlliance('n-xuanying', true)).toBe(true)
expect(w.state.npcFamilies['n-nulei'].relation).toBeLessThan(before)
})
it('调停:花50灵石令世仇回暖、声望+2', () => {
const w = World.create({ seed: 'st-5', surname: '凌', familyName: '凌家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const dyns = w.state.worldSim as WorldSimState
dyns.npcDyn['n-danxin']!.relationsWithOthers['n-sihai'] = -60
w.state.family.stones = 500
const r0 = dyns.npcDyn['n-danxin']!.relationsWithOthers['n-sihai']
expect(w.mediateNpcs('n-danxin', 'n-sihai')).toBe(true)
const r1 = dyns.npcDyn['n-danxin']!.relationsWithOthers['n-sihai']
expect(r1).toBeGreaterThan(r0)
expect(w.state.family.reputation).toBeGreaterThanOrEqual(2)
})
it('盟友求援:互攻失利盟友告急 → assistAlly 可应', () => {
const w = World.create({ seed: 'st-6', surname: '古', familyName: '古家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const ws = w.state.worldSim as WorldSimState
ws.npcDyn['n-xuanying']!.relationsWithOthers['n-nulei'] = -80
ws.npcDyn['n-nulei']!.relationsWithOthers['n-xuanying'] = -80
w.state.npcFamilies['n-xuanying'].allied = true
w.state.family.stones = 1000
// 手动挂求援(互攻触发具随机性,此处测响应链)
ws.distress = { id: 'n-xuanying', year: w.state.year, month: w.state.month }
const p0 = w.state.npcFamilies['n-xuanying'].power
expect(w.assistAlly('n-xuanying')).toBe(true)
expect(w.state.npcFamilies['n-xuanying'].power).toBeGreaterThan(p0)
expect(ws.distress).toBeUndefined()
})
})