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
@@ -137,7 +137,7 @@ export class GameFacade {
about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } {
return {
title: '仙途家族志',
version: '0.1.20',
version: '0.1.21',
modules: this.world.systemList().length,
systems: this.world.systemList().filter((s) => s.enabled).length,
plugins: this.world.pluginList().length,
@@ -100,6 +100,7 @@ export function normalizeGameState(state: GameState): GameState {
state.worldSim.eraStartYear = state.year ?? 1
}
if (typeof state.worldSim.overfluxYear !== 'number') state.worldSim.overfluxYear = -99
if (!Array.isArray(state.worldSim.worldAnnals)) state.worldSim.worldAnnals = []
if (state.worldSim.distress && typeof state.worldSim.distress !== 'object') state.worldSim.distress = undefined
for (const dyn of Object.values(state.worldSim.npcDyn)) {
if (dyn && typeof (dyn as { prosperity?: unknown }).prosperity !== 'number') {
+36 -6
View File
@@ -1,6 +1,6 @@
/** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts */
import { World } from '../runtime/World'
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, REGION_TIDE, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
import { pack } from '../../data/registry'
import { ITEMS } from '../../data/items'
import { npcById } from '../../data/npcs'
@@ -52,8 +52,17 @@ export class WorldSim {
const roll = rng.next()
let acc = 0
let moved = false
// W1 景气闭环:世界温度调制转移概率(大世托盛世、市道衰微催乱世)
const temp = this.worldTemperature()
const k = WORLDSIM.tempEraK
const tempMod = (next: string): number => {
if (next === 'shengshi') return 1 + (temp - 1) * k
if (next === 'luanshi' || next === 'mofa') return 1 + (1 - temp) * k
return 1
}
for (const [next, wgt] of flow) {
acc += wgt
const wgtMod = wgt * tempMod(next)
acc += wgtMod
if (roll < acc) {
s.era = next
s.eraStartYear = this.w.state.year
@@ -126,9 +135,13 @@ export class WorldSim {
if (distAge > 18 && !this.w.state.npcFamilies[s.distress.id]?.allied) s.distress = undefined
}
// ---- D2. 天下十年一鉴(史官综述) ----
// ---- D2. 天下十年一鉴(史官综述;入 newsFeed 展示 + 独立留档 ----
if (this.w.state.month === 1 && this.w.state.year % 10 === 0) {
s.newsFeed.push({ year: this.w.state.year, month: 1, src: '史官', text: decadeChronicle(s, this.w.state.year), kind: 'annal' })
const text = decadeChronicle(s, this.w.state.year)
s.newsFeed.push({ year: this.w.state.year, month: 1, src: '史官', text, kind: 'annal' })
if (!s.worldAnnals) s.worldAnnals = []
s.worldAnnals.push({ year: this.w.state.year, text })
if (s.worldAnnals.length > 80) s.worldAnnals.splice(0, s.worldAnnals.length - 80)
}
// ---- E. 天下快讯(节流) ----
@@ -192,6 +205,19 @@ export class WorldSim {
return ERA_CONF[(this.s().era ?? 'pingshi')].auctionMult
}
/** 世界温度(市场景气 0.5~2.4;era 转移的反馈输入) */
worldTemperature(): number {
const s = this.s()
let sum = 0
let n = 0
for (const id of MARKET_IDS) {
const base = poolBase(id)
sum += (s.marketPool[id] ?? base) / base
n++
}
return n > 0 ? sum / n : 1
}
news(): WorldSimState['newsFeed'] {
return this.s().newsFeed
}
@@ -269,7 +295,9 @@ function npcTrade(w: World, s: WorldSimState, noise: number): void {
const isCalamity = !!s.calamity
const stance2 = (dyn.stance ?? 'guardian') as string
const tradeMult = stance2 === 'expand' ? 1.3 : stance2 === 'endure' ? 0.7 : 1
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult
// W3 区域灵势:各 era 修正(e.g. 乱世北岳玄影峰灵衰 0.08)
const regionMod = 1 + (REGION_TIDE[id]?.[(s.era ?? 'pingshi') as 'shengshi'] ?? 0)
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult * regionMod
// 景气驱动(1-2):入不敷出则衰、仓廪常足则旺
let prosperity = dyn.prosperity ?? 50
// 卖:NPC 抛货 → 池增;有货自给,景气微涨
@@ -395,7 +423,9 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
const foeStance = dyn.stance ?? 'guardian'
const hateBar = foeStance === 'expand' ? -40 : foeStance === 'endure' ? -70 : -50
if (foeStance === 'ally') void hateBar
if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(WORLDSIM.npcEventChance)) {
// W4 灾年全环:天下凶年群雄相噬——互攻概率 ×1.5
const foeEventChance = (s.calamity ? WORLDSIM.npcEventChance * 1.5 : WORLDSIM.npcEventChance)
if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(foeEventChance)) {
// 1-3 蝴蝶效应:互攻按实力加权(强者越可能胜,弱者一败再败)
const wSum = npc.power + foe.power
const winner = w.rng.chance(wSum > 0 ? npc.power / wSum : 0.5) ? npc : foe
+14 -1
View File
@@ -48,6 +48,8 @@ export interface WorldSimState {
overfluxYear?: number
/** 盟友求援(最近一次:互攻失利波及盟我之族) */
distress?: { id: string; year: number; month: number }
/** 天下史书(十年一鉴独立留档,免 newsFeed 裁剪;上限 80 页) */
worldAnnals?: { year: number; text: string }[]
}
export function makeWorldSimState(): WorldSimState {
@@ -64,7 +66,8 @@ export function makeWorldSimState(): WorldSimState {
npcSuccessions: 0,
era: 'pingshi',
eraStartYear: 1,
overfluxYear: -99
overfluxYear: -99,
worldAnnals: []
}
}
@@ -103,6 +106,14 @@ export const ERA_DURA: Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', [numb
mofa: [20, 40]
}
/** 区域灵势(NPC id × era → 修正量 +-0.06~0.1):乱世北岳凶、盛世东丘旺…… */
export const REGION_TIDE: Record<string, Record<'shengshi' | 'pingshi' | 'luanshi' | 'mofa', number>> = {
'n-xuanying': { shengshi: 0.06, pingshi: 0, luanshi: -0.08, mofa: -0.06 },
'n-danxin': { shengshi: 0.04, pingshi: 0, luanshi: -0.04, mofa: -0.02 },
'n-sihai': { shengshi: 0.05, pingshi: 0.01, luanshi: -0.06, mofa: -0.04 },
'n-nulei': { shengshi: 0.02, pingshi: 0, luanshi: 0.05, mofa: -0.03 }
}
/** 世界演化参数表(全部可调) */
export const WORLDSIM = {
marketDriftRate: 0.03,
@@ -126,6 +137,8 @@ export const WORLDSIM = {
secretRecover: 2, // 灵气月恢复(潮高 ×2.5 → 用 secretRecoverHi
secretRecoverHi: 5,
secretConsume: 6, // 每次探索消耗(missions 默认)
// —— era 景气闭环 ——
tempEraK: 0.8, // 世界温度对 era 转移权重的调制强度(±0.8/单位温差)
// —— 盟约连坐(加性) ——
allianceGriefRaid: 0.012, // кажд 世仇盟连加性 raid 概率
// —— 天下事件 ——
+1
View File
@@ -197,6 +197,7 @@ export interface GameState {
eraStartYear?: number
overfluxYear?: number
distress?: { id: string; year: number; month: number }
worldAnnals?: { year: number; text: string }[]
newsFeed?: { year: number; month: number; src: string; text: string; itemId?: string; kind?: string }[]
lastNewsMonth?: number
npcSuccessions?: number