games-development-ai/contracts/sdk-interface.d.ts
lili 2ce0ed3a9d feat(save-cap): W-SAVE-CAP 存档面定容+schemaVersion——单槽版本信封,北极星三款共享基建
契约先行(sdk-interface.d.ts+镜像 additive):白名单新增恰等键族 save:{gameId}:{versionId}(≤65536 码元,
写入必须 {schemaVersion≥1, data} 信封,宿主闸 fail-closed);idle: 族 4096 逐字不动;弃多 key 分片。
宿主闸抽纯函数模块 storageGate.ts(四闸合一可 node 直测);save-progress 插件 v1.0.0→v1.1.0 additive:
setVersioned/getVersioned 逐级迁移/缺级兜底/降档读新档不毁档/无版本旧档按 0 版进链+探针两计数。
证据:插件单测 18/18、game-runtime 31/31、宿主闸负向演示脚本入仓真跑全断言过、vue-tsc+vite build 绿。
三书 M 门影子存档降级口径解除条件就位(卡牌 M2/割草 M4/夜市 M5 切换)。设计页 docs/plans/2026-07-06-002。
2026-07-06 09:24:44 -07:00

153 lines
7.8 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.

/**
* 契约 #3 | WanxiangGameSDK 公共 API 类型 + SDK↔宿主 postMessage 协议
* ownerWS3 SDK 负责人 | 消费方game-studio 宿主 / 游戏侧
*
* 硬约束(来自 engineering-conventions §3
* - 零第三方依赖Core(Lifecycle+EventBus+Telemetry+ErrorTrack) 压缩后 < 8KB每 Plugin < 5KB
* - 所有对外 API 不返回 Promisefire-and-forget不占游戏主线程异常绝不向游戏抛
* - 新版本只增字段/方法、不删不改已有签名semver
*/
/** SDK 初始化参数(宿主注入) */
export interface WanxiangGameSDKInitOptions {
gameId: string;
versionId: string;
/** 贯穿 生成→编译→加载→运行→上报 的 trace_id */
traceId: string;
/** 是否开发模式(加载 Plugin.Debug仅 dev */
debug?: boolean;
}
/** 游戏生命周期事件名 */
export type LifecycleEvent =
| 'sdk_ready' // SDK 初始化完成
| 'game_loaded' // 游戏资源加载完成
| 'game_start' // 开始游玩
| 'game_end' // 本局结束
| 'game_error'; // 运行错误
/** SDK 根对象(挂在游戏侧 window.WanxiangGameSDK */
export interface WanxiangGameSDK {
/** 初始化(宿主调用一次) */
init(options: WanxiangGameSDKInitOptions): void;
/** 生命周期事件订阅EventBus */
on(event: LifecycleEvent, handler: (payload?: Record<string, unknown>) => void): void;
off(event: LifecycleEvent, handler: (payload?: Record<string, unknown>) => void): void;
/** 遥测上报fire-and-forget内部 10条/5s 批量 flush + sendBeacon 兜底) */
track(event: string, props?: Record<string, unknown>): void;
/** 错误上报(去重,不中断游戏) */
reportError(error: { message: string; stack?: string }): void;
/** 插件(首次调用懒加载) */
ad: AdPlugin;
pay: PayPlugin;
/** 存储插件首次调用懒加载idle 离线存档T1b-β 由前端 host/contract.ts 定稿形态上提至根契约) */
storage: StoragePlugin;
}
/** 广告插件MVP 桩 → 真实穿山甲/优量汇切换;每调用 5s 超时降级) */
export interface AdPlugin {
/** 激励视频;完成回调 rewarded=true */
showRewarded(slotId: string, cb: (result: { rewarded: boolean }) => void): void;
/** 插屏广告 */
showInterstitial(slotId: string, cb?: (result: { shown: boolean }) => void): void;
}
/** 支付插件MVP 桩 → 真实微信支付切换) */
export interface PayPlugin {
/** 发起积分充值;宿主侧拉起支付 */
pay(order: { orderId: string; amount: number }, cb: (result: { paid: boolean }) => void): void;
}
/** 存储插件idle 离线产出存档HJ-MC-TPL-EXEC-002 §6.1.1 定稿对象形态T1b-β 上提至根契约semver 只增不改) */
export interface StoragePlugin {
/**
* 写入存档fire-and-forget不返回 Promise。宿主侧白名单前缀校验 + 大小上限校验后落 localStorage。
* @param key 存档键runtime 侧按 'idle:'+gameId+':'+versionId 约定)。
* @param value 存档值JSON 字符串;宿主侧落库前校验大小)。
*/
save(key: string, value: string): void;
/**
* 读取存档宿主受信边界已解析为对象后回调runtime 侧零 JSON.parse守 runtime §5.5 零解析红线)。
* @param key 存档键。
* @param cb 回调;命中=已解析对象,未命中/校验失败/**非纯对象(含数组)**=null见 StorageResultPayload.value 的 array 排除)。
*/
load(key: string, cb: (value: Record<string, unknown> | null) => void): void;
}
/* ============================================================================
* SDK ↔ 宿主 postMessage 协议
* 游戏在 iframe 沙箱内,通过 postMessage 与 game-studio 宿主通信。
* 宿主侧校验 origin 白名单 + 消息 schema双校验见 security-and-reliability §1.1)。
* ========================================================================== */
/** 消息方向 */
export type MessageDirection = 'game_to_host' | 'host_to_game';
/** 消息类型全集 */
export type PostMessageType =
| 'init' // 宿主→游戏:初始化参数
| 'lifecycle' // 游戏→宿主:生命周期事件
| 'telemetry' // 游戏→宿主:遥测事件转发
| 'error' // 游戏→宿主:错误上报
| 'ad' // 双向:广告请求/结果(广告在 iframe 外宿主侧渲染)
| 'pay' // 双向:支付请求/结果
| 'social' // 双向:分享等
| 'storage'; // 双向:键值存储
/** postMessage 信封 */
export interface PostMessageEnvelope<T = unknown> {
/** 固定标识,宿主据此过滤非本协议消息 */
channel: 'wanxiang-game-sdk';
type: PostMessageType;
direction: MessageDirection;
/** 请求/响应配对 IDad/pay 等需回调的消息) */
requestId?: string;
traceId: string;
payload: T;
}
/* ----- storage 消息 payloadidle 离线产出存档HJ-MC-TPL-EXEC-002 §6.1.1T1b-β 上提semver 只增不改) ----- */
/**
* 存档键族与分层上限W-SAVE-CAP 2026-07-06 增补additive。宿主白名单恰等匹配、每族一槽
* - `idle:{gameId}:{versionId}`:轻量离线产出存档。上限 4096 码元value.length无信封要求——存量语义逐字不变。
* - `save:{gameId}:{versionId}`:复杂游戏主存档(北极星三款起用)。上限 65536 码元,写入值**必须**是
* SaveEnvelope 版本信封的 JSON 序列化,宿主闸 fail-closed非信封/版本非法一律丢弃)。
* 两族之外的 key 一律丢弃。上限口径 = UTF-16 码元数(与宿主 localStorage 存储口径一致);
* 游戏侧 save-progress 插件的 maxValueBytes 是 UTF-8 字节口径的更紧缺省内闸32KB可上调
*/
export interface SaveEnvelope {
/** 存档 schema 版本(整数 ≥1无版本旧档由读取侧按 0 处理进迁移链——见 save-progress getVersioned */
schemaVersion: number;
/** 存档正文(纯对象;数组/标量非法——与 StorageResultPayload 的「对象语义不含数组」一致) */
data: Record<string, unknown>;
}
/** storage 写入请求 payload游戏→宿主保存键值 */
export interface StorageSetPayload {
/** 存档键(键族与上限见 SaveEnvelope 注释:'idle:'|'save:' + gameId + ':' + versionId宿主侧白名单恰等校验 */
key: string;
/** 存档值JSON 字符串,宿主侧落 localStorage 前按键族校验大小上限save: 族还须为 SaveEnvelope 信封) */
value: string;
}
/** storage 读取请求 payload游戏→宿主按 key 取值;带 requestId宿主读分支按同 requestId 回包) */
export interface StorageGetPayload {
key: string;
}
/** storage 读取结果 payload宿主→游戏回包 value无则 null */
export interface StorageResultPayload {
key: string;
/**
* 命中的存档值 = 宿主受信边界已解析好的对象(非原始 JSON 字符串);未命中/校验失败为 null。
* 解析在宿主侧做runtime 侧零 JSON.parse守 runtime §5.5「runtime 内不引可抛解析路径」),
* loadState resolve 出对象后 runtime 直接读字段。写入 payloadStorageSetPayload.value仍是 JSON 字符串。
* **对象语义不含数组T1b-β R4-P1**宿主回包端game-studio inject.ts 已落 !Array.isArray 运行时排除)
* 与本类型层须一致——value 虽 TS 标注 Record<string,unknown>|nullarray 在 TS 是 object 子型不被拒),
* 但**契约语义明令排除数组**:数组/字符串/标量一律按未命中处理resolve null
* storage 三套形状(本根契约 / game-studio host/contract.ts / inject.ts 应答端收敛校验须覆盖此「array→null」运行时语义不止类型签名对齐。
*/
value: Record<string, unknown> | null;
}