games-development-ai/game-studio/scripts/gen-preview-manifest.mjs
lili f6f8e01274 feat(studio): 本地预览渲染真生成物——runtime mock 9001 接 E1 engineBundle,闭合 create→预览可玩闭环
打通 E1↔E6:本地 create→预览此前走「无 manifestUrl→demo 兜底」,而 demo 用的自研壳正被 T1b 替换 → load_timeout 玩不了。改为:
- mock/runtime.ts:9001 回填 manifestUrl=/mock-manifests/9001.json + checksum(sha256 of 原文);宿主 fetchAndVerifyManifest 校验后走【引擎包路】__GameBundle.bootGameHost 真玩
- scripts/gen-preview-manifest.mjs(新建):读仓内已提交的九门探针 bundle(E1 适配器产物)→ 包成 GamePackage(engineBundle 内嵌)静态件 + 打印 checksum;确定性(同源 bundle→同 sha256)
- public/mock-manifests/ gitignore(生成件大,不入库;源 bundle 已在 game-runtime 入库一次)
- .agent 记本地预览 setup 步

验证:`npm run build` 绿;**CDP 真 UI 走查实证**——/create/preview/9001 状态由「加载失败 load_timeout」→「可试玩」,预览渲染 tap-target 真玩件(Score:0/3 + 3 目标),errtip=null。静态 manifest 经 vite 原样服务,curl 校 checksum 与 mock 一致。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 16:05:36 -07:00

64 lines
3.5 KiB
JavaScript
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.

/**
* gen-preview-manifest.mjs —— 生成本地预览的静态 GamePackage manifest含 E1 适配器 engineBundle
*
* 为什么:本地 mock 预览(/create/preview/9001旧走「无 manifestUrl → demo 兜底」,而 demo 兜底用的
* 自研壳正被 T1bLittleJS+Runner v2替换 → 本地 load_timeout 玩不了。本脚本把【已验九门的 E1 适配器产物
* bundle】(__GameBundle) 包成一份 GamePackage 静态件,宿主 GamePlayer 经 fetchAndVerifyManifest 校 sha256 后
* 走【引擎包路】真玩——本地闭合 create→预览【可玩】闭环CDP 走查实证)。
*
* 用法(在 game-studio/ 下node scripts/gen-preview-manifest.mjs
* → 写 public/mock-manifests/9001.jsongitignore不入库体积大
* → 打印 OUTER_CHECKSUM若与 src/mock/runtime.ts 的 LOCAL_MANIFEST_CHECKSUM 不一致,按打印值更新它。
*
* 确定性:源 bundle = 仓内已提交的 game-runtime/games/_wg1-gen/_adapter-probe/bundle.iife.jsesbuild 确定性产物),
* 故同一源 bundle → 同一 manifest json → 同一 sha256 → 硬编码 checksum 稳定。换源 bundle 须重跑并同步 checksum。
*/
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { createHash } from 'node:crypto'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
const HERE = dirname(fileURLToPath(import.meta.url))
const studioRoot = resolve(HERE, '..')
// 源 = 已提交的九门探针 bundleE1 适配器 tap-target 产物__GameBundle.bootGameHost 入口)
const BUNDLE_PATH = resolve(studioRoot, '../game-runtime/games/_wg1-gen/_adapter-probe/bundle.iife.js')
const OUT_PATH = resolve(studioRoot, 'public/mock-manifests/9001.json')
const bundle = readFileSync(BUNDLE_PATH, 'utf8')
// GamePackage对齐 contract.ts GamePackage / game-package.schema.json#4assets 空走几何兜底)
const pkg = {
schemaVersion: '1.0',
gameId: '1',
versionId: '9001',
templateId: 'generic',
gameConfig: { title: '本地预览 · 点目标', winScore: 3 },
assets: [],
manifest: {
runtimeVersion: '1.0.0',
entry: 'index.html',
preloadPolicy: 'eager',
bundleSize: Buffer.byteLength(bundle),
checksum: createHash('sha256').update(bundle).digest('hex'), // 内层=bundle 自身 sha256非校验门
},
meta: {
title: '本地预览 · 点目标',
summary: 'E1 适配器产物(__GameBundle)经本地 mock 预览试玩——闭合 create→预览可玩闭环。',
cover: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
ageRating: 'all',
},
engineBundle: bundle, // 宿主 inject.ts 取此作独立内联 <script> → window.__GameBundle
provenance: { model: 'adapter-probe', traceId: 'local-preview-9001' },
}
const json = JSON.stringify(pkg) // 被服务的原文vite 原样静态服务)
mkdirSync(dirname(OUT_PATH), { recursive: true })
writeFileSync(OUT_PATH, json)
// 外层 checksum = sha256(被服务 json 原文),宿主 fetchAndVerifyManifest 据此校验(须 = runtime.ts LOCAL_MANIFEST_CHECKSUM
const checksum = createHash('sha256').update(json, 'utf8').digest('hex')
console.log('[gen-preview-manifest] 写出:', OUT_PATH.replace(studioRoot + '/', ''))
console.log('[gen-preview-manifest] bytes:', Buffer.byteLength(json))
console.log('[gen-preview-manifest] OUTER_CHECKSUM:', checksum)
console.log('[gen-preview-manifest] ↑ 若与 src/mock/runtime.ts LOCAL_MANIFEST_CHECKSUM 不一致,按此更新。')