v0.1.15: 深度审计加固(7 P0 + 20+ P1 歼灭)

【引擎层 P0】
- GameEngine.restore 双重时钟注册(回档后整月双跑)→ restore 重建内核+回置 rng 状态
- engineFromSnapshot 必崩(globalThis.__state 从未设)→ 修正带 seed 重建
- DEV 性能分支二次执行整月管线 → 只读告警(绝不重跑)
- 引擎 advance 遇待决事件死锁 → resolvePending(idx) 通道(自动化/测试推进)

【WorldSim 大修】
- NPC 换代文案垃圾(n-danxin行情降50%)→ 换代快讯专属文案
- secretQi 假闭环(纯写无读)→ 全秘境初始化+missions 统一 consumeSecretQi
- 快讯节流自锁(lastNewsMonth 恒 1 月)→ totalTicks%24
- power 月度收敛坍塌(1400±3)→ 换代/年首化+年度成长
- relation 双重拉零(4 家全 0)→ 交还外交年度漂移
- 市场基准 5 处重复 → POOL_BASE 单一权威;灾年 stale 清理;drift 各池独立噪声
- worldsim 能力卡补全 + normalize 兜底(worldSim/stats 子字段)

【UI/动效/存储】
- ModalShell 三条 CSS 规则真正落地(close 定位/body 滚动/footer 独立底排)——0.1.12 声明未实施项
- fx-canvas:resize 监听移除/mistTimer 清零/reduced 联动/soft 档无雾 + drift 走 gate(过滤器)
- 档位 density 脱钩修复;顶栏“止”钮补 onClick;版本号三处统一 0.1.15
- importAll 清 chronicle 表;自动推进季度落盘(手动逐次)
- 性能:LogFeed memo / Genealogy computeGenealogy memo / Chronicle deps 修正;死码清除(lastPhaseStats/clockFromKernel/哨兵函数等 12 处)
- 删除误入库的“p”游离文件

【测试】979 全绿(36 套件);金钟罩三档 0.1.15 基线固化;
typecheck/build 通过
This commit is contained in:
2026-08-23 13:52:51 +08:00
parent 26bd61fba0
commit c5fa2a3357
26 changed files with 220 additions and 208 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.11',
version: '0.1.15',
modules: this.world.systemList().length,
systems: this.world.systemList().filter((s) => s.enabled).length,
plugins: this.world.pluginList().length,
@@ -421,8 +421,7 @@ export function applyEventChoice(
}
const opt = def.options[optionIdx]
if (opt) {
if (formation) opt.eff.formation = formation
applyEffect(w, opt.eff, squad)
applyEffect(w, opt.eff, squad, undefined, formation)
if (def.once && !s.completedEvents.includes(def.id)) s.completedEvents.push(def.id)
}
s.pendingEvent = undefined
@@ -468,7 +467,7 @@ export function rankPower(w: World, c: Character): number {
return order.indexOf(c.realm.major) * 10 + c.realm.minor
}
export function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
export function applyEffect(w: World, eff: EffectDef, squad?: string[], _extra?: unknown, formation?: string): void {
const s = w.state
const fam = s.family
@@ -513,7 +512,7 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
if (!fam.techniques.includes(eff.addTech)) fam.techniques.push(eff.addTech)
}
if (eff.tournament) {
runTournament(w, squad, eff.formation as never)
runTournament(w, squad, formation as never)
}
if (eff.apprentice?.build) {
buildApprentice(w)
@@ -3,6 +3,7 @@ import { MissionState } from '../../../types/domain'
import { FormationId } from '../../../data/formations'
import { missionById, MissionDef, ENEMIES } from '../../../data/secrets'
import { resolveEncounter, rollWarbooty } from './combat'
import { WorldSim } from '../../sim/WorldSim'
import { techniqueById } from '../../../data/techniques'
import { describeRealm } from '../../../data/realms'
@@ -138,14 +139,9 @@ export function sendMission(w: World, defId: string, members: string[], formatio
const c = w.memberById(id)
c.state = 'expedition'
})
// 世界秘境灵气消耗(有 sim
if (w.state.worldSim?.secretQi) {
const qi = w.state.worldSim.secretQi[def.id] as number | undefined
if (qi !== undefined) {
w.state.worldSim.secretQi[def.id] = Math.max(0, qi - 6)
} else {
w.state.worldSim.secretQi[def.id] = 44
}
// 世界秘境灵气消耗(统一走 WorldSim 入口
if (w.state.worldSim) {
new WorldSim(w).consumeSecretQi(def.id)
}
w.state.missions.push(m)
w.state.family.missionIds.push(m.id)
+16 -8
View File
@@ -17,10 +17,6 @@ import { createWorldState, findInheritor } from './creation'
import { SYSTEM_DEFS, SystemDef } from './capabilities'
import { emptyClock } from './clocks'
import { Rng } from '../kernel/rng'
function clockFromKernel(_target: GameClock, source: GameClock): void {
void source
}
import { CotycPlugin, PluginContext, PluginStatus } from '../kernel/plugin'
import { PluginManager } from './pluginManager'
import { EventDef } from '../../data/events'
@@ -79,6 +75,15 @@ export function normalizeGameState(state: GameState): GameState {
if (!state.family.buildings) state.family.buildings = {}
if (!state.family.techniques) state.family.techniques = []
if (!state.family.missionIds) state.family.missionIds = []
if (!state.stats.tourneyHistory) state.stats.tourneyHistory = []
if (state.stats.tourneyBest === undefined) state.stats.tourneyBest = undefined
if (state.worldSim) {
state.worldSim.marketPool = state.worldSim.marketPool ?? {}
state.worldSim.npcDyn = state.worldSim.npcDyn ?? {}
state.worldSim.secretQi = state.worldSim.secretQi ?? {}
state.worldSim.newsFeed = state.worldSim.newsFeed ?? []
if (typeof state.worldSim.tide !== 'number') state.worldSim.tide = 0.5
}
if (state.family.headId && !state.members[state.family.headId]) {
const firstAlive = Object.values(state.members).find((c) => c.alive)
if (firstAlive) state.family.headId = firstAlive.id
@@ -275,10 +280,6 @@ export class World {
this.out.forEach((o) => o.onGameOver(reason, year))
}
gameOver___placeholder(): void {
void 0
}
memberById(id: Id): Character {
const c = this.state.members[id]
if (!c) throw new Error(`member not found ${id}`)
@@ -316,6 +317,13 @@ export class World {
this.pruneYearFlags()
}
applyEventChoice(eventId: string, idx: number): boolean {
const pid = this.state.pendingEvent
if (!pid) return false
applyEventChoice(this, pid, idx)
return true
}
requeueEvent(eventId: string): void {
const s = this.state
if (!s.eventQueue.includes(eventId)) s.eventQueue.push(eventId)