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 通过
This commit is contained in:
2026-08-23 15:22:19 +08:00
parent be6f2f5fe7
commit 10ed1e7d9b
14 changed files with 162 additions and 28 deletions
+87
View File
@@ -0,0 +1,87 @@
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)
})
})