打通 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>
64 lines
3.5 KiB
JavaScript
64 lines
3.5 KiB
JavaScript
/**
|
||
* gen-preview-manifest.mjs —— 生成本地预览的静态 GamePackage manifest(含 E1 适配器 engineBundle)。
|
||
*
|
||
* 为什么:本地 mock 预览(/create/preview/9001)旧走「无 manifestUrl → demo 兜底」,而 demo 兜底用的
|
||
* 自研壳正被 T1b(LittleJS+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.json(gitignore,不入库,体积大)
|
||
* → 打印 OUTER_CHECKSUM;若与 src/mock/runtime.ts 的 LOCAL_MANIFEST_CHECKSUM 不一致,按打印值更新它。
|
||
*
|
||
* 确定性:源 bundle = 仓内已提交的 game-runtime/games/_wg1-gen/_adapter-probe/bundle.iife.js(esbuild 确定性产物),
|
||
* 故同一源 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, '..')
|
||
// 源 = 已提交的九门探针 bundle(E1 适配器 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#4;assets 空走几何兜底)
|
||
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 不一致,按此更新。')
|