游戏在线游玩加载体验:进入游玩长时间黑屏/空白且无加载反馈(网页用户端 + 后台试玩) #636

Closed
opened 2026-10-05 16:37:17 +08:00 by suzmii · 1 comment
Member

现象

用户反馈(IM 截图):「进入游玩得有个游戏加载页面,现在加载有点慢,一直黑屏体验不好」。

范围(用户确认):网页用户端 /games/play 与后台管理端「游戏审核 → 待审版本试玩」两处。

复现与实测

环境:本地真实栈(真实 SpacetimeDB + 真实私有 OSS + 真实发行包)+ 真实 Chromium,链路按 4 Mbps 下行 / 100 ms RTT 模拟(按真实线字节数延迟后再原样投递响应)。

夹具:按真实 AGC 发行包形状打包 —— 根 index.html + 真实 Phaser 4 压缩包 assets/phaser.min.js(1,375,976 B)+ assets/game.js + assets/logo.png,审核通过后从网页进入游玩。

观测点 用户端 /games/play 后台试玩
点击「开始游戏 / 试玩当前待审版本」 t=0 t=0
加载期界面 近黑面板(99.3% 像素亮度 < 24),只有一行 游戏正在启动… iframe 在 242 ms 直接以 opacity:1 出现,区域全白空白,页面全程未出现任何加载文案
游戏画面真正可见 3298 ms 4587 ms
期间主要流量 phaser.min.js 1,375,976 B 原样下发(模拟耗时 2724 ms) 同

发行网关响应头实测(GET /games/<gameId>/…):

资源 线字节 Content-Encoding Cache-Control ETag
index.html 1396 B none public, max-age=60, must-revalidate 无
assets/phaser.min.js 1,375,976 B none 同上 无
assets/game.js 428 B none 同上 无

即使显式带 Accept-Encoding: gzip, deflate, br,仍是 1,375,976 B 原样返回。

根因

  1. 用户端加载态只有一行字:src/components/game-distribution/GamePlayPage.tsx 的 .game-player-loading 覆盖在 #17131b 近黑壳(.game-player-shell)上,文案仅「游戏正在启动…」,没有封面、进度、动效;.game-player-frame 在 onLoad 前 opacity: 0。用户看到的就是「黑屏 + 一行小字」。
  2. ready 判定用 iframe onLoad:文档加载完成 ≠ 游戏可玩。实测包内 Phaser 脚本 3041 ms 才执行、场景 3253 ms 才出画面,onLoad 只是这两者之间的近似;换更重的包时误差会更大。
  3. 后台试玩没有任何加载态:apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx 拿到 previewUrl 后直接渲染 <iframe class="admin-game-review-preview">(CSS 只有 min-height: 42rem)。没有加载提示、没有超时、没有失败/会话过期提示 —— 预览会话过期或资源 404 时,审核员会永远停在空白区域。
  4. 发行网关不压缩文本资源:serve_release_asset / serve_admin_version_preview_asset_inner 把 ZIP 里解压出来的字节直出,不下发 Content-Encoding。Phaser 4 的 1.34 MB 压缩包对用户是 1.34 MB 实传;4 Mbps 下光这一个文件约 2.7 s,这也是「加载慢」的主因。
  5. (次要)每个发行资源都要过一次 SpacetimeDB 公开投影查询并现解 ZIP:冷包首资源 TTFB 1200 ms(从 OSS 拉 1.31 MiB 包并缓存),热后 13–33 ms。当前不是瓶颈,本次只在文档记录口径。

修改方案

前端(两处共用同一个共享组件,避免两端再次跑偏)

  • 新增 packages/shared 共享组件:游戏加载面 —— 封面/标题 + 动效进度指示 + 按时长派生的阶段文案(准备中 → 正在加载游戏资源 → 正在启动游戏)+ 「等待较久」的安抚提示;role="status"、aria-live 语义,移动端优先。
  • GamePlayPage:把一行字的 .game-player-loading 换成该加载面;ready 之后才淡入 iframe(保留现有 220 ms 过渡),超时后仍是现有「重试 / 返回」面板。
  • AdminGameDistributionReviewPage:试玩区域包同一加载面,iframe 加载完成才显示;补预览会话失败/资源 404 的错误提示与重试入口,不留空白。

后端(只作用于发行网关路由)

  • 对文本类发行资源(text/html、text/javascript、text/css、application/json、image/svg+xml 等)按 Accept-Encoding 下发 gzip/deflate,图片/音频/视频等已是压缩格式的不动;CSP、X-Content-Type-Options、CORP/CORS、Cache-Control 语义保持不变。
  • 定向测试锁住「压缩生效 + 头不变 + 二进制资源不被二次压缩」。

