Files
youle_app_ios_v2/docs/Verification-Checklist.md
T
joywayerandClaude Opus 4.7 a6095ef4c8 Phase 4 方向最终决策 A:QQ 不在 H5 桥范围,不实施
深度 grep msext NewRootVC.m / gameController.m 未发现 QQShareManager
引用 — QQ 完全不接 H5 桥。Contract §3.1 [2] sharetype 取值仅 "1"/"2"=
微信 / "3"=闲聊,无 QQ 选项。

按 CLAUDE.md 原则 A(H5 端零修改不可妥协):契约不变 → H5 桥不实现
QQ 分享。msext 里 QQShareManager 只服务于原生 SharePanel(独立功能),
新外壳无原生 SharePanel-like 入口需求 → 也不需要 QQShareManager。

文档同步调整为"不实施":
  - Plan §2.3 阻塞表:QQ OpenSDK 改写为详细理由(H5 桥契约无 QQ
    选项 + msext QQShareManager 不接 H5 桥的 grep 证据)
  - Plan §5 Phase 4.2:从"QQ 改走 URL Scheme"改为"不实施",但保留
    URL Scheme 备用知识库(未来如需原生入口可参 msext fallback)
  - CLAUDE.md 依赖管理段:"不接入 QQ SDK 也不实现 QQ 分享",明示
    原因(契约无 QQ + msext 行为)
  - Verification-Checklist L 章节:QQ 验证项删除(不实施无需验证)

Phase 4 至此剩 4.B 微信 SDK 接入(项目方阻塞)+ 4.D 截图/远端图分享
(依赖 4.B)。当前 stub 已让 H5 调登录/分享不报 "no handler",
等微信 SDK 到位后升级。

Plan §6.4 里程碑加一行决策记录。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 19:45:12 +08:00

