GameBuilder iOS Manifest 线上接口与 iOS 对接说明
previewUrl、runtimeUrl、htmlUrl 或 GameBuilder H5 URL。
iOS 只允许读取 Firebase 保存的 playableDelivery.ios.manifest。
1. 线上访问地址
| 用途 | 地址 |
|---|---|
| iOS 对接文档 | https://game.vvicat.com/docs/game-builder-ios-manifest-integration.html |
| 完整实现方案 Markdown | https://game.vvicat.com/docs/game-builder-ios-manifest-api-implementation-plan.md |
| GameBuilder API | https://game.vvicat.com/api/game-builder |
| 健康检查 | https://game.vvicat.com/api/health |
2. 平台下发规则
| 平台 | Firebase 保存内容 | 客户端运行方式 |
|---|---|---|
| Android | playableDelivery.android.runtimeUrl |
继续使用现有 H5/WebView runtime。 |
| iOS | playableDelivery.ios.manifest |
App 内置 native playable runtime 渲染 ios_manifest_v1。 |
3. Firebase 调 GameBuilder:Create iOS Manifest
POST https://game.vvicat.com/api/game-builder?action=create_playable_manifest&access_token=YOUR_TOKEN
Content-Type: application/json
推荐 Firebase 先调用 get_ios_capability_catalog 获取当前完整能力表,再把其中的 capabilities 原样传给 create/remix/migration 接口。
下面请求体为最小点击类示例;复杂游戏应使用完整 catalog,避免误触发 requires_capabilities。
GET https://game.vvicat.com/api/game-builder?action=get_ios_capability_catalog&access_token=YOUR_TOKEN
{
"action": "create_playable_manifest",
"requestId": "ios_project_001",
"actorId": "firebase_uid_001",
"idempotencyKey": "ios_project_001",
"runtimeContract": "ios_manifest_v1",
"sourceContract": "playable_source_v1",
"platform": "ios",
"appVersion": "1.0.0",
"engineVersion": "1.0.0",
"prompt": "做一个月亮门解谜小游戏",
"genre": "Mini Puzzle",
"playableType": "Mini Puzzle",
"locale": "zh-CN",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"maxMechanicNodes": 48,
"maxRuleEdges": 96,
"maxAssets": 8,
"maxScenes": 9,
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
},
"mediaAssets": []
}
当前 Native Manifest Ready 返回
当前线上 iOS 不下发 H5,返回 ios_manifest_v1 纯 JSON。已支持 tapTarget、tileBoard、gridCombat、flapRunner、arcadePhysics、aimShooter、sportsFlick、simQueue、dialogChoice、paintCanvas、musicRhythm、appPanel、combo、dragObject、timer、score,以及声明式 motionPath、feedback、controls、targetRole、shape、lane、boardKind、boardPieces、legalMoves。棋类/井字棋会以 tileBoard + ticTacToe/chessLite manifest 运行;tank/tower-defense/grid battle 类会以 gridCombat + grid_fire 原生网格战斗运行;clicker/stack/增量点击类会以 combo + combo_chain 原生连击运行;catch/fish/pop 类会以 dragObject + drop zone 原生拖拽收集运行;quiz/RPS/dice/democracy/decision/joke/prank/laugh/annoy 类会以 dialogChoice + branch_choice 原生按钮选择运行;feed/serve/cleanup/lawn/garden/dollhouse/sushi/dumpling/forge/rags/slay/follower/ranch/collector 类服务经营游戏会以 simQueue + choice_queue 原生任务队列运行;radio/music/rhythm 类会以 musicRhythm + ordered_tap 原生序列运行;dodge/racer/ball/jump/break-wall/ricochet/timer/countdown/physics/capybara/wakeup/wrestling/brawl/launch 类会以 arcadePhysics + timed_tap 原生物理反应运行。当前 186 个游戏已验证为 186 ready、0 unsupported;超出 capability catalog 的玩法仍返回 requires_capabilities,不能回退 Android H5。
{
"code": 0,
"action": "create_playable_manifest",
"data": {
"taskId": "GBM_1770000000000_ABC123",
"requestNo": "GBM_1770000000000_ABC123",
"status": "ready",
"taskStatus": "ready",
"buildStatus": "ready",
"message": "Playable manifest ready",
"artifact": {
"runtimeContract": "dual_android_h5_ios_manifest_v1",
"externalGameId": "gbm-build-a-moon-gate-abc123",
"title": "Build A Moon Gate",
"concept": "做一个月亮门解谜小游戏",
"playableType": "Mini Puzzle",
"androidRuntimeUrl": "https://game.vvicat.com/games/gbm-build-a-moon-gate-abc123/#/",
"previewUrl": "https://game.vvicat.com/games/gbm-build-a-moon-gate-abc123/#/",
"iosManifest": {
"schemaVersion": "ios_manifest_v1",
"sourceHash": "sha256:...",
"engineId": "aivvi_native_playable",
"engineVersionMin": "1.0.0",
"format": "Mini Puzzle",
"title": "Build A Moon Gate",
"mechanicGraph": {
"nodes": [
{"id": "timer", "type": "timer", "durationSeconds": 35},
{"id": "score", "type": "score", "initial": 0},
{"id": "target_1", "type": "tapTarget", "x": 0.24, "y": 0.34, "radius": 0.085, "scoreDelta": 20}
],
"edges": [
{"from": "target_1", "event": "hit", "to": "score", "effect": {"type": "add", "value": 20}}
]
},
"rules": {
"win": {"type": "scoreAtLeast", "state": "score", "value": 100},
"fail": {"type": "timerExpired", "state": "timeRemaining"}
},
"assets": [],
"telemetry": {"events": ["ready", "start", "input", "hit", "complete", "fail"]}
},
"sourceSummary": {
"sourceId": "src_GBM_1770000000000_ABC123",
"sourceHash": "sha256:...",
"schemaVersion": "playable_source_v1"
},
"verification": {
"status": "passed",
"stateParity": "passed",
"eventParity": "passed",
"outcomeParity": "passed",
"visualParity": "passed",
"visualMaxDiffRatio": 0,
"visualKeyframeCount": 3
}
}
}
}
4. Firebase 查询任务
GET https://game.vvicat.com/api/game-builder?action=get_task_manifest&taskId=GBM_1770000000000_ABC123&access_token=YOUR_TOKEN
5. Firebase 拉取 iOS 预置 Feed
GET https://game.vvicat.com/api/game-builder?action=list_games_manifest&page=1&pageSize=50&access_token=YOUR_TOKEN
6. 旧 H5 游戏逐步迁移
旧 H5 游戏不能直接在 iOS 上运行。迁移必须先扫描,再按单个 gameKey 导入。
第一阶段会自动导入已经包含 playable-source.json 和 ios-manifest.json 的 manifest-ready runtime。
第二阶段支持保守的声明式旧 H5:页面中带 data-ios-tap-target、data-x、data-y、data-score 的元素可恢复为 tapTarget manifest。
第三阶段支持 Vite/JS bundle 内的静态迁移 metadata 注释块;GameBuilder 只读取 JSON 注释,不执行 JS。
普通旧 H5 会返回 requires_capabilities,等待 Legacy H5 Importer 后续处理;可识别的 2048、chess、noughts-and-crosses、clicker、stack、catch、fish、pop、quiz、rock-paper-scissors、dice、democracy、joke、laugh、annoy、feed、serve、cleanup、lawn、garden、sushi、dumpling、forge、rags、follower、ranch、collector、radio、dodge、racer、jump、timer、countdown、physics、capybara、wakeup、wrestling、brawl 等类型会通过 source-first rebuild 生成对应原生 manifest。
扫描本地游戏
GET https://game.vvicat.com/api/game-builder?action=scan_legacy_manifests&page=1&pageSize=100&access_token=YOUR_TOKEN
{
"code": 0,
"action": "scan_legacy_manifests",
"data": {
"list": [
{
"gameKey": "gbm-build-a-moon-gate-abc123",
"migrationStatus": "ready",
"reason": "Manifest-ready runtime can be imported.",
"hasAndroidRuntime": true,
"hasPlayableSource": true,
"hasIosManifest": true,
"sourceHash": "sha256:..."
},
{
"gameKey": "old-canvas-game",
"migrationStatus": "unsupported",
"reason": "Missing playable-source.json or ios-manifest.json; legacy H5 importer is required.",
"hasAndroidRuntime": true,
"hasPlayableSource": false,
"hasIosManifest": false
}
],
"total": 2,
"page": 1,
"pageSize": 100
}
}
导入单个 manifest-ready 游戏
POST https://game.vvicat.com/api/game-builder?action=migrate_legacy_manifest&access_token=YOUR_TOKEN
Content-Type: application/json
{
"action": "migrate_legacy_manifest",
"gameKey": "gbm-build-a-moon-gate-abc123",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
声明式旧 H5 可迁移格式
这类 H5 不执行 JS 反编译,只读取 HTML 中的声明式属性。
<meta name="aivvi-playable-type" content="Mini Puzzle">
<meta name="aivvi-duration-seconds" content="20">
<button data-ios-tap-target data-x="0.25" data-y="0.35" data-score="25">A</button>
<button data-ios-tap-target data-x="0.75" data-y="0.35" data-score="25">B</button>
<button data-ios-tap-target data-x="0.50" data-y="0.70" data-score="50">C</button>
Vite/JS bundle 静态 metadata 可迁移格式
适用于已经打包成 assets/index-*.js 的旧游戏。注意:迁移器只读取注释块 JSON,不执行 bundle。
/* AIVVI_PLAYABLE_SOURCE_V1
{
"title": "Bundle Metadata Migration",
"playableType": "Mini Puzzle",
"locale": "en-US",
"durationSeconds": 18,
"tapTargets": [
{"x": 0.2, "y": 0.3, "scoreDelta": 40, "label": "L"},
{"x": 0.8, "y": 0.7, "scoreDelta": 60, "label": "R"}
]
}
AIVVI_PLAYABLE_SOURCE_V1_END */
普通旧 H5 的返回
{
"code": 0,
"action": "migrate_legacy_manifest",
"data": {
"status": "requires_capabilities",
"error": {
"code": "legacy_h5_import_required",
"message": "This existing H5 game does not contain playable_source_v1 and ios_manifest_v1 artifacts."
},
"requiresCapabilities": [
{
"id": "legacy_h5_importer",
"kind": "compiler",
"reason": "Legacy H5 cannot be proven safe for iOS manifest delivery yet."
}
]
}
}
批量回填
建议先看全量迁移总览,再生成单页迁移计划,之后 dry-run,最后按页执行 apply。总览和计划接口都是只读的,不会写入 playable-source.json 或 ios-manifest.json。
POST https://game.vvicat.com/api/game-builder?action=get_legacy_manifest_migration_summary&access_token=YOUR_TOKEN
Content-Type: application/json
{
"action": "get_legacy_manifest_migration_summary",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "get_legacy_manifest_migration_summary",
"data": {
"total": 128,
"ready": 3,
"importable": 4,
"unsupported": 121,
"nextActions": {
"rebuildManifestRegistry": 3,
"backfillLegacyManifests": 4,
"manualSourceRebuild": 121
},
"sampleImportable": [
{"gameKey": "simple-h5-game", "status": "importable", "nextAction": "backfill_legacy_manifests"}
]
}
}
POST https://game.vvicat.com/api/game-builder?action=plan_legacy_manifest_migration&access_token=YOUR_TOKEN
Content-Type: application/json
{
"action": "plan_legacy_manifest_migration",
"page": 1,
"pageSize": 25,
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "plan_legacy_manifest_migration",
"data": {
"ready": 3,
"importable": 4,
"unsupported": 18,
"list": [
{"gameKey": "manifest-ready-game", "status": "ready", "nextAction": "rebuild_manifest_registry"},
{"gameKey": "simple-h5-game", "status": "importable", "mode": "legacy_bundle_metadata_import", "nextAction": "backfill_legacy_manifests"},
{"gameKey": "complex-canvas-game", "status": "requires_capabilities", "nextAction": "manual_source_rebuild"}
]
}
}
ready 表示磁盘已有合法 sidecar,可重建 registry;
importable 表示可由当前 importer 自动转成 manifest;
requires_capabilities 表示不能安全自动迁移,需要先重做 source 或扩展 native runtime capability。
列出 source-first 重建候选
对 requires_capabilities 且 nextAction=manual_source_rebuild 的复杂旧 H5,可用该接口分页生成重建队列。
该接口只读,不会生成 manifest;返回的 iosDelivery 仍是 unsupported,供 Firebase/iOS 客户端 fail closed。
POST https://game.vvicat.com/api/game-builder?action=list_manual_source_rebuild_candidates&access_token=YOUR_TOKEN
Content-Type: application/json
{
"page": 1,
"pageSize": 20,
"sortBy": "complexity",
"complexityTier": "medium",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "list_manual_source_rebuild_candidates",
"data": {
"total": 186,
"list": [
{
"gameKey": "complex-canvas-game",
"title": "Complex Canvas Game",
"previewUrl": "https://game.vvicat.com/games/complex-canvas-game/#/",
"nextAction": "manual_source_rebuild",
"complexity": {
"tier": "medium",
"score": 6,
"htmlBytes": 20480,
"scriptTags": 2,
"canvasTags": 1,
"localAssetReferences": 3
},
"iosDelivery": {
"type": "unsupported",
"reason": "requires_capabilities"
}
}
]
}
}
complexity 是静态分析结果,不执行旧 JS。建议优先迁移 tier=low 或 tier=medium 且
localAssetReferences 较少的游戏。
可传 sortBy=complexity 按 score 升序返回,也可传 complexityTier=low|medium|high 过滤。
查询单个游戏迁移状态
逐个迁移时,先用该接口确认当前 gameKey 的 iOS delivery、审计记录和下一步动作。
POST https://game.vvicat.com/api/game-builder?action=get_game_manifest_migration_status&access_token=YOUR_TOKEN
Content-Type: application/json
{
"gameKey": "complex-canvas-game",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "get_game_manifest_migration_status",
"data": {
"gameKey": "complex-canvas-game",
"status": "requires_capabilities",
"delivery": {
"ios": {
"type": "unsupported",
"reason": "requires_capabilities"
}
},
"nextAction": "manual_source_rebuild",
"canPrepareDraft": true,
"canApply": false,
"canRollback": false
}
}
批量准备 source-first 重建草稿
可按复杂度排序/过滤,一次生成一批 create_playable_manifest 草稿供人工审核。
该接口只读,不写 playable-source.json、ios-manifest.json 或 registry。
POST https://game.vvicat.com/api/game-builder?action=prepare_manual_source_rebuild_batch&access_token=YOUR_TOKEN
Content-Type: application/json
{
"page": 1,
"pageSize": 20,
"limit": 5,
"sortBy": "complexity",
"complexityTier": "medium",
"actorId": "firebase_uid_rebuilder",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "prepare_manual_source_rebuild_batch",
"data": {
"totalCandidates": 53,
"selected": 5,
"drafts": [
{
"sourceGameKey": "2048-puzzle-game",
"request": {
"action": "create_playable_manifest",
"runtimeContract": "ios_manifest_v1",
"sourceContract": "playable_source_v1"
},
"complexity": {"tier": "medium", "score": 4}
}
]
}
}
生成迁移批次报告
用于每批迁移审批。接口会读取同一批手工重建候选,生成草稿摘要,并导出/校验这批游戏当前的
Firebase delivery 状态。该接口只读,不写 playable-source.json、
ios-manifest.json、registry 或迁移报告。
POST https://game.vvicat.com/api/game-builder?action=get_manifest_migration_batch_report&access_token=YOUR_TOKEN
Content-Type: application/json
{
"page": 1,
"pageSize": 20,
"limit": 5,
"sortBy": "complexity",
"complexityTier": "medium",
"actorId": "firebase_uid_rebuilder",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "get_manifest_migration_batch_report",
"data": {
"schemaVersion": "manifest_migration_batch_report_v1",
"totalCandidates": 53,
"selected": 5,
"draftsReady": 5,
"deliveryValidation": {
"status": "passed",
"checked": 5,
"failed": 0,
"unsupported": 5
},
"items": [
{
"gameKey": "2048-puzzle-game",
"request": {"action": "create_playable_manifest"},
"delivery": {"status": "passed", "deliveryType": "unsupported"},
"nextAction": "review_then_apply_manual_source_rebuild"
}
]
}
}
准备 source-first 重建草稿
对单个候选游戏生成 create_playable_manifest 请求草稿。草稿只引用旧游戏标题和预览地址供人工参考,
不复制旧 HTML/JS/CSS/wasm 内容。审核后可把 data.request 提交给 create_playable_manifest。
POST https://game.vvicat.com/api/game-builder?action=prepare_manual_source_rebuild_draft&access_token=YOUR_TOKEN
Content-Type: application/json
{
"gameKey": "complex-canvas-game",
"actorId": "firebase_uid_rebuilder",
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "prepare_manual_source_rebuild_draft",
"data": {
"sourceGameKey": "complex-canvas-game",
"legacy": {
"title": "Complex Canvas Game",
"previewUrl": "https://game.vvicat.com/games/complex-canvas-game/#/"
},
"request": {
"action": "create_playable_manifest",
"runtimeContract": "ios_manifest_v1",
"sourceContract": "playable_source_v1",
"platform": "ios",
"prompt": "Rebuild the legacy game ... as a source-first native playable manifest."
},
"forbiddenContent": ["html", "javascript", "css", "wasm", "script_expression"]
}
}
应用 source-first 重建草稿
审核草稿后,可调用 apply 接口为旧 gameKey 写入 playable-source.json、ios-manifest.json
和 ios-manifest-migration.json,
并写入 manifest registry 与迁移报告。该接口不会覆盖旧 index.html,Android 仍继续旧 H5;
iOS 使用新的 source-first manifest。旧 Android H5 与新 iOS manifest 不作为视觉一致性证明,只保证新 source 与 iOS manifest 的 replay 校验通过。
生产写入必须显式传 confirmApply: true,否则接口会拒绝请求。
POST https://game.vvicat.com/api/game-builder?action=apply_manual_source_rebuild_draft&access_token=YOUR_TOKEN
Content-Type: application/json
{
"gameKey": "complex-canvas-game",
"actorId": "firebase_uid_rebuilder",
"confirmApply": true,
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
{
"code": 0,
"action": "apply_manual_source_rebuild_draft",
"data": {
"status": "ready",
"artifact": {
"externalGameId": "complex-canvas-game",
"androidRuntimeUrl": "https://game.vvicat.com/games/complex-canvas-game/#/",
"iosManifest": {"schemaVersion": "ios_manifest_v1"},
"verification": {"status": "passed"}
},
"migration": {
"mode": "manual_source_rebuild",
"manualReviewRequired": true
}
}
}
ios-manifest-migration.json 用于审计,包含 schemaVersion、mode、
createdBy、confirmApply、legacyAndroidRuntimePreserved、
androidRuntimeUrl 和 iosManifestSourceHash。
批量应用 source-first 重建
对已经人工审核过的一批旧游戏,可调用批量 apply。每个 items[] 项仍必须显式传
confirmApply: true,否则该项会按失败返回且不会写入 sidecar。接口按项处理,单项失败不会阻断其他项。
顶层 confirmApply 不会代替逐项确认。
传 dryRun: true 时会执行同样的 source 编译、manifest sanitizer 和 replay 校验,但不写任何 sidecar、registry 或迁移报告。
正式写入时,每个 item 还必须带上 dry-run 返回的 sourceHash 作为 approvedSourceHash;
如果 hash 不一致,接口会拒绝该项并且不写入。
POST https://game.vvicat.com/api/game-builder?action=apply_manual_source_rebuild_batch&access_token=YOUR_TOKEN
Content-Type: application/json
{
"actorId": "firebase_uid_rebuilder",
"dryRun": false,
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
},
"items": [
{
"gameKey": "complex-canvas-game",
"confirmApply": true,
"approvedSourceHash": "sha256:..."
},
{
"gameKey": "needs-more-review"
}
]
}
预演模式响应:
{
"code": 0,
"action": "apply_manual_source_rebuild_batch",
"data": {
"dryRun": true,
"total": 1,
"applied": 0,
"readyToApply": 1,
"failed": 0,
"results": [
{
"gameKey": "complex-canvas-game",
"status": "ready_to_apply",
"sourceHash": "sha256:...",
"verification": {"status": "passed"},
"artifact": {
"externalGameId": "complex-canvas-game",
"iosManifestSchemaVersion": "ios_manifest_v1"
}
}
]
}
}
{
"code": 0,
"action": "apply_manual_source_rebuild_batch",
"data": {
"total": 2,
"applied": 1,
"failed": 1,
"results": [
{
"gameKey": "complex-canvas-game",
"status": "ready",
"task": {
"artifact": {
"externalGameId": "complex-canvas-game",
"iosManifest": {"schemaVersion": "ios_manifest_v1"}
}
}
},
{
"gameKey": "needs-more-review",
"status": "failed",
"error": {
"code": "invalid_request",
"field": "confirmApply"
}
},
{
"gameKey": "hash-mismatch-game",
"status": "failed",
"error": {
"code": "validation_failed",
"field": "approvedSourceHash"
}
}
]
}
}
查询已应用迁移审计
POST https://game.vvicat.com/api/game-builder?action=list_applied_manifest_migrations&access_token=YOUR_TOKEN
Content-Type: application/json
{
"page": 1,
"pageSize": 50
}
{
"code": 0,
"action": "list_applied_manifest_migrations",
"data": {
"total": 1,
"list": [
{
"gameKey": "complex-canvas-game",
"schemaVersion": "ios_manifest_migration_v1",
"mode": "manual_source_rebuild",
"createdBy": "firebase_uid_rebuilder",
"legacyAndroidRuntimePreserved": true,
"iosManifestSourceHash": "sha256:..."
}
]
}
}
回滚 source-first 重建
如果某个已应用的 iOS manifest 需要撤回,可调用 rollback。该接口只删除
playable-source.json、ios-manifest.json、ios-manifest-migration.json、
manifest registry 和迁移报告中的对应记录,不删除旧 index.html。
生产回滚必须显式传 confirmRollback: true。
POST https://game.vvicat.com/api/game-builder?action=rollback_manual_source_rebuild&access_token=YOUR_TOKEN
Content-Type: application/json
{
"gameKey": "complex-canvas-game",
"actorId": "firebase_uid_rebuilder",
"confirmRollback": true
}
{
"code": 0,
"action": "rollback_manual_source_rebuild",
"data": {
"status": "rolled_back",
"gameKey": "complex-canvas-game",
"legacyAndroidRuntimePreserved": true,
"removed": {
"playableSource": true,
"iosManifest": true,
"migrationAudit": true
}
}
}
POST https://game.vvicat.com/api/game-builder?action=backfill_legacy_manifests&access_token=YOUR_TOKEN
Content-Type: application/json
{
"action": "backfill_legacy_manifests",
"dryRun": true,
"page": 1,
"pageSize": 25,
"capabilities": {
"mechanics": ["tapTarget", "timer", "score"],
"renderers": ["glowCircle", "textLabel"],
"events": ["ready", "start", "input", "hit", "complete", "fail"],
"rules": ["scoreAtLeast", "timerExpired"],
"assetMimeTypes": ["image/png", "image/jpeg", "image/webp"]
}
}
apply 成功的 ready 结果会写入 build/game-builder-manifest-registry.json。
因此 PM2/Node 重启后,list_games_manifest 仍可返回已迁移的 iOS manifest-ready 游戏。
所有 apply 结果,包括 requires_capabilities,会写入 build/game-builder-migration-report.json 便于跟踪迁移进度。
重建 manifest registry
如果 build/game-builder-manifest-registry.json 丢失或部署后需要从磁盘恢复,可调用该接口扫描
build/games/*/playable-source.json 与 ios-manifest.json sidecar。
只有 sourceHash 匹配、通过 sanitizer 和 replay 校验的游戏会恢复为 ready;普通复杂 H5 会返回 skipped,不会作为 iOS 可运行游戏下发。
POST https://game.vvicat.com/api/game-builder?action=rebuild_manifest_registry&access_token=YOUR_TOKEN
Content-Type: application/json
{
"action": "rebuild_manifest_registry",
"page": 1,
"pageSize": 500
}
{
"code": 0,
"action": "rebuild_manifest_registry",
"data": {
"total": 128,
"rebuilt": 42,
"skipped": 86,
"failed": 0,
"list": [
{"gameKey": "simple-game", "status": "ready", "sourceHash": "sha256:..."},
{"gameKey": "complex-h5", "status": "skipped", "reason": "Missing playable-source.json or ios-manifest.json; legacy H5 importer is required."}
]
}
}
查询迁移报告
GET https://game.vvicat.com/api/game-builder?action=get_legacy_migration_report&page=1&pageSize=100&access_token=YOUR_TOKEN
{
"code": 0,
"action": "get_legacy_migration_report",
"data": {
"total": 2,
"ready": 1,
"requiresCapabilities": 1,
"failed": 0,
"list": [
{"gameKey": "simple-game", "status": "ready", "mode": "legacy_bundle_metadata_import", "sourceHash": "sha256:..."},
{"gameKey": "complex-canvas-game", "status": "requires_capabilities", "error": "legacy_h5_import_required", "reason": "Legacy H5 cannot be proven safe for iOS manifest delivery yet."}
]
}
}
导出 Firebase 写库包
Firebase 可用该接口批量拉取 Firestore-ready 文档。
ready 项包含 playableDelivery.ios.manifest;
unsupported 项可来自迁移报告,也可来自本地旧 H5 扫描,只包含不支持原因,不含 H5 fallback。
可传 gameKeys 精确导出某一批游戏。
GET https://game.vvicat.com/api/game-builder?action=export_manifest_delivery&page=1&pageSize=100&includeUnsupported=true&access_token=YOUR_TOKEN
{
"code": 0,
"action": "export_manifest_delivery",
"data": {
"ready": 1,
"unsupported": 1,
"list": [
{
"gameKey": "simple-game",
"status": "ready",
"firestore": {
"buildStatus": "ready",
"playableDelivery": {
"android": {"type": "h5", "runtimeUrl": "https://game.vvicat.com/games/simple-game/#/"},
"ios": {
"type": "ios_manifest",
"manifest": {"schemaVersion": "ios_manifest_v1"},
"verification": {"status": "passed"}
}
}
}
},
{
"gameKey": "complex-game",
"status": "requires_capabilities",
"firestore": {
"buildStatus": "requires_capabilities",
"playableDelivery": {
"ios": {
"type": "unsupported",
"reason": "requires_capabilities",
"requiresCapabilities": [{"id": "legacy_h5_importer"}]
}
}
}
}
]
}
}
POST https://game.vvicat.com/api/game-builder?action=export_manifest_delivery&access_token=YOUR_TOKEN
Content-Type: application/json
{
"includeUnsupported": true,
"gameKeys": ["complex-canvas-game", "simple-game"]
}
{
"code": 0,
"action": "export_manifest_delivery",
"data": {
"total": 25,
"ready": 3,
"unsupported": 22,
"list": [
{"gameKey": "simple-game", "status": "ready"},
{"gameKey": "complex-canvas-game", "status": "requires_capabilities"}
]
}
}
发布前校验 Firebase 写库包
写入 Firestore 前建议先调用该接口。它会校验 ready 项是否为安全的 ios_manifest、
replay verification 是否 passed、iOS delivery 是否不包含 H5/runtimeUrl/HTML/JS/CSS/wasm fallback;
unsupported 项必须 fail closed,只返回 type: "unsupported" 和不支持原因。
POST https://game.vvicat.com/api/game-builder?action=validate_manifest_delivery&access_token=YOUR_TOKEN
Content-Type: application/json
{
"includeUnsupported": true,
"gameKeys": ["complex-canvas-game", "simple-game"]
}
{
"code": 0,
"action": "validate_manifest_delivery",
"data": {
"status": "passed",
"checked": 2,
"passed": 2,
"failed": 0,
"ready": 1,
"unsupported": 1,
"items": [
{
"gameKey": "simple-game",
"status": "passed",
"deliveryType": "ios_manifest"
},
{
"gameKey": "complex-canvas-game",
"status": "passed",
"deliveryType": "unsupported"
}
]
}
}
如果调用方已经拿到了 export_manifest_delivery 的结果,也可以把完整 delivery
放到请求体里校验。失败时返回 status: "failed",并在对应 item 的
checks 中标出失败项,例如 executableContent、noIosH5Fallback
或 verification。
7. Firebase 写入 Firestore 的推荐结构
capability 不支持时
{
"buildStatus": "requires_capabilities",
"playableDelivery": {
"ios": {
"type": "unsupported",
"reason": "requires_capabilities",
"requiresCapabilities": [
{
"id": "ios_manifest_compiler",
"kind": "compiler"
}
]
}
}
}
编译器启用后的 ready 结构
{
"buildStatus": "ready",
"playableDelivery": {
"android": {
"type": "h5",
"runtimeUrl": "https://game.vvicat.com/games/moon-gate/#/"
},
"ios": {
"type": "ios_manifest",
"manifest": {
"schemaVersion": "ios_manifest_v1",
"sourceHash": "sha256:...",
"engineId": "aivvi_native_playable",
"engineVersionMin": "1.0.0",
"format": "Mini Puzzle",
"title": "Moon Gate Puzzle",
"mechanicGraph": {
"nodes": [
{
"id": "target_1",
"type": "dragObject",
"x": 0.5,
"y": 0.5,
"radius": 0.1,
"gesture": {
"type": "drag",
"phases": ["start", "move", "drop"],
"dropBehavior": "completeOnDrop",
"snapToTarget": true,
"dropZones": [{"id": "zone_1", "x": 0.5, "y": 0.5, "radius": 0.12, "accepts": ["target_1"]}]
},
"uiComponents": [
{"id": "screen_1", "type": "screen", "role": "primary", "title": "Home", "text": "Home"},
{"id": "card_1", "type": "productCard", "role": "item", "title": "Item", "text": "Select item", "action": "select"},
{"id": "action_1", "type": "actionButton", "role": "primary", "action": "select", "text": "Select"}
]
}
],
"edges": []
},
"rules": {},
"theme": {
"palette": ["#152033", "#2f66d0", "#f3c969"],
"backgroundStyle": "radialStage",
"backgroundAssetId": "asset_1",
"backgroundContentMode": "fill",
"motionIntensity": "medium"
},
"assets": [
{
"id": "asset_1",
"kind": "image",
"url": "https://firebasestorage.googleapis.com/...",
"contentType": "image/png",
"usage": "sprite",
"width": 256,
"height": 256,
"frames": [{"id": "idle", "x": 0, "y": 0, "width": 128, "height": 128}],
"sha256": "sha256:..."
}
],
"renderers": [
{
"id": "target_1",
"renderer": "sprite",
"assetId": "asset_1",
"spriteFrame": "idle",
"contentMode": "fit",
"zIndex": 1,
"animation": [
{"state": "idle", "effect": "pulse", "durationSeconds": 1.2, "repeat": true, "intensity": 1, "easing": "easeInOut"},
{"state": "hit", "effect": "pop", "durationSeconds": 0.18, "repeat": false, "intensity": 2, "easing": "easeOut"},
{"state": "complete", "effect": "fade", "durationSeconds": 0.24, "repeat": false, "intensity": 1, "easing": "easeOut"}
]
}
],
"telemetry": {
"events": ["ready", "start", "input", "complete", "fail"]
}
},
"sourceHash": "sha256:...",
"verification": {
"status": "passed"
}
}
}
}
8. iOS 客户端对接逻辑
// 伪代码
let iosPlayable = project.playableDelivery?.ios
if iosPlayable?.type == "ios_manifest" {
let manifest = iosPlayable.manifest
validateManifest(manifest)
nativePlayableEngine.render(manifest)
} else {
showUnsupportedPlayable()
}
iOS 必须校验
manifest.schemaVersion == "ios_manifest_v1"manifest.engineVersionMin不高于当前 App 内置 runtime 版本。manifest.sourceHash存在。iosPlayable.verification.status == "passed"。- manifest 内不能包含 HTML、JS、CSS、wasm、脚本表达式、动态模块或外链导航。
9. iOS 禁止行为
| 禁止行为 | 原因 |
|---|---|
读取 previewUrl/runtimeUrl/htmlUrl 并用 WebView 打开 | iOS 上架风险,等价于下发 H5/JS playable。 |
| GameBuilder capability 不支持时 fallback 到 Android H5 | 违反 fail-closed 规则。 |
| 在 manifest 中解释通用脚本表达式 | 等价于远程代码/DSL 执行。 |
| 从 GameBuilder URL 直接拉 manifest 给 iOS 客户端 | manifest 应由 Firebase 服务端拉取并写入 Firestore。 |
10. 状态处理
| status | Firebase 处理 | iOS 处理 |
|---|---|---|
processing | 继续轮询 get_task_manifest | 展示生成中。 |
ready | 写入 playableDelivery.ios.manifest | native runtime 渲染。 |
requires_capabilities | 写入 playableDelivery.ios.type = "unsupported" | 展示不支持或要求升级 App。 |
failed | 标记失败,不下发 iOS artifact | 展示失败状态。 |
iosManifest,由 App 内置 native runtime 渲染。任何情况下 iOS 都不能用 H5 URL 兜底。