验收判据

  • 同一 4 Mbps 模拟链路下,用户端/后台端加载期都不再是纯黑/纯白:出现可见加载面与阶段文案;游戏画面可见时间从 3298 / 4587 ms 明显下降。
  • assets/phaser.min.js 改为 Content-Encoding: gzip(约 1,376 KB → 约 330 KB),该文件在 4 Mbps 下的等待由 ~2.7 s 降到 ~0.7 s;Cache-Control / CSP / CORP / CORS 与现状一致。
  • 回归:check:game-distribution-web-e2e、check:game-distribution-sandbox-e2e、check:game-distribution-ops-rollback-e2e、admin-web 与 web 的定向测试保持通过。
  • 两处加载面均有真实 Chromium 截图对照(改前 / 改后)。

证据与说明

  • 实测脚本为一次性探针(不入库):真实发行包 + Chromium + 按线字节延迟的链路模拟;发布夹具与两处加载面的截图会在 PR 里附上。
  • 本次不改动 publicationRevision、审核 CAS、沙箱口径与发行入口路由;/games/<gameId>/ 路径与 Cookie 拒绝语义不变。
## 现象 用户反馈(IM 截图):「进入游玩得有个游戏加载页面,现在加载有点慢,一直黑屏体验不好」。 范围(用户确认):**网页用户端** `/games/play` 与**后台管理端**「游戏审核 → 待审版本试玩」两处。 ## 复现与实测 环境:本地真实栈(真实 SpacetimeDB + 真实私有 OSS + 真实发行包)+ 真实 Chromium,链路按 4 Mbps 下行 / 100 ms RTT 模拟(按真实线字节数延迟后再原样投递响应)。 夹具:按真实 AGC 发行包形状打包 —— 根 `index.html` + 真实 Phaser 4 压缩包 `assets/phaser.min.js`(1,375,976 B)+ `assets/game.js` + `assets/logo.png`,审核通过后从网页进入游玩。 | 观测点 | 用户端 `/games/play` | 后台试玩 | | --- | --- | --- | | 点击「开始游戏 / 试玩当前待审版本」 | t=0 | t=0 | | 加载期界面 | 近黑面板(99.3% 像素亮度 < 24),只有一行 `游戏正在启动…` | iframe 在 242 ms 直接以 `opacity:1` 出现,区域**全白空白**,页面全程未出现任何加载文案 | | 游戏画面真正可见 | **3298 ms** | **4587 ms** | | 期间主要流量 | `phaser.min.js` 1,375,976 B 原样下发(模拟耗时 2724 ms) | 同 | 发行网关响应头实测(`GET /games/<gameId>/…`): | 资源 | 线字节 | `Content-Encoding` | `Cache-Control` | ETag | | --- | --- | --- | --- | --- | | `index.html` | 1396 B | none | `public, max-age=60, must-revalidate` | 无 | | `assets/phaser.min.js` | 1,375,976 B | **none** | 同上 | 无 | | `assets/game.js` | 428 B | none | 同上 | 无 | 即使显式带 `Accept-Encoding: gzip, deflate, br`,仍是 1,375,976 B 原样返回。 ## 根因 1. **用户端加载态只有一行字**:`src/components/game-distribution/GamePlayPage.tsx` 的 `.game-player-loading` 覆盖在 `#17131b` 近黑壳(`.game-player-shell`)上,文案仅「游戏正在启动…」,没有封面、进度、动效;`.game-player-frame` 在 `onLoad` 前 `opacity: 0`。用户看到的就是「黑屏 + 一行小字」。 2. **ready 判定用 iframe `onLoad`**:文档加载完成 ≠ 游戏可玩。实测包内 `Phaser` 脚本 3041 ms 才执行、场景 3253 ms 才出画面,`onLoad` 只是这两者之间的近似;换更重的包时误差会更大。 3. **后台试玩没有任何加载态**:`apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx` 拿到 `previewUrl` 后直接渲染 `<iframe class="admin-game-review-preview">`(CSS 只有 `min-height: 42rem`)。没有加载提示、没有超时、没有失败/会话过期提示 —— 预览会话过期或资源 404 时,审核员会永远停在空白区域。 4. **发行网关不压缩文本资源**:`serve_release_asset` / `serve_admin_version_preview_asset_inner` 把 ZIP 里解压出来的字节直出,不下发 `Content-Encoding`。Phaser 4 的 1.34 MB 压缩包对用户是 1.34 MB 实传;4 Mbps 下光这一个文件约 2.7 s,这也是「加载慢」的主因。 5. (次要)每个发行资源都要过一次 SpacetimeDB 公开投影查询并现解 ZIP:冷包首资源 TTFB 1200 ms(从 OSS 拉 1.31 MiB 包并缓存),热后 13–33 ms。当前不是瓶颈,本次只在文档记录口径。 ## 修改方案 **前端(两处共用同一个共享组件,避免两端再次跑偏)** - 新增 `packages/shared` 共享组件:游戏加载面 —— 封面/标题 + 动效进度指示 + 按时长派生的阶段文案(准备中 → 正在加载游戏资源 → 正在启动游戏)+ 「等待较久」的安抚提示;`role="status"`、`aria-live` 语义,移动端优先。 - `GamePlayPage`:把一行字的 `.game-player-loading` 换成该加载面;`ready` 之后才淡入 iframe(保留现有 220 ms 过渡),超时后仍是现有「重试 / 返回」面板。 - `AdminGameDistributionReviewPage`:试玩区域包同一加载面,iframe 加载完成才显示;补预览会话失败/资源 404 的错误提示与重试入口,不留空白。 **后端(只作用于发行网关路由)** - 对文本类发行资源(`text/html`、`text/javascript`、`text/css`、`application/json`、`image/svg+xml` 等)按 `Accept-Encoding` 下发 gzip/deflate,图片/音频/视频等已是压缩格式的不动;CSP、`X-Content-Type-Options`、CORP/CORS、`Cache-Control` 语义保持不变。 - 定向测试锁住「压缩生效 + 头不变 + 二进制资源不被二次压缩」。 ## 验收判据 - 同一 4 Mbps 模拟链路下,用户端/后台端加载期都不再是纯黑/纯白:出现可见加载面与阶段文案;游戏画面可见时间从 3298 / 4587 ms 明显下降。 - `assets/phaser.min.js` 改为 `Content-Encoding: gzip`(约 1,376 KB → 约 330 KB),该文件在 4 Mbps 下的等待由 ~2.7 s 降到 ~0.7 s;`Cache-Control` / CSP / CORP / CORS 与现状一致。 - 回归:`check:game-distribution-web-e2e`、`check:game-distribution-sandbox-e2e`、`check:game-distribution-ops-rollback-e2e`、admin-web 与 web 的定向测试保持通过。 - 两处加载面均有真实 Chromium 截图对照(改前 / 改后)。 ## 证据与说明 - 实测脚本为一次性探针(不入库):真实发行包 + Chromium + 按线字节延迟的链路模拟;发布夹具与两处加载面的截图会在 PR 里附上。 - 本次不改动 `publicationRevision`、审核 CAS、沙箱口径与发行入口路由;`/games/<gameId>/` 路径与 Cookie 拒绝语义不变。
suzmii added the Kind/Bug
Priority
Medium
3
labels 2026-10-05 16:37:17 +08:00
suzmii self-assigned this 2026-10-05 16:37:17 +08:00
Author
Member

