【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 全绿——金钟罩零漂(新档结构全字段,删除不涉世界数值)
141 lines
4.7 KiB
TypeScript
141 lines
4.7 KiB
TypeScript
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
|
||
}
|