import { GameState } from '../types/domain' import { normalizeGameState } from '../engine/world' export const CURRENT_SCHEMA = 2 export const APP_ID = 'cotyc' export interface MigrationError { code: 'TOO_NEW' | 'UNKNOWN_VERSION' version: number message: string } /** * 迁移链:obj.from 升到 obj.to 逐级执行(任何一步失败即中断抛出,原状态不被写入)。 * v1(≤0.1.8)→ v2(0.1.9):补 stats/finance/annals 字段并打上 schema 版本戳。 */ const MIGRATIONS: Array<{ from: number; to: number; fn: (s: GameState) => GameState }> = [ { from: 1, to: 2, fn: (s) => { normalizeGameState(s) s.schemaVersion = 2 return s } } ] export function migrateIfNeeded(state: GameState): { ok: true; state: GameState } | { ok: false; error: MigrationError } { const version = typeof state.schemaVersion === 'number' ? state.schemaVersion : 1 if (version > CURRENT_SCHEMA) { return { ok: false, error: { code: 'TOO_NEW', version, message: `存档版本 ${version} 高于当前游戏支持(${CURRENT_SCHEMA}),请升级游戏。` } } } let cur = state let v = version let guard = 0 while (v < CURRENT_SCHEMA && guard < 20) { const step = MIGRATIONS.find((m) => m.from === v) if (!step) { return { ok: false, error: { code: 'UNKNOWN_VERSION', version: v, message: `未知存档版本 ${v},无法迁移。` } } } try { cur = step.fn(cur) } catch (e) { return { ok: false, error: { code: 'UNKNOWN_VERSION', version: v, message: `迁移失败:${String(e)}` } } } v = step.to guard++ } return { ok: true, state: cur } } export function exportEnvelope(state: GameState): string { return JSON.stringify({ app: APP_ID, schemaVersion: state.schemaVersion ?? CURRENT_SCHEMA, payload: state }) } export function parseImportEnvelope(text: string): { ok: true; state: GameState } | { ok: false; error: string } { try { const raw = JSON.parse(text) as { app?: string; schemaVersion?: number; payload?: GameState; state?: GameState } if (raw.app !== undefined && raw.app !== APP_ID) return { ok: false, error: '非本作存档(app 标识不符)。' } const state = raw.payload ?? raw.state if (!state || typeof state !== 'object') return { ok: false, error: '存档内容为空或损坏。' } const migrated = migrateIfNeeded(state as GameState) if (migrated.ok) return { ok: true, state: migrated.state } return { ok: false, error: migrated.error.message } } catch (e) { return { ok: false, error: `解析失败:${String(e)}` } } }