Files
ChronicleOfTheImmortalClan/src/renderer/game/engine/GameEngine.ts
T
thzxx 9b0f36c70a v0.1.35b: 兼容层铲除——迁移链/版本戳/死表死代码/normalize 兼容批/死字段
【A+I】migrate.ts 迁移链(CURRENT_SCHEMA/MIGRATIONS/migrateIfNeeded/MigrationError)整链铲除→validateLoadedState 结构校验(seed/family/members 断言);信封统一 payload 单格式(去双格式兜底);APP_ID 保留
【F】schemaVersion 字段全删(domain/creation/save 8 处);SaveMeta.version 记游戏版本字符串
【C】chronicle 死表+saveChronicle 死方法(首行 return+unreachable legacy 僵尸)全删
【D/E】GameEngine __state globalThis 泄漏口+seed 三元(恒不可达)删除;engineFromSnapshot/noAutoCreate 保留(测试便利——非兼容机器)
【B/H】normalizeGameState 兼容批删:老档字段补(28行)/worldGen 重放(18行)/P0-2 占位 def(9行)/worldSim 字段批删;保留 npcs 重注册/headId 修复/d distress 防护;initSim 补 0.1.34 四键(唯一初始源)
【G】domain 收紧 5 兼容 optional(techniqueRank/Progress/tribBoost/worldGen 必填)+pcgen 必写
【测试】删 9 个兼容验证体(audit 0.1.0-era/全残缺矩阵/空 worldSim 兜底);5989 全绿——金钟罩零漂(新档结构全字段,删除不涉世界数值)
2026-08-23 22:35:17 +08:00

141 lines
4.7 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 { Kernel, makeKernel } from './kernel/Kernel'
import { World } from './runtime/World'
import { GameFacade as ApiFacade, ActName, ActPayload, FacadeEvent } from './runtime/ApiFacade'
import { GameState } from '../types/domain'
import { GameClock } from './kernel/clock'
import { RngHub } from './kernel/rng'
import { CotycPlugin } from './kernel/plugin'
import { DataPack, PACK } from '../data/registry'
import { createWorldState } from './runtime/creation'
import './runtime/demo-plugins' // side-effect:注册示例插件工厂(持久化重装用)
import { normalizeGameState } from './runtime/World'
export interface GameEngineOptions {
seed?: string
datapack?: Partial<DataPack>
noAutoCreate?: boolean
/** 建档参数(NewGame 透传;缺省林氏兜底) */
surname?: string
familyName?: string
motto?: string
difficulty?: 'easy' | 'normal' | 'hard'
}
export type EngineStatus = 'idle' | 'running' | 'gameover'
/**
* GameEngine —— 仙途家族志统一游戏引擎(0.1.14 架构核心)。
* 一个引擎对象 = 内核(Kernel: 时钟+随机+总线) + 世界(World) + 门面(ApiFacade) + 存储接口。
* UI/测试/未来工具仅需认识 GameEngine。
*/
export class GameEngine {
kernel: Kernel
readonly datapack: typeof PACK
world: World
facade: ApiFacade
private _seed: string
autosaveEvery = 12
private tickSinceSave = 0
constructor(opts: GameEngineOptions = {}) {
this._seed = opts.seed ?? RngHub.rollSeed()
this.kernel = makeKernel(this._seed)
this.datapack = PACK
if (opts.datapack) this.datapack.override(opts.datapack)
// 组装World(内核注入→时钟/随机/总线单源;0.1.35 移除 __state 泄漏口——seed 恒用 _seed
if (opts.noAutoCreate) {
this.world = new World(createWorldState({
seed: this._seed, surname: '占位', familyName: '占位', motto: '', difficulty: 'normal'
}), [], this.kernel)
} else {
const state = createWorldState({
seed: this._seed,
surname: opts.surname?.trim() || '林',
familyName: opts.familyName?.trim() || `${opts.surname?.trim() || '林'}氏`,
motto: opts.motto?.trim() || '耕读传家,术法继世',
difficulty: opts.difficulty ?? 'normal'
})
this.world = new World(state, [], this.kernel)
}
this.facade = new ApiFacade(this.world, 1)
// Kernel 总线 → World.out 桥接(attached 后同步 feed
this.world.out = this.world.out.concat([])
}
view(): GameState {
return this.world.state
}
advance(): void {
if (this.world.state.gameOver) return
if (this.world.state.pendingEvent) return // 事件待决:停下(用户/自动化应 resolvePending
this.world.advanceMonth()
}
/** 处理当前待决事件(默认选第一项);无待决则 no-op */
resolvePending(idx = 0): boolean {
const pid = this.world.state.pendingEvent
if (!pid) return false
// 直接走门面 apply 通路的引擎包装(不依赖 UI)
return this.world.applyEventChoice(pid, idx)
}
syncRng(): void {
this.world.syncRng()
}
act(name: ActName, payload: ActPayload): boolean {
return this.facade.act(name, payload)
}
query(ref: string): Record<string, unknown> {
return this.facade.query(ref)
}
subscribe(on: (e: FacadeEvent) => void): () => void {
return this.facade.subscribe(on)
}
about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } {
const a = this.facade.about()
return a
}
installPlugin(p: CotycPlugin): { ok: boolean; reason?: string } {
return this.world.installPlugin(p)
}
/** 档位快照(存档 JSON */
snapshot(): GameState {
this.world.syncRng()
return JSON.parse(JSON.stringify(this.world.state)) as GameState
}
/** 从快照恢复(可回放)——重建内核以避免时钟双注册,并回置 rng 保真 */
restore(snap: GameState): void {
normalizeGameState(snap)
// 新内核(同一 seed 派生)+ 回写存档 rng 快照 —— 回放保真
this.kernel = makeKernel(snap.seed)
this.kernel.rng.state = { ...snap.rng }
const newWorld = new World(snap, [], this.kernel)
newWorld.out = this.world.out
this.world = newWorld
this.facade = new ApiFacade(this.world, 1)
}
status(): EngineStatus {
return this.world.state.gameOver ? 'gameover' : 'running'
}
seed(): string {
return this._seed
}
}
/** 已用旧内核时钟注册的世界快照,反序列化时公用(测试/便捷入口——非兼容机器) */
export function engineFromSnapshot(state: GameState): GameEngine {
const eng = new GameEngine({ seed: state.seed, noAutoCreate: true })
eng.restore(state)
return eng
}