GameBuilder iOS Manifest 线上接口与 iOS 对接说明

更新时间:2026-07-09
线上文档:https://game.vvicat.com/docs/game-builder-ios-manifest-integration.html
API 入口:https://game.vvicat.com/api/game-builder
目标:iOS 不再加载 H5,改为 Firebase 下发 iosManifest 给 App 内置 native runtime 渲染。
iOS 客户端不能加载 previewUrlruntimeUrlhtmlUrl 或 GameBuilder H5 URL。 iOS 只允许读取 Firebase 保存的 playableDelivery.ios.manifest

1. 线上访问地址

用途地址
iOS 对接文档https://game.vvicat.com/docs/game-builder-ios-manifest-integration.html
完整实现方案 Markdownhttps://game.vvicat.com/docs/game-builder-ios-manifest-api-implementation-plan.md
GameBuilder APIhttps://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。已支持 tapTargettileBoardgridCombatflapRunnerarcadePhysicsaimShootersportsFlicksimQueuedialogChoicepaintCanvasmusicRhythmappPanelcombodragObjecttimerscore,以及声明式 motionPathfeedbackcontrolstargetRoleshapelaneboardKindboardPieceslegalMoves。棋类/井字棋会以 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.jsonios-manifest.json 的 manifest-ready runtime。 第二阶段支持保守的声明式旧 H5:页面中带 data-ios-tap-targetdata-xdata-ydata-score 的元素可恢复为 tapTarget manifest。 第三阶段支持 Vite/JS bundle 内的静态迁移 metadata 注释块;GameBuilder 只读取 JSON 注释,不执行 JS。 普通旧 H5 会返回 requires_capabilities,等待 Legacy H5 Importer 后续处理;可识别的 2048chessnoughts-and-crossesclickerstackcatchfishpopquizrock-paper-scissorsdicedemocracyjokelaughannoyfeedservecleanuplawngardensushidumplingforgeragsfollowerranchcollectorradiododgeracerjumptimercountdownphysicscapybarawakeupwrestlingbrawl 等类型会通过 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.jsonios-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_capabilitiesnextAction=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=lowtier=mediumlocalAssetReferences 较少的游戏。 可传 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.jsonios-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.jsonios-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.jsonios-manifest.jsonios-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 用于审计,包含 schemaVersionmodecreatedByconfirmApplylegacyAndroidRuntimePreservedandroidRuntimeUrliosManifestSourceHash

批量应用 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.jsonios-manifest.jsonios-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.jsonios-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 中标出失败项,例如 executableContentnoIosH5Fallbackverification

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 必须校验

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. 状态处理

statusFirebase 处理iOS 处理
processing继续轮询 get_task_manifest展示生成中。
ready写入 playableDelivery.ios.manifestnative runtime 渲染。
requires_capabilities写入 playableDelivery.ios.type = "unsupported"展示不支持或要求升级 App。
failed标记失败,不下发 iOS artifact展示失败状态。
一句话:Android 继续 H5;iOS 只读 Firebase 中的 iosManifest,由 App 内置 native runtime 渲染。任何情况下 iOS 都不能用 H5 URL 兜底。