# 平台发行认证清单 (Platform Certification Checklist)

> **用途**：列出 Steam / Nintendo Switch 两个目标平台的强制通过项，并映射到对应开发切片。  
> 每项完成后在 `[ ]` 处打勾，并记录验证者与日期。  
> **规则**：所有 P0 项必须在 S6 发布前全部完成，P1 项视平台要求决定是否阻断发布。

---

## 一、Steam (PC) 必须项

### 1.1 隐私与数据合规（EU GDPR / Steam 要求）

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-01 | 游戏不收集任何可识别玩家身份的数据（无账号系统、无上报遥测） | S2 | ⬜ |
| ST-02 | 崩溃日志（`user://crash_log.txt`）不包含 Steam ID / 机器码等 PII | S2 | ⬜ |
| ST-03 | 设置菜单提供「清除所有本地数据」选项（删除 `user://` 下所有文件） | S6 | ✅ 2026-06-05（`SettingsManager.clear_all_local_data`，二次确认；实测删除 save_data/endless_records/run_a·b/runs）|
| ST-04 | Steam 商店页包含隐私政策链接（即使只写"本游戏不收集任何数据"） | S6 前置 | ⬜ |
| ST-05 | 如启用 opt-in 崩溃上报，玩家首次启动须明确同意 | S6（可选）| ⬜ |

> **ST-03 实现指引**：  
> `SettingsManager.clear_all_local_data()` 遍历删除：  
> `user://crash_log.txt`、`user://crash_log_prev.txt`、`user://run_a.json`、  
> `user://run_b.json`、`user://endless_records.json`、`user://save_data.json`、  
> `user://runs/`（截图目录）。调用后重置 `ProfileManager` 单例状态并返回主菜单。

### 1.2 Steam 成就 (Achievements)

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-10 | Steam API 初始化成功（`Steam.init()` 返回 OK，或使用 GodotSteam 插件） | S6 | ⬜ |
| ST-11 | 所有成就在 Steamworks 后台已定义并与 EventBus 钩子对应 | S6 | ⬜ |
| ST-12 | 离线模式下成就解锁不崩溃（本地缓存后联网再同步） | S6 | ⬜ |
| ST-13 | 成就解锁调用幂等（重复触发不报错、不重复计数） | S6 | ⬜ |

> **EventBus 成就钩子**：`EventID.ACHIEVEMENT_UNLOCKED` **固定为 ID `18`**（负载：`{ "achievement_id": String }`），权威定义见 **`technical/implementation_plan.md` §2.1** 事件目录与 `event_ids.gd` 常量。  
> `AchievementManager` 订阅此事件，调用 `Steam.set_achievement(achievement_id)` + `Steam.store_stats()`（须幂等，见 ST-13）。

### 1.3 Steam 输入与手柄

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-20 | 手柄（Xbox / PS / Switch Pro）基本功能可用 | S1 | ⬜ |
| ST-21 | 手柄连接/断开时游戏不崩溃，自动切换输入方案 | S4 | ⬜ |
| ST-22 | 按键提示图标根据当前设备自动切换（Xbox A / PS × / 键盘 Space） | S6 | ⬜ |

### 1.4 Steam Deck 兼容性

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-30 | 游戏在 Steam Deck 默认分辨率（1280×800）下可正常显示 | S6 | ⬜ |
| ST-31 | 所有 UI 文字在 Steam Deck 屏幕上可读（最小字号 ≥ 16px）| S6 | ⬜ |
| ST-32 | Steam Deck 触摸屏操作不引起误触崩溃 | S6 | ⬜ |
| ST-33 | Proton 兼容性验证（在 SteamOS 下通过 Proton 层运行测试） | S6 | ⬜ |

### 1.5 排行榜数据完整性

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-40 | 本地 `endless_records.json` 写入时附加 HMAC-SHA256 签名 | S5 | ✅ 2026-06-05（`endless_records.gd`，对精确 JSON 字符串签名）|
| ST-41 | 读取时验证签名，签名失败则标记分数为「未验证」并拒绝提交 Steam 排行榜 | S5 | ✅ 2026-06-05（验签失败 `push_warning`+返回空，实测篡改拒绝）|
| ST-42 | Steam 排行榜提交时服务端异常（网络断开）不崩溃，本地缓存待下次提交 | S6 | ⬜ |

> **ST-40 HMAC 实现指引（C-4 修复）**：  
> ```gdscript
> # endless_records_manager.gd
> # HMAC Key 存于 GDScript 编译常量（不含在存档内），对抗普通玩家手动编辑
> # 注意：本地 HMAC 无法对抗逆向工程，主要目的是防止随意手改
> const _HMAC_KEY: PackedByteArray = [0xA3, 0x7F, 0x2C, ...]  # S5 前生成 32 字节随机密钥
>
> func _sign(data: String) -> String:
>     var crypto := Crypto.new()
>     var hmac := crypto.hmac_digest(HashingContext.HASH_SHA256, _HMAC_KEY,
>                                     data.to_utf8_buffer())
>     return hmac.hex_encode()
>
> func save_records(records: Array) -> void:
>     var payload := JSON.stringify(records)
>     var signed := { "data": records, "sig": _sign(payload) }
>     FileAccess.open("user://endless_records.json", FileAccess.WRITE
>                     ).store_string(JSON.stringify(signed))
>
> func load_records() -> Array:
>     if not FileAccess.file_exists("user://endless_records.json"): return []
>     var raw := JSON.parse_string(
>                    FileAccess.open("user://endless_records.json",
>                                    FileAccess.READ).get_as_text())
>     if not raw is Dictionary: return []
>     var expected := _sign(JSON.stringify(raw.get("data", [])))
>     if raw.get("sig", "") != expected:
>         push_warning("EndlessRecords: 签名验证失败，分数不可信")
>         return []   # 拒绝载入被篡改的数据
>     return raw.get("data", [])
> ```

