Files
ChronicleOfTheImmortalClan/tests/stance-world-0.1.19.test.ts
T
thzxx ec25bf92fc v0.1.24: 寰宇初构——世界种子生成器 + 插件时轮锚点(1034 全绿)
【世界种子生成器(NPC 数量不再定死)】
- sim/worldgen.ts:generateWorld(seed) 确定性塑造天下——NPC 4~8 家随机
  (老牌世家模板 + 词库组合新贵,名字去重)、初始关系网(1-2世仇+1盟友)、
  开局 era 三态、区域风味、市场偏移 ±15%
- 独立派生 rng(seed::worldgen)——World.rng 主序列零消耗:同 seed 世界可重放、
  玩法随机序列不受生成器移动(金钟罩玩法序保护)
- state.worldGen 落档(normalize 旧档按 seed 重放=同世界,存档兼容)
- creation 初始化 npcFamilies/initSim 关系网/era/池偏置全走 worldgen——开局即恩怨
- 局内补位目标 = 开局家数(4~8),生灭闭环持续

【插件深化(0.1.24 第二组钩子)】
- PluginContext.beforePhase/afterPhase(时轮锚点:phase 前后挂勾,卸载全摘)
- PluginContext.addNpcTemplate(世界模板注入——词库插件化)
- plugin-public 文档同步;示例 Plugin 契约升级

【测试适配(世界名单随机化)】
- 静态 npc id 假设全面改动态取家(anyNpc helper 共享);
- 难度梯度用例改「同 seed 不同难度」比较;worldgen 确定性/范围/去重/关系对称 7 例

【测试】1034 全绿(45 套件);金钟罩 0.1.24 基线固化(worldgen 随机世界)+ worldAnnals 等;
build 通过
2026-08-23 16:15:22 +08:00

115 lines
5.6 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
const npcKey = Object.keys(w.state.npcFamilies).find((k) => k === 'n-nulei') ?? Object.keys(w.state.npcFamilies)[0]!
dyns.npcDyn[npcKey] = { ...dyns.npcDyn[npcKey]!, stance: 'expand', stanceSinceYear: 1 }
// 关系拉到仇深 + 伪造 raidCD 前一年,跑 240 月计数
const npc = w.state.npcFamilies[npcKey]
npc.relation = -70
npc.allied = false
let evcount = 0
for (let i = 0; i < 600; i++) {
npc.relation = -70 // 钉死(diplomacy drift 会拉回)
dyns.npcDyn[npcKey] = { ...dyns.npcDyn[npcKey], 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
const allyKey = Object.keys(w.state.npcFamilies).find((k) => k === 'n-xuanying') ?? Object.keys(w.state.npcFamilies)[0]!
const foeKey = Object.keys(w.state.npcFamilies).find((k) => k === 'n-nulei') ?? Object.keys(w.state.npcFamilies)[1]!
dyns.npcDyn[allyKey]!.relationsWithOthers[foeKey] = -70
w.state.npcFamilies[allyKey].relation = 60
const before = w.state.npcFamilies[foeKey].relation
expect(w.setAlliance(allyKey, true)).toBe(true)
expect(w.state.npcFamilies[foeKey].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
const aKey = Object.keys(w.state.npcFamilies)[0]!
const bKey = Object.keys(w.state.npcFamilies)[1]!
dyns.npcDyn[aKey]!.relationsWithOthers[bKey] = -60
w.state.family.stones = 500
const r0 = dyns.npcDyn[aKey]!.relationsWithOthers[bKey]
expect(w.mediateNpcs(aKey, bKey)).toBe(true)
const r1 = dyns.npcDyn[aKey]!.relationsWithOthers[bKey]
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()
})
})