v0.1.9: 百年报告 & 发布之窗(报告/迁移/阵型/冬祷/哨兵)
- 百年族史报告:buildReport 纯函数(人口/声望/战力三线、死因榜、突破密度、 大比战绩、代际长度带)+ 春秋原 SVG 折线视图 + 自动措辞结语 - 存档迁移管线 v2:migrate 链(v1→v2)+ 未来版本拒载(TOO_NEW)+ 导出信封 带 appId/schemaVersion + 导入全链路迁移校验;saveState 自动写 CURRENT_SCHEMA - 战斗阵型:锋阵(+10%攻)/雁阵(御5%+溃伤-15%)/蛇阵(溃伤-30%),三处出战接入 (遣队/大比/劫掠)点将面板下拉 + 战报首行标阵 - 冬至岁祷:每年十一月三选(观星+3声望/祈福全族修为/问卜吉凶) - 发布准备:scripts/verify-release.mjs 验收哨兵(npm run verify/verify:package) + README 全面重写(架构/守护/版本沿革);版本号三处归一 - 测试 861→881(报告四例/迁移五链/阵型六则含胜率统计/冬祷四则); 金钟罩三指纹零漂移;typecheck/build 通过
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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)}` }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user