①受控面引擎能力透传通道 PluginContext.getEngine()→EngineCapabilities(api.d.ts):白名单3项particles.spawnEmitter/audio.synth/math.lerp+smoothStep(YAGNI核6件impl真调用点);铁律=封装禁裸Vector2/引擎import只活集成段host背书engineFactory不散进插件impl(保引擎可换+满足门1真接线);random/time集成段换引擎RandomGenerator/timeReal背书(契约形状不变) ②GamePackage manifest加packageUrl+immutable(additive,复用后端packageUrl镜像走DB-manifest降级,checksum复用旧字段供门4防demo假阳性) ③SDK StoragePlugin根契约上提(sdk-interface.d.ts):前端contract.ts定稿对象形态上提+含R4-P1 array排除契约语义声明 grounded工位核impl逮关键澄清:hitStop/屏震非通道项(impl.js:582-615=受控面输出+游戏层render.js:915应用闭环,插件不调引擎;屏震引擎化=render.js切相机属agent生成域)→通道收敛6件→3项,§7/§6 A2同步澄清;audio.synth留契约A2据SIZES定用否 JSON校验过+.d.ts括号平衡 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
138 lines
6.6 KiB
TypeScript
138 lines
6.6 KiB
TypeScript
/**
|
||
* 契约 #3 | WanxiangGameSDK 公共 API 类型 + SDK↔宿主 postMessage 协议
|
||
* owner:WS3 SDK 负责人 | 消费方:game-studio 宿主 / 游戏侧
|
||
*
|
||
* 硬约束(来自 engineering-conventions §3):
|
||
* - 零第三方依赖;Core(Lifecycle+EventBus+Telemetry+ErrorTrack) 压缩后 < 8KB;每 Plugin < 5KB
|
||
* - 所有对外 API 不返回 Promise(fire-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;
|
||
/** 请求/响应配对 ID(ad/pay 等需回调的消息) */
|
||
requestId?: string;
|
||
traceId: string;
|
||
payload: T;
|
||
}
|
||
|
||
/* ----- storage 消息 payload(idle 离线产出存档;HJ-MC-TPL-EXEC-002 §6.1.1,T1b-β 上提,semver 只增不改) ----- */
|
||
|
||
/** storage 写入请求 payload(游戏→宿主:保存键值) */
|
||
export interface StorageSetPayload {
|
||
/** 存档键(runtime 侧按 'idle:'+gameId+':'+versionId 约定,宿主侧白名单前缀校验) */
|
||
key: string;
|
||
/** 存档值(JSON 字符串,宿主侧落 localStorage 前校验大小上限) */
|
||
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 直接读字段。写入 payload(StorageSetPayload.value)仍是 JSON 字符串。
|
||
* **对象语义不含数组(T1b-β R4-P1)**:宿主回包端(game-studio inject.ts 已落 !Array.isArray 运行时排除)
|
||
* 与本类型层须一致——value 虽 TS 标注 Record<string,unknown>|null(array 在 TS 是 object 子型不被拒),
|
||
* 但**契约语义明令排除数组**:数组/字符串/标量一律按未命中处理(resolve null)。
|
||
* storage 三套形状(本根契约 / game-studio host/contract.ts / inject.ts 应答端)收敛校验须覆盖此「array→null」运行时语义,不止类型签名对齐。
|
||
*/
|
||
value: Record<string, unknown> | null;
|
||
}
|