243 lines
12 KiB
Markdown
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.
# 功能验证清单(开发期累积,统一验收)
> **定位**:各 Phase 实施期间陆续累积的验证项目,本文档**不是契约**(契约在 `H5-Native-Contract.md` §10),而是面向开发者的**指导步骤**——告诉验证者"应该做什么操作、看到什么现象、用什么工具"。
>
> **使用方式**
> - 每完成一个 Phase 子项 → 把对应验证步骤勾掉
> - **不要在 Phase 进行中频繁手动验证**,避免反复重装 app 浪费时间
> - 多个 Phase 累积后**统一跑一次**完整验证清单
> - 与 Contract §10 契约验收清单**并行**Contract §10 是黑盒契约边界("必须长这样"),本文档是**怎么验证它长这样**的操作手册
---
## 通用准备
### 环境
| 工具 | 用途 | 启用方式 |
|------|------|---------|
| Xcode console | 看原生 os.log 输出 | Xcode 底部输出窗口 |
| Safari Web Inspector | 看 H5 端 JS console / DOM / Network | macOS Safari → 开发菜单 → Simulator / 真机 → 当前 H5 页面 |
| 模拟器 Features 菜单 | 切换 Battery State / Network Link Conditioner / Toggle In-Call Status | Simulator → Features |
| 真机 Safari 调试 | 真机 H5 网页检查器 | iPhone → 设置 → Safari → 高级 → 网页检查器 = ON |
### 重装 app(每次跑完整验证前都做一遍)
按 CLAUDE.md「typical case 1launch snapshot 缓存粘滞」教训:
- 模拟器/真机:**长按 app → 删除** → Xcode 重新 Run
- 或:`xcrun simctl uninstall booted com.skyapp.ylgamehall` 然后 Xcode Run
- 这样可以清掉 iOS sandbox 内的 launch snapshot 缓存,避免改了 Info.plist / LaunchScreen 后还是看到旧版本
### 沙盒访问
```bash
xcrun simctl get_app_container booted com.skyapp.ylgamehall data
# 输出形如 /Users/.../Containers/Data/Application/<UUID>/
```
---
## Phase 1 — 启动闭环
### A. 启动图 + LaunchScreen
- [ ] **A.1 启动图方向正确**:横屏设备启动瞬间看到完整 logo + "快乐游戏 / 健康生活" 文字,画面正向(不是横躺)
- [ ] **A.2 状态栏方向**:电池/时间在画面顶部居中横向,**不在**画面左侧竖向(竖向 = iOS 错误地按 portrait 渲染 LaunchScreen,参 CLAUDE.md「典型案例 1」)
- [ ] **A.3 黑边合理**:比 16:9 更宽的 iPhone(如 16 Pro)启动图两侧补黑边可接受;iPad 4:3 上下补黑边可接受
### B. 启动流水线时序
预期 Xcode console 顺序:
```
[BundleConfig] 渠道注入读取结果(11 项)
[SandboxPaths] 路径计算结果
[ResourceUnzipper] ensureReady 完成
[RemoteConfigClient] 拉取成功 .parsed
[VersionResolver] chulishengji 双子树合并结果
[LocalVersionReader] 本地 vs 远端对比
[LobbyZipUpgrader] 无需升级 / 升级完成 N → M
[lobby] write app_data.js → ... ← Phase 1.7 落地(AppDataWriter
[lobby] write app_gamesname.js → ...
[lobby] write app_battery.js: app_getbattery=0.XX → ...
[lobby] write app_network.js: app_getnetwork=2 → ...
```
- [ ] **B.1 启动一气呵成**splash 显示 → "拉取配置中" → ("下载更新中 XX%") → "加载大厅..." → splash 0.3s 淡出,看到 H5 大厅
- [ ] **B.2 16:9 letterbox 黑边**:H5 大厅在屏幕中间,左右(或上下)有黑边,画面比例 16:9
- [ ] **B.3 启动总耗时 < 2s**(无升级路径),有升级时进度条实时更新
### C. `app_*.js` 文件落盘 + 全局变量命名
```bash
# 进入沙盒看 4 个文件
xcrun simctl get_app_container booted com.skyapp.ylgamehall data
# 然后 cat 4 个文件
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_data.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_battery.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_network.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_gamesname.js
```
- [ ] **C.1 4 个文件全部存在**且内容非空
- [ ] **C.2 app_data.js 内 12 个 var 命名 100% 正确**(特别注意大小写:`app_Launchtype` L 大写、`app_getwifisignalLevel` wifi 小写 + signal/Level 区分)
- [ ] **C.3 app_battery.js 变量名带 get**`var app_getbattery=N;`(不是 `app_battery`
- [ ] **C.4 app_network.js 变量名带 get**`var app_getnetwork=N;`
- [ ] **C.5 app_gamesname.js 沿用 msext 字面**`var app_gamesname=new Array('xxx');`var 后两个空格 + new Array(...)
- [ ] **C.6 不存在已撤销变量**`app_data.js` 内没有 `app_gameid` / `app_compareCode`(早期 Contract 误列,原 msext 不写)
### D. `window.app_*` H5 console 对账
Safari Web Inspector → Console
```js
console.log("app_gameconfig =", app_gameconfig); // 期望 ChannelConfig.plist gameconfig 实际值
console.log("app_market =", app_market); // 期望 "2"
console.log("app_agent =", app_agent); // 期望 ChannelConfig.plist agent 实际值
console.log("app_channel =", app_channel);
console.log("app_Launchtype =", app_Launchtype); // 大厅期望 0
console.log("app_appversion =", app_appversion); // 期望 '0' 字符串(审核切换标志非版本号)
console.log("app_getbattery =", app_getbattery); // 期望 0.XX
console.log("app_getnetwork =", app_getnetwork); // 期望 1/2/3
console.log("app_gamesname =", app_gamesname); // 期望 Array
console.log("app_gameid =", app_gameid); // 期望 undefined ✓
console.log("app_compareCode =", app_compareCode); // 期望 undefined ✓
```
- [ ] **D.1 所有 15 个 app_* 全局变量都 `!== undefined`**
- [ ] **D.2 关键字段都是 ChannelConfig.plist 的实际值**(不是 H5 zip 默认占位)
- [ ] **D.3 `app_appversion === '0'`**(审核标志,正常业务);`app_gameconfig` 取 BundleConfig.gameConfig
---
## Phase 2 — 大厅 handler + 反向 callback
### E. H5 → Native handler 响应
每项的验证方式:在 H5 console 调对应 `bridge.callHandler('xxx', data, function(resp){console.log(resp);})`,看 Xcode console 有无副作用 + H5 收到 cb。
- [ ] **E.1 vibrator** → 真机震动 + H5 cb "vibrator"
- [ ] **E.2 repeatvibrator** → 真机震动 + cb "repeatvibrator"
- [ ] **E.3 canclevibrator** → cb "canclevibrator"(无可感知副作用,正常)
- [ ] **E.4 gameCopytext('test123')** → 系统剪贴板含 "test123";其他 app 粘贴可验证;iOS 14+ 会出"已粘贴自 ylgamehall" banner(平台行为非 bug
- [ ] **E.5 gamepastetext** → cb 是当前剪贴板内容(不是字面 "gamepastetext"
- [ ] **E.6 SwitchShake(1) + SwitchShake(0)** → 内部 canVoice 状态切换(无可视化副作用,cb "SwitchShake"
- [ ] **E.7 startshake → 摇一摇手机 → H5 收到 `shakeEnd` 反向 callback**`bridge.registerHandler('shakeEnd', fn)` 先注册)
- [ ] **E.8 stopshake → 摇一摇 → H5 不再收到 shakeEnd**
- [ ] **E.9 voicePlaying(1) / voicePlaying(0)** → 内部 actor VoiceCenter 开关位(cb "Response from voicePlaying");mediaTypeAudio 实现后效果可验证
- [ ] **E.10 getphoneInfo** → cb "getphoneInfo"**大写 I**+ 反向 callback `getphoneinfo`**小写 i**)含 6 字段(PhoneAdresseMAC / PhoneDeviceBrand / PhoneIMEI / PhoneModel / PhoneProvidersName / PhoneVersion
- [ ] **E.11 browser('https://www.apple.com')** → Safari 打开苹果首页
- [ ] **E.12 opensaoma** → cb "opensaoma"(空实现,无可感知副作用,符合契约)
- [ ] **E.13 startlocation** → cb "startlocation"Phase 2 stub,无定位真实业务;Phase 5 升级后再验真实定位)
### F. Native → H5 反向 callback
H5 需先用 `bridge.registerHandler('callbackName', fn)` 注册:
- [ ] **F.1 模拟器 Features → Battery State 切换** → H5 收到 `getBattery` "0.XX" + `window.app_getbattery` 实时跟随
- [ ] **F.2 模拟器 Features → Network Link Conditioner 切换 100% Loss** → H5 收到 `getnetwork` "1" + `window.app_getnetwork === 1`;切回 WiFi → 收 "2";切 3G → 收 "3"
- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "1";回前台 → 收 "2"
- [ ] **F.4 摇一摇** → shakeEnd(见 E.7
- [ ] **F.5 getphoneInfo 触发 getphoneinfo**(见 E.10
### G. ExternalSubscriptions 生命周期
> Phase 2.C 已实现 setup/teardown 配对,但 Phase 6 子游戏容器才能完整验证"防双发"。当前只验证基本生命周期。
- [ ] **G.1 viewWillAppear setup**app 启动后 onChange 闭包都已挂钩(F.1-F.4 都生效)
- [ ] **G.2 viewWillDisappear teardown**:手动触发场景待 Phase 6 子游戏 push 上来时验证
---
## Phase 3 — 音频体系(部分实现:3.A 完成;3.B/C/D 待外部资源)
### H. 本地音频(srcIsloop3.A 完成)
- [ ] **E.14 srcIsloop({src:"xxx.wav", isloop:0})** → 听到单次按钮音;cb "Response from srcIsloop"
- [ ] **E.15 srcIsloop({src:"bg.wav", isloop:1})** → 听到背景循环音 + 同时调 srcIsloop({src:"另一首.wav", isloop:1}) → 替换为新背景循环
- [ ] **E.16 srcIsloop({src:"bg.wav", isloop:-1})** → 停止 bg.wav 背景;如果当前背景不是 bg.wav 则无效(与 msext 同名 type 校验)
> **依赖**:测试需要 H5 zip 内 `assets/wav/` 目录有可用音频文件。
> H5 团队 / 项目方提供后做。
### I. prepareaudio / mediaTypeAudio stub3.B/3.C/3.D 待实现)
- [ ] **E.17 prepareaudio** → cb "Response from prepareaudio"stub 仅 cb,无录音 / 无 getaudiourl 反向)
- [ ] **E.18 mediaTypeAudio** → cb "Response from mediaTypeAudio"stub 仅 cb,无下载 / 无播放 / 无 gameui_play_voice 反向)
> 3.B AMR 转码 + 3.C 七牛上传 + 3.D 远端播放完整实现后升级。
---
## Phase 4 — 分享 + 登录(部分实现:4.A stub 完成;4.B/4.C 待外部资源)
### J. accreditlogin / friendsShare stub4.A 完成)
- [ ] **E.19 accreditlogin** → cb "Response from accreditlogin"stub 仅 cb,无微信授权调起、无 sharelogin 反向)
- [ ] **E.20 friendsSharetypeUrlToptitleDescript** → cb "sharefriend"stub 仅 cb,无微信/QQ 分享调起、无 sharesuccess 反向)
> 4.B 微信 SDK + 后台 /wechat/login + 4.C QQ URL Scheme 完整实现后升级。
### K. 微信 SDK + Universal Links4.B 待实现)
> 项目方提供 microSDK + AppID + UL 后填充。预备验证项:
> - sharelogin 7 字段(含大写 Province + 小写 city 经 danbian: 处理)
> - sharesuccess({success:"2", type:"1"/"2"})
> - app 切到后台微信授权 → 切回 app sharelogin 触发
### L. ~~QQ URL Scheme 分享~~(不实施,按 A 方向最终决策)
> Contract §3.1 2`sharetype` 取值仅 "1"/"2"=微信 / "3"=闲聊,无 QQ 选项。
> msext 的 QQShareManager 只服务于原生 SharePanel,不接 H5 桥。新外壳契约
> 不变 → 不实现 QQ 分享,无需验证。
---
## Phase 5 — 定位(待实现)
> Phase 5 实施后填充。预备验证项:
> - startlocation 一次性 + 持续 + getlocationinfo 9 字段(含小写 province
---
## Phase 6 — 子游戏(待实现)
> Phase 6 实施后填充。预备验证项:
> - SwitchOverGameData push 子游戏
> - 子游戏 backgameData 回大厅 + getWebdata
> - 子游戏 push 上来后大厅 ExternalSubscriptions teardown(验证不会双发)
---
## Phase 7-10(待实现)
> 各 Phase 实施后陆续填充。
---
## 附:契约 §10 26 项验收清单映射
本文档每个章节对应 Contract §10 的 A/B/C/D/E 五节:
| 本文档章节 | Contract §10 节 | 状态 |
|---|---|---|
| A. 启动图 + LaunchScreen | §A 启动(启动相关) | Phase 1 实现 |
| B. 启动流水线时序 | §A 启动 | Phase 1 实现 |
| C. app_*.js 落盘 + 命名 | §A 启动 | Phase 1 实现 |
| D. window.app_* H5 console | §A 启动 + §B 桥接 | Phase 1 实现 |
| E. H5→Native handler | §B 桥接 | Phase 2 实现 |
| F. 反向 callback | §C 系统事件 + §B 桥接 | Phase 2 实现 |
| G. 生命周期 | Design §2.4.2 内部约束) | Phase 2 实现 |
| 音频 / 录音 / 远端语音 | §B 桥接(音频相关) | Phase 3 待实现 |
| 分享 / 登录 | §B 桥接 + §E 回归 | Phase 4 待实现 |
| 定位 | §B 桥接 | Phase 5 待实现 |
| 子游戏 / 视频房间 | §B + §D 视频房间 | Phase 6 + 8 待实现 |
---
## 维护规则
- 每完成一个 Phase 子项 → 在对应章节补 / 勾验证步骤
- **不要把验证步骤复制到 Contract §10**Contract §10 是契约边界("必须长这样"),本文档是验证操作手册("怎么验证它长这样"),两份保持独立
- 验证发现的 bug → 修复后 commit 时在 message 引用本文档对应 checkbox 编号(如 "修 E.10 getphoneInfo 大小写错"