Files
ChronicleOfTheImmortalClan/tests/tide-0.1.21.test.ts
T
thzxx 10ed1e7d9b v0.1.21: 大势流转(线程定论 + era景气双向闭环 + 世界史表 + 渲染管线)
【线程架构定论(实测)】
- 引擎与 UI 同在 renderer 主线程(单线程);2160 月实测 advanceMonth 平均 0.05ms/tick
- 结论:Worker 化无收益——0.05ms 不需要换线程;此前 60ms 告警来自 UI 渲染+存档(已修复)

【世界自演进闭环(双向因果,自演进最高形态)】
- era↔景气双向闭环:世界温度(marketPool 景气均值)调制 era 转移权重
  (tempEraK=0.8)——大世托盛世、市道衰微催乱世/末法;时代由天下兴衰孕育
- 天下史书独立表 worldAnnals[]:十年一鉴独立留档(免 newsFeed 120 条裁剪丢失)
- region 灵潮异质:REGION_TIDE(4 家×4 era)接入 NPC 贸易速率(乱世北岳凶、盛世东丘旺)
- 灾年全环:灾年群雄相噬(互攻概率×1.5)

【渲染管线(性能)】
- LogFeed 精订阅(去掉 revision 级联重渲)+ 滑动窗 120
- WorldPanel newsFeed useMemo 预聚合(不再每 render 全量重扫)

【测试】1018 全绿(43 套件):tide-0.1.21 7 例(延续档/温度/史表/区域/灾年/指纹敏感/滑动窗);
双金钟罩 0.1.21 基线固化;build 通过
2026-08-23 15:22:19 +08:00

88 lines
3.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 { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
import { WorldSimState, ERA_CONF, REGION_TIDE } from '../src/renderer/game/engine/sim/worldsim-data'
import { stateFingerprint } from './fingerprint.helper'
function worldMid(seed: string): World {
const w = World.create({ seed, surname: '澹', familyName: '澹台家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 360; i++) w.advanceMonth()
return w
}
describe('0.1.21 大势流转', () => {
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)
}
})
it('世界温度:景气高涨 → 向盛世转移权重提升(双向闭环)', () => {
const w = World.create({ seed: 't1', surname: '贺', familyName: '贺家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const sim = new WorldSim(w)
expect(sim.worldTemperature()).toBeGreaterThan(0.4)
// 池压到高景气 → 温度高
const ws = w.state.worldSim as WorldSimState
ws.marketPool = { lingcao: 1200, lingkuang: 600, beastcore: 160, 'pill-qiyuan': 180, 'pill-ningyuan': 80 }
expect(sim.worldTemperature()).toBeGreaterThan(1.5)
})
it('十年鉴入独立史表(worldAnnals),newsFeed 裁剪后仍留存', () => {
const w = World.create({ seed: 't2', surname: '燕', familyName: '燕家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 480; i++) w.advanceMonth()
const ws = w.state.worldSim as WorldSimState
expect((ws.worldAnnals ?? []).length).toBeGreaterThanOrEqual(4)
const feedEntries = ws.newsFeed.filter((r) => r.kind === 'annal').length
expect(feedEntries).toBeLessThanOrEqual((ws.worldAnnals ?? []).length + 2)
// 史表内容锚定
const last = (ws.worldAnnals ?? []).pop()!
expect(last.text).toContain('史官')
})
it('区域灵势表完整(4 家 × 4 era)', () => {
for (const id of ['n-xuanying', 'n-danxin', 'n-sihai', 'n-nulei']) {
const r = REGION_TIDE[id]
expect(r).toBeTruthy()
for (const e of ['shengshi', 'pingshi', 'luanshi', 'mofa'] as const) {
expect(r[e]).toBeTypeOf('number')
}
}
})
it('灾年 NPC 互攻概率提高(W4 全环)', () => {
const w = World.create({ seed: 't4', surname: '蓟', familyName: '蓟家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
const ws = w.state.worldSim as WorldSimState
const before = (ws.npcDyn['n-nulei']?.relationsWithOthers?.['n-xuanying'] ?? 0)
ws.calamity = '旱灾'
ws.calamityLeft = 6
expect(typeof before).toBe('number')
// 灾年期间长跑不炸(60 月)
for (let i = 0; i < 60; i++) {
w.state.worldSim = ws as never
w.state.month = 1
w.state.year++
w.advanceMonth()
}
expect(w.state).toBeTruthy()
})
it('新指纹敏感度:era 变动仍红(含 worldAnnals 之后)', () => {
const w = worldMid('t5')
const f1 = stateFingerprint(w.state)
const ws = w.state.worldSim as WorldSimState
ws.era = ws.era === 'mofa' ? 'shengshi' : 'mofa'
const f2 = stateFingerprint(w.state)
expect(f1).not.toBe(f2)
})
it('LogFeed 滑动窗语义(120 条上限的函数观点)', () => {
const arr = Array.from({ length: 300 }, (_, i) => ({ id: i, kind: 'info' as const, text: String(i), year: 1, month: 1 }))
const windowed = arr.slice(-120)
expect(windowed.length).toBe(120)
expect(windowed[0]!.id).toBe(180)
})
})