---

### 1.6 Steam 云存档 (Steam Cloud Save)

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| ST-50 | `ProfileManager.save_run()` / `save_settings()` 通过 `Steam.beginFileWriteBatch()` / `Steam.endFileWriteBatch()` 写入，确保 Steam Cloud 同步 | S6 | ⬜ |
| ST-51 | `user://run_a.json`、`user://run_b.json`、`user://save_data.json` 均在 Steamworks 后台 Remote Storage 中启用 | S6 前置 | ⬜ |
| ST-52 | 跨设备（PC → Steam Deck）存档同步测试：PC 端存档可在 Steam Deck 读取并继续游戏 | S6 | ⬜ |
| ST-53 | Steam Cloud 配额不超标（Steam 默认 100MB/游戏；所有存档文件估算总量 < 1MB） | S6 | ⬜ |

> **ST-50 实现指引**：
> ```gdscript
> # profile_manager.gd
> func save_run(data: Dictionary) -> void:
>     if Steam.is_steam_running():
>         Steam.beginFileWriteBatch()   # GodotSteam: 开始批量写入，触发 Steam Cloud 同步
>     var which := get_int("run_write_slot", 0)
>     var path  := _SLOT_A if which == 0 else _SLOT_B
>     var f := FileAccess.open(path, FileAccess.WRITE)
>     if f:
>         f.store_string(JSON.stringify(data))
>     if Steam.is_steam_running():
>         Steam.endFileWriteBatch()     # 结束批量写入，触发 Steam Cloud 上传
>     set_int("run_write_slot", 1 - which)
> ```
> **注意**：`beginFileWriteBatch` / `endFileWriteBatch` 仅在 `Steam.is_steam_running()` 时调用；离线模式下正常写本地文件，不崩溃。`Steam.is_steam_running()` 需 GodotSteam 插件支持。

---

## 二、Nintendo Switch（未来扩展）

> 以下为 Nintendo Lotcheck 最常见的阻断项，仅在立项 Switch 移植时启用。

### 2.1 系统级强制项

| 编号 | 检查项 | 优先度 |
| :--- | :--- | :--- |
| SW-01 | 游戏内按钮提示使用 Switch 图标（A/B/X/Y），不得显示 Xbox 图标 | P0 |
| SW-02 | Joy-Con 横持模式不崩溃（若不支持须在商品页说明） | P0 |
| SW-03 | 游戏可从睡眠状态恢复（`NOTIFICATION_APPLICATION_PAUSED` 处理） | P0 |
| SW-04 | 存档容量不超过 Switch 游戏卡限制（存档 < 32MB） | P0 |
| SW-05 | 不在联机功能中收集 Nintendo Account 信息 | P0 |
| SW-06 | Logo & Rating（CERO / PEGI / ESRB）在正确位置显示 | P0 |
| SW-07 | 游戏内截图功能不包含版权保护内容 | P1 |

### 2.2 性能要求

| 编号 | 检查项 | 优先度 |
| :--- | :--- | :--- |
| SW-10 | 掌机模式（720p）稳定 30fps（Switch 性能下限） | P0 |
| SW-11 | 内存使用峰值 < 2.5GB（Switch 总 RAM 4GB 共享系统/GPU） | P0 |
| SW-12 | 存档读写不阻塞主线程超过 100ms | P0 |

---

## 三、无障碍合规（Accessibility — 跨平台必须项）

> 参见 `architecture_design.md ADR-C1` 获取完整技术实现规范。

| 编号 | 检查项 | 切片 | 状态 |
| :--- | :--- | :--- | :--- |
| AC-01 | 元素标签同时用颜色 + 形状图标标识（色盲玩家可区分） | S3 | ⬜ |
| AC-02 | 所有 UI 字体支持缩放（50%~150%，设置项） | S4 | ⬜ |
| AC-03 | 高对比度模式（黑底白字选项）| S6 | ⬜ |
| AC-04 | 所有 VFX 提供「减少闪光」选项（防光敏性癫痫）| S6 | ⬜ |
| AC-05 | 游戏内所有音效有对应视觉提示（不依赖纯音频传达关键信息） | S6 | ⬜ |

---

## 四、评级申报

| 平台 | 机构 | 提交时机 | 状态 |
| :--- | :--- | :--- | :--- |
| Steam | IARC（自评系统，Steam 自动申报） | S6 发布前 | ⬜ |
| Steam（欧洲） | PEGI 自评或正式申请 | S6 发布前 | ⬜ |
| Switch | CERO（日本）/ PEGI / ESRB（需提前约 8 周提交） | 若立项 Switch | ⬜ |

---

## 五、发布前最终清单

| 编号 | 检查项 | 完成标志 |
| :--- | :--- | :--- |
| PRE-01 | Steam 商店页（截图 × 5、宣传片、描述文字）审核通过 | Steam 后台审核 OK |
| PRE-02 | 版本号 `1.0.0` 打 git tag，构建哈希记录在 `CHANGELOG.md` | git tag v1.0.0 |
| PRE-03 | 所有 P0 认证项状态为 ✅ | 本文档所有 P0 行 ✅ |
| PRE-04 | 发布分支通过完整 Wave 1~20 通关测试（无崩溃） | CI 通关测试日志 |
| PRE-05 | `crash_reporter.gd` 已注册为首位 Autoload，日志路径可写 | project.godot 验证 |
