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 全绿——金钟罩零漂(新档结构全字段,删除不涉世界数值)
This commit is contained in:
@@ -42,20 +42,20 @@ export class GameEngine {
|
||||
this.kernel = makeKernel(this._seed)
|
||||
this.datapack = PACK
|
||||
if (opts.datapack) this.datapack.override(opts.datapack)
|
||||
// 组装World(内核注入→时钟/随机/总线单源)
|
||||
// 组装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 = opts.seed ? createWorldState({
|
||||
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'
|
||||
}) : (globalThis as never as { __state?: GameState }).__state
|
||||
this.world = new World(state!, [], this.kernel)
|
||||
})
|
||||
this.world = new World(state, [], this.kernel)
|
||||
}
|
||||
this.facade = new ApiFacade(this.world, 1)
|
||||
// Kernel 总线 → World.out 桥接(attached 后同步 feed)
|
||||
@@ -132,7 +132,7 @@ export class GameEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/** 已用旧内核时钟注册的世界快照,反序列化时公用(便捷入口) */
|
||||
/** 已用旧内核时钟注册的世界快照,反序列化时公用(测试/便捷入口——非兼容机器) */
|
||||
export function engineFromSnapshot(state: GameState): GameEngine {
|
||||
const eng = new GameEngine({ seed: state.seed, noAutoCreate: true })
|
||||
eng.restore(state)
|
||||
|
||||
@@ -78,90 +78,14 @@ export function normalizeGameState(state: GameState): GameState {
|
||||
registerNpcDef(n as never)
|
||||
}
|
||||
}
|
||||
// P0-2 双保险:npcFamilies 中缺 def 的 id → 占位 def(防极端旧档僵尸)
|
||||
for (const id of Object.keys(state.npcFamilies ?? {})) {
|
||||
if (!npcById(id)) {
|
||||
const n = state.npcFamilies[id]
|
||||
registerNpcDef({
|
||||
id, name: n.name, region: n.region, style: '遗族', desc: '世族遗脉,家道中落而名犹在册。',
|
||||
leaderRealm: 'foundation', initialPower: n.power, powerGrowth: [2, 6], sells: [], buys: ['lingcao']
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!state.worldGen && state.seed) {
|
||||
const wg = generateWorld(state.seed)
|
||||
state.worldGen = {
|
||||
relations: wg.relations,
|
||||
eras: wg.eras,
|
||||
marketOffset: wg.marketOffset,
|
||||
regionFlavor: wg.regionFlavor,
|
||||
alliances: wg.alliances,
|
||||
feuds: wg.feuds,
|
||||
npcCount: wg.npcs.length,
|
||||
// P1-9 重放同步 npcs 摘要(与 creation 一致——该档自此自捕获,不再每次重放)
|
||||
npcs: wg.npcs.map((n) => ({
|
||||
id: n.id, name: n.name, region: n.region, style: n.style,
|
||||
desc: n.desc, leaderRealm: n.leaderRealm, initialPower: n.initialPower,
|
||||
powerGrowth: n.powerGrowth, sells: n.sells, buys: n.buys
|
||||
}))
|
||||
}
|
||||
}
|
||||
// 老版本存档(<0.1.1)缺少新增字段,加载时补齐,避免运行期 undefined 崩溃
|
||||
if (!state.finance) state.finance = { accum: 0 }
|
||||
if (!state.yearStats) state.yearStats = { births: 0, deaths: 0 }
|
||||
if (!state.yearlyReports) state.yearlyReports = []
|
||||
if (!state.stats) {
|
||||
state.stats = {
|
||||
repPeak: state.family?.reputation ?? 0,
|
||||
popPeak: Object.values(state.members).filter((c) => c.alive).length,
|
||||
maxRealmIdx: peakRealmIndex(state.members ?? {}),
|
||||
techniqueGrand: grandTechniqueCount(state.members ?? {}),
|
||||
tourneyHistory: [],
|
||||
feishengCount: 0
|
||||
}
|
||||
}
|
||||
if (typeof state.totalTicks !== 'number') state.totalTicks = 0
|
||||
if (typeof state.seq !== 'number') state.seq = 10
|
||||
if (!state.battles) state.battles = []
|
||||
if (!state.eventQueue) state.eventQueue = []
|
||||
if (!state.completedEvents) state.completedEvents = []
|
||||
if (!state.missions) state.missions = []
|
||||
if (!state.flags) state.flags = {}
|
||||
if (!state.npcFamilies) state.npcFamilies = {}
|
||||
if (!state.family.flag) state.family.flag = {}
|
||||
if (!state.family.inventory) state.family.inventory = {}
|
||||
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 (typeof state.worldSim.warFatigue !== 'number') state.worldSim.warFatigue = 0
|
||||
if (!state.worldSim.shortage) state.worldSim.shortage = {}
|
||||
if (!state.worldSim.shortageWarned) state.worldSim.shortageWarned = {}
|
||||
if (!state.worldSim.wonders) state.worldSim.wonders = {}
|
||||
if (typeof state.worldSim.tideTicks !== 'number') state.worldSim.tideTicks = 0
|
||||
if (typeof state.worldSim.npcSuccessions !== 'number') state.worldSim.npcSuccessions = 0
|
||||
if (typeof state.worldSim.calamityLeft !== 'number') state.worldSim.calamityLeft = 0
|
||||
// 0.1.35 收敛:初始字段职责归 initSim(WorldSim.ts),此处仅防脏(异常落盘/测试注入)
|
||||
// 防护一:era 非法值兜底(语义防护——主产物 initSim 造)
|
||||
if (state.worldSim.era !== 'shengshi' && state.worldSim.era !== 'pingshi' && state.worldSim.era !== 'luanshi' && state.worldSim.era !== 'mofa') {
|
||||
state.worldSim.era = 'pingshi'
|
||||
state.worldSim.eraStartYear = 1
|
||||
} else if (typeof state.worldSim.eraStartYear !== 'number') {
|
||||
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') {
|
||||
(dyn as { prosperity: number }).prosperity = 50
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.family.headId && !state.members[state.family.headId]) {
|
||||
const firstAlive = Object.values(state.members).find((c) => c.alive)
|
||||
|
||||
@@ -23,7 +23,6 @@ export function createWorldState(opts: NewGameOptions): GameState {
|
||||
|
||||
const worldGen = generateWorld(opts.seed)
|
||||
const state: GameState = {
|
||||
schemaVersion: 1,
|
||||
seed: opts.seed,
|
||||
worldGen: {
|
||||
relations: worldGen.relations,
|
||||
|
||||
@@ -95,6 +95,9 @@ export function newCharacter(
|
||||
state: 'idle',
|
||||
health: 100,
|
||||
alive: true,
|
||||
techniqueRank: 0,
|
||||
techniqueProgress: 0,
|
||||
tribBoost: 0,
|
||||
isHead: opts.isHead,
|
||||
isFounder: opts.isFounder
|
||||
}
|
||||
|
||||
@@ -81,7 +81,11 @@ export function makeWorldSimState(): WorldSimState {
|
||||
era: 'pingshi',
|
||||
eraStartYear: 1,
|
||||
overfluxYear: -99,
|
||||
worldAnnals: []
|
||||
worldAnnals: [],
|
||||
warFatigue: 0,
|
||||
shortage: {},
|
||||
shortageWarned: {},
|
||||
wonders: {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user