# 平台发行认证清单 (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 验证 |