已在 fix/game-play-loading 上完成,PR:#639(含前端/后端改动、测试与截图对照)。

根因(真实栈复现,非推断)

真实发行包夹具(根 index.html + 真实 Phaser 4 压缩包 phaser.min.js 1,375,976 B)+ 真实 Chromium,链路按 4 Mbps / 100 ms 模拟:

  1. 用户端:加载期是 #17131b 近黑壳 + 一行 游戏正在启动…(实测 99.3% 像素亮度 < 24),ready 判定只看 iframe onLoad(文档加载完成 ≠ 游戏可玩)→ 用户看到的就是「黑屏 + 一行小字」。
  2. 后台试玩:AdminGameDistributionReviewPage 拿到 previewUrl 后直接渲染 <iframe class="admin-game-review-preview">(CSS 只有 min-height: 42rem)——没有加载态、没有超时、没有失败提示,加载期是一片空白,会话过期或资源 404 也会永远停在空白。
  3. 发行网关:文本资源把 ZIP 解压后的原文直出,phaser.min.js 1,375,976 B 原样下发(Content-Encoding: none)、无 ETag,60 秒 max-age 过后浏览器只能重下整包。

改动(PR #639)

  • 新增共享加载面 PlatformGameLoadingSurface(packages/shared):封面/标题 + 动效进度 + 阶段文案(准备 → 加载资源 → 启动引擎)+ 慢加载提示;网页游玩页与后台试玩共用,两端加载上限统一为 PLATFORM_GAME_LOADING_TIMEOUT_MS(20 秒),后台超时给「重新创建试玩会话」。
  • 发行网关:文本类资源(≥1 KiB、客户端接受 gzip)下发 Content-Encoding: gzip + Vary;下发强 ETag,命中 If-None-Match 返回 304。max-age=60, must-revalidate 与撤销窗口不变。
  • 门禁:cargo test -p api-server -- game_distribution 49 passed(含 5 条新增)、check:game-distribution-ops-rollback-e2e 47 PASS(含 4 条新增)、共享/游玩页/后台试玩三条前端用例、typecheck 与编码检查全通过。

实测对照(同一 4 Mbps 模拟链路)

观测点 改前 改后
用户端「开始游戏」→ 画面可见 3298 ms 1543 ms
用户端加载期界面 近黑面板 + 一行小字 封面 + 标题 + 动效进度 + 阶段文案
后台「试玩」→ 画面可见 4587 ms 2694 ms
后台加载期界面 全白空白、无任何文案 同一加载面(浅色面板)
phaser.min.js 传输 1,375,976 B(none,2724 ms) 353,336 B(gzip,774 ms)

用户端加载面实测截图、后台加载面实测截图与两条「画面就绪」对照见 PR #639 正文。

实拍截图

用户端加载面:

用户端加载面

后台试玩加载面:

后台试玩加载面

已在 `fix/game-play-loading` 上完成,PR:#639(含前端/后端改动、测试与截图对照)。 ## 根因(真实栈复现,非推断) 真实发行包夹具(根 `index.html` + 真实 Phaser 4 压缩包 `phaser.min.js` 1,375,976 B)+ 真实 Chromium,链路按 4 Mbps / 100 ms 模拟: 1. **用户端**:加载期是 `#17131b` 近黑壳 + 一行 `游戏正在启动…`(实测 99.3% 像素亮度 < 24),`ready` 判定只看 iframe `onLoad`(文档加载完成 ≠ 游戏可玩)→ 用户看到的就是「黑屏 + 一行小字」。 2. **后台试玩**:`AdminGameDistributionReviewPage` 拿到 `previewUrl` 后直接渲染 `<iframe class="admin-game-review-preview">`(CSS 只有 `min-height: 42rem`)——**没有加载态、没有超时、没有失败提示**,加载期是一片空白,会话过期或资源 404 也会永远停在空白。 3. **发行网关**:文本资源把 ZIP 解压后的原文直出,`phaser.min.js` 1,375,976 B 原样下发(`Content-Encoding: none`)、无 ETag,60 秒 `max-age` 过后浏览器只能重下整包。 ## 改动(PR #639) - 新增共享加载面 `PlatformGameLoadingSurface`(`packages/shared`):封面/标题 + 动效进度 + 阶段文案(准备 → 加载资源 → 启动引擎)+ 慢加载提示;网页游玩页与后台试玩共用,两端加载上限统一为 `PLATFORM_GAME_LOADING_TIMEOUT_MS`(20 秒),后台超时给「重新创建试玩会话」。 - 发行网关:文本类资源(≥1 KiB、客户端接受 gzip)下发 `Content-Encoding: gzip` + `Vary`;下发强 `ETag`,命中 `If-None-Match` 返回 304。`max-age=60, must-revalidate` 与撤销窗口不变。 - 门禁:`cargo test -p api-server -- game_distribution` 49 passed(含 5 条新增)、`check:game-distribution-ops-rollback-e2e` 47 PASS(含 4 条新增)、共享/游玩页/后台试玩三条前端用例、typecheck 与编码检查全通过。 ## 实测对照(同一 4 Mbps 模拟链路) | 观测点 | 改前 | 改后 | | --- | --- | --- | | 用户端「开始游戏」→ 画面可见 | 3298 ms | **1543 ms** | | 用户端加载期界面 | 近黑面板 + 一行小字 | 封面 + 标题 + 动效进度 + 阶段文案 | | 后台「试玩」→ 画面可见 | 4587 ms | **2694 ms** | | 后台加载期界面 | 全白空白、无任何文案 | 同一加载面(浅色面板) | | `phaser.min.js` 传输 | 1,375,976 B(none,2724 ms) | **353,336 B(gzip,774 ms)** | 用户端加载面实测截图、后台加载面实测截图与两条「画面就绪」对照见 PR #639 正文。 ### 实拍截图 用户端加载面: ![用户端加载面](https://git.genarrative.world/git/attachments/aaaa1702-bff8-4078-bb48-79991e8944c9) 后台试玩加载面: ![后台试玩加载面](https://git.genarrative.world/git/attachments/e8692956-2027-4ea1-accb-45fbaf5de146)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#636