Files
spellforge/docs/plan/certification_checklist.md
T
2026-07-20 10:56:52 +08:00

9.4 KiB
Raw Blame History

平台发行认证清单 (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-05SettingsManager.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.txtuser://crash_log_prev.txtuser://run_a.json
user://run_b.jsonuser://endless_records.jsonuser://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-05endless_records.gd,对精确 JSON 字符串签名)
ST-41 读取时验证签名,签名失败则标记分数为「未验证」并拒绝提交 Steam 排行榜 S5 2026-06-05(验签失败 push_warning+返回空,实测篡改拒绝)
ST-42 Steam 排行榜提交时服务端异常(网络断开)不崩溃,本地缓存待下次提交 S6

ST-40 HMAC 实现指引(C-4 修复)

# 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.jsonuser://run_b.jsonuser://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 实现指引

# 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 & RatingCERO / PEGI / ESRB)在正确位置显示 P0
SW-07 游戏内截图功能不包含版权保护内容 P1

2.2 性能要求

编号 检查项 优先度
SW-10 掌机模式(720p)稳定 30fpsSwitch 性能下限) P0
SW-11 内存使用峰值 < 2.5GBSwitch 总 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 验证