Files
ChronicleOfTheImmortalClan/tests/audit-0.1.20.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

107 lines
4.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/runtime/World'
import { fire, applyEventChoice } from '../src/renderer/game/engine/runtime/Systems/events'
import { ERA_CONF, ERA_DURA, WORLDSIM, WorldSimState } from '../src/renderer/game/engine/sim/worldsim-data'
import { stateFingerprint } from './fingerprint.helper'
describe('0.1.20 万世无谬', () => {
it('fire 高优可顶替普通 pending,被顶替者入队不丢失', () => {
const w = World.create({ seed: 'a1', surname: '岳', familyName: '岳家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
w.state.pendingEvent = undefined // 清月度事件闸(测试隔离)
fire(w, 'ev-legacypass') // 普通占位
expect(w.state.pendingEvent).toBe('ev-legacypass')
const ok = fire(w, `ev-raid-${Object.keys(w.state.npcFamilies)[0]}`, 1) // 高优顶替
expect(ok).toBe(true)
expect(w.state.pendingEvent).toBe(`ev-raid-${Object.keys(w.state.npcFamilies)[0]}`)
expect(w.state.eventQueue).toContain('ev-legacypass') // 未丢失
})
it('fire 普通被占则 false(调用方可冷却)', () => {
const w = World.create({ seed: 'a2', surname: '满', familyName: '满家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
w.state.pendingEvent = undefined
fire(w, 'ev-legacypass')
expect(fire(w, 'ev-legacypass')).toBe(false)
})
it('名宿传薪同一年仅一次(年锚 flag)', () => {
const w = World.create({ seed: 'a3', surname: '贺', familyName: '贺家', motto: 'm', difficulty: 'normal' })
w.state.year = 4 // year%8===4
w.state.completedEvents = []
let n = 0
for (let i = 0; i < 12; i++) {
if (w.state.year % 8 === 4 && !w.state.family.flag[`legacyDone-${w.state.year}`]) {
if (fire(w, 'ev-legacypass')) {
w.state.family.flag[`legacyDone-${w.state.year}`] = true
n++
}
w.applyEventChoice(w.state.pendingEvent ?? 'ev-legacypass', 0)
}
w.state.month++
if (w.state.month > 12) { w.state.month = 1; w.state.year++ }
}
expect(n).toBeLessThanOrEqual(1)
})
it('二次结算防线:陈旧 Modal 选择被忽略(pending 已清/易主)', () => {
const w = World.create({ seed: 'a4', surname: '冼', familyName: '冼家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
fire(w, 'ev-legacypass')
const before = w.state.pendingEvent
// 恶意重复选择:pending 已易主(自动压制)时 apply 不得落盘
w.state.pendingEvent = undefined
let threw = false
try {
applyEventChoice(w, before!, 0)
} catch {
threw = true
}
expect(threw).toBe(false) // 不崩
expect(w.state.pendingEvent).toBeUndefined() // 未二次应用
})
it('era 有延续档:flow 权重和 < 1(不再每届必换)', () => {
for (const k of Object.keys(ERA_CONF) as Array<keyof typeof ERA_CONF>) {
const sum = ERA_CONF[k].flow.reduce((a, [, wgt]) => a + wgt, 0)
expect(sum).toBeLessThan(1)
}
expect(ERA_DURA.pingshi[0]).toBeLessThan(ERA_DURA.pingshi[1])
})
it('潮汐供给正号:tide>1 → 供给乘 >1(灵涨必丰)', () => {
const multAt = (tide: number): number => Math.max(0.75, Math.min(1.35, 1 + (tide - 1) * WORLDSIM.tideSupplySpan))
expect(multAt(1.3)).toBeGreaterThan(1)
expect(multAt(0.7)).toBeLessThan(1)
})
it('断供/池底单常量一致:拒单阈值 == poolFloorPct', () => {
expect(WORLDSIM.tradeFloorPct).toBe(WORLDSIM.poolFloorPct)
})
it('rebalance 弱锚定:供需可撬动价格(1200 月池有超过 ±25% 振幅的时机)', () => {
const w = World.create({ seed: 'a9', surname: '郄', familyName: '郄家', motto: 'm', difficulty: 'normal' })
let min = Infinity
let max = -Infinity
for (let i = 0; i < 1200; i++) {
w.advanceMonth()
const pool = (w.state.worldSim as WorldSimState | undefined)?.marketPool?.['lingcao'] ?? 600
min = Math.min(min, pool / 600)
max = Math.max(max, pool / 600)
}
expect(max - min).toBeGreaterThan(0.5) // 弱锚定下真实起伏
})
it('指纹含世界轴(era/stance 变更会红)', () => {
const w = World.create({ seed: 'a10', surname: '苻', familyName: '苻家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 240; i++) w.advanceMonth()
const f1 = stateFingerprint(w.state)
const ws = w.state.worldSim as WorldSimState
ws.era = ws.era === 'mofa' ? 'shengshi' : 'mofa' // 确保改成异值
const dynKey = Object.keys(w.state.npcFamilies)[0]!
ws.npcDyn[dynKey]!.stance = ws.npcDyn[dynKey]!.stance === 'expand' ? 'endure' : 'expand'
const f2 = stateFingerprint(w.state)
expect(f1).not.toBe(f2)
})
})