feat: 收敛玩法运行时配置并加入故障恢复

This commit is contained in:
2026-04-01 13:04:26 +08:00
parent 1635a11780
commit 3ef841ecc7
73 changed files with 8820 additions and 2122 deletions

View File

@@ -0,0 +1,239 @@
# 故障恢复机制
本文档用于说明当前客户端在“游戏进行中非正常退出”场景下的恢复机制。
目标:
- 明确当前恢复能力边界
- 说明恢复快照保存什么、不保存什么
- 说明恢复流程、清理流程和测试方法
---
## 1. 设计原则
当前恢复机制采用:
`轻量快照恢复,而不是页面状态回放`
原则如下:
- 只恢复核心运行态
- 不恢复瞬时 UI
- 恢复后由规则层和展示层重新计算派生状态
- 恢复失败时允许直接降级回正常开局
- 不为了恢复体验牺牲主流程性能
---
## 2. 当前恢复范围
### 2.1 会恢复的内容
- 当前配置入口
- 当前对局核心状态
- 已完成点 / 已跳过点
- 当前分数
- 开赛时间 / 结束时间 / 结束原因
- 模式状态 `modeState`
- 遥测累计值
- 最后 GPS 点与精度
- 地图基础视口
- GPS 锁定状态
### 2.2 不恢复的内容
- 白色内容卡
- 答题卡中间态
- 黑底引导提示条
- 彩色短反馈条
- 待查看内容入口
- 临时动画和过渡效果
- 各类计时器、定时器、提示队列
说明:
这些内容都属于派生 UI 或瞬时体验,恢复后会重新按当前运行态计算,不直接持久化。
---
## 3. 快照结构
当前恢复快照定义在:
- [sessionRecovery.ts](D:/dev/cmr-mini/miniprogram/game/core/sessionRecovery.ts)
结构分为 4 块:
1. 快照元信息
- `schemaVersion`
- `savedAt`
2. 配置身份
- `launchEnvelope`
- `configAppId`
- `configVersion`
3. 对局核心状态
- `gameState`
4. 运行态附属状态
- `telemetry`
- `viewport`
- `currentGpsPoint`
- `currentGpsAccuracyMeters`
- `currentGpsInsideMap`
- `bonusScore`
- `quizCorrectCount`
- `quizWrongCount`
- `quizTimeoutCount`
---
## 4. 保存时机
当前 V1 保存时机如下:
- 对局进入 `running` 时先保存一次
- 对局进行中按低频节流定时保存
- 页面 `onHide` 时保存一次
- 页面 `onUnload` 时保存一次
当前默认节流周期:
- `5`
说明:
- 不做每帧保存
- 不在所有 GPS 更新时保存
- 只做低频检查点式持久化
---
## 5. 恢复流程
当前恢复流程如下:
1. 页面进入时先解析启动入口
2. 如果没有显式新启动参数,则检查是否存在恢复快照
3. 若存在恢复快照,则优先使用快照中的 `launchEnvelope`
4. 配置加载完成后校验快照身份
5. 若快照属于当前配置,则弹出“继续恢复 / 放弃”确认
6. 用户确认恢复后:
- 先恢复系统设置运行态
- 再恢复 `GameRuntime` 核心状态
- 再恢复 `TelemetryRuntime`
- 再恢复地图视口与 GPS 锁定态
- 最后重新计算 HUD、按钮和提示
恢复相关落点:
- [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts)
- [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts)
- [gameRuntime.ts](D:/dev/cmr-mini/miniprogram/game/core/gameRuntime.ts)
- [telemetryRuntime.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryRuntime.ts)
---
## 6. 清理规则
以下情况会清除恢复快照:
- 正常完赛
- 超时结束
- 主动退出
- 用户在恢复确认框中选择“放弃”
- 快照配置身份与当前配置不匹配
- 恢复失败
说明:
恢复快照只服务“未正常结束的进行中对局”。
---
## 7. 当前实现边界
当前 V1 的明确边界是:
- 支持续局
- 不支持恢复答题进行中
- 不支持恢复内容卡浏览进行中
- 不支持恢复弹层堆栈
- 不支持恢复临时 FX 状态
这不是缺陷,而是当前版本的刻意收敛。
---
## 8. 性能策略
当前恢复机制的性能策略如下:
- 快照只存小而必要的结构
- 不重复存整份远端配置 JSON
- 不存派生展示状态
- 不做高频写入
- 恢复后尽量走现有规则重算链
所以这套恢复机制更接近:
`保存运行核心状态 + 重新生成界面`
而不是:
`保存整个页面现场`
---
## 9. 测试建议
### 9.1 基础恢复
1. 开一局并至少完成 1 个点
2. 不正常结束,直接退后台并杀掉程序
3. 再次进入地图页
4. 确认出现恢复提示
5. 点击“继续恢复”
6. 确认分数、已完成点、计时和地图状态都能续上
### 9.2 放弃恢复
1. 重复上面步骤进入恢复提示
2. 点击“放弃”
3. 确认回到正常初始状态
4. 再次进入时不应重复提示
### 9.3 正常结束清理
分别验证:
- 正常打终点结束
- 主动退出
- 超时结束
结果都应为:
- 不再保留恢复快照
- 再次进入不再提示恢复
---
## 10. 后续演进方向
后续如果要继续增强,建议顺序如下:
1. 先补恢复链的 smoke tests
2. 再考虑恢复更多遥测累计细节
3. 最后才考虑是否恢复答题态或内容态
不建议一开始就追求全量 UI 恢复。
---
## 11. 一句话结论
当前故障恢复机制的定位是:
**保证玩家在异常退出后可以继续当前对局,但不承担恢复所有临时界面状态。**

View File

@@ -0,0 +1,380 @@
# 游戏规则架构
本文档用于说明当前项目中“游戏规则”在文档、配置文件、样例 JSON、解析代码和运行时规则引擎之间的实际组织方式。
目标:
- 说明规则设计从哪里开始
- 说明公共配置和玩法配置如何分层
- 说明样例配置、代码解析、运行时规则如何对应
- 作为后续后台配置管理的架构基线
---
## 1. 总体原则
当前项目采用的是“文档定义规则,配置承载规则,代码解析配置,规则引擎执行配置”的结构。
可以概括为四句话:
1. 玩法规则先在文档里定义
2. 规则通过 JSON 配置文件表达
3. 客户端解析配置生成运行态定义
4. 规则引擎按运行态定义驱动游戏流程
也就是说:
**文档是设计源头,配置是规则载体,代码是执行层。**
如果换一种更偏运行时的说法:
- `MapEngine` 和页面壳子是骨架
- 默认值层和规则层决定骨架怎么动
- 配置像神经系统,把活动差异传进运行时
- 遥测和反馈层负责把玩家状态再回流到界面
---
## 2. 当前分层结构
当前规则体系分成 6 层。
### 2.1 公共规则层
位置:
- [全局规则与配置维度清单](D:/dev/cmr-mini/doc/config/全局规则与配置维度清单.md)
- [配置选项字典](D:/dev/cmr-mini/doc/config/配置选项字典.md)
- [当前最全配置模板](D:/dev/cmr-mini/doc/config/当前最全配置模板.md)
作用:
- 定义系统共用能力有哪些
- 定义公共字段、可选项和默认值
- 作为所有玩法的公共配置全集
这一层回答的是:
- 系统支持哪些规则块
- 系统有哪些字段
- 这些字段默认怎么工作
### 2.2 玩法设计层
位置:
- [程序默认规则基线](D:/dev/cmr-mini/doc/gameplay/程序默认规则基线.md)
- [运行时编译层总表](D:/dev/cmr-mini/doc/gameplay/运行时编译层总表.md)
- [玩法设计文档模板](D:/dev/cmr-mini/doc/gameplay/玩法设计文档模板.md)
- [玩法构想方案](D:/dev/cmr-mini/doc/gameplay/玩法构想方案.md)
- `doc/games/<游戏名称>/规则说明文档.md`
- `doc/games/<游戏名称>/游戏说明文档.md`
作用:
- 定义客户端自身应该内建的默认行为
- 定义某个玩法怎么玩
- 定义局流程、状态机、计分、胜负、表现
- 明确该玩法选用了哪些公共规则块
这一层回答的是:
- 这个玩法的目标是什么
- 这个玩法如何开始、推进、结束
- 这个玩法和系统公共能力的关系是什么
### 2.3 玩法配置层
位置:
- `doc/games/<游戏名称>/最小配置模板.md`
- `doc/games/<游戏名称>/最大配置模板.md`
- `doc/games/<游戏名称>/全局配置项.md`
- `doc/games/<游戏名称>/游戏配置项.md`
- `event/*.json`
作用:
- 把玩法规则翻译为可执行配置
- 明确这个玩法实际使用的公共字段子集
- 给出最小可跑样例和较完整样例
这一层回答的是:
- 规则具体落到哪些字段
- 哪些字段是系统默认
- 哪些字段由玩法覆盖
- 当前联调跑的是哪份样例配置
### 2.4 配置解析层
位置:
- [remoteMapConfig.ts](D:/dev/cmr-mini/miniprogram/utils/remoteMapConfig.ts)
- [courseToGameDefinition.ts](D:/dev/cmr-mini/miniprogram/game/content/courseToGameDefinition.ts)
作用:
- 读取远端或本地 JSON
- 补齐模式默认值
- 归一化为运行时使用的定义对象
这一层回答的是:
- 配置进入程序后如何被解释
- 系统默认值何时注入
- 不同玩法模式如何做差异化默认处理
### 2.5 运行时默认与设置层
位置:
- [gameModeDefaults.ts](D:/dev/cmr-mini/miniprogram/game/core/gameModeDefaults.ts)
- [systemSettingsState.ts](D:/dev/cmr-mini/miniprogram/game/core/systemSettingsState.ts)
作用:
- 统一维护玩法默认值
- 统一维护设置页默认值
- 明确哪些值持久化,哪些锁态不持久化
- 为页面层和规则层提供同一套默认基线
补充约束:
- 设置值允许玩家修改并持久化
- 设置锁态只由系统默认值和活动配置决定
- 锁态不持久化,也不允许玩家在页面里切换
- 锁态只在当前游戏配置运行生命周期内生效;本局结束或主动退出后失效
### 2.6 运行时编译层
位置:
- [runtimeProfileCompiler.ts](D:/dev/cmr-mini/miniprogram/game/core/runtimeProfileCompiler.ts)
- [运行时编译层总表](D:/dev/cmr-mini/doc/gameplay/运行时编译层总表.md)
- [故障恢复机制](D:/dev/cmr-mini/doc/gameplay/故障恢复机制.md)
作用:
- 统一合并系统默认值、玩法默认值、活动配置和玩家设置
- 产出页面、引擎和规则层可直接消费的运行时 profile
- 逐步替代页面和引擎中分散的默认值判断
当前进度:
- `settings / telemetry / feedback` 已开始走编译层入口
- 设置页大部分引擎型设置项已改成统一更新玩家设置,再由编译层回灌引擎
- `settings` 当前已通过 `MapEngine.applyCompiledSettingsProfile(...)` 统一落地,不再由页面层散着逐项推送
- `presentation` 和一部分 `game` 字段也已开始接入
- `map` 也已开始接入地图底座和配置状态这组基础字段
- `MapEngine.applyRemoteMapConfig(...)` 已开始退回为原始场地与资源入口
这一层回答的是:
- 程序默认值放在哪里
- 玩家本地设置如何和系统默认值合并
- 锁态为什么不能从历史缓存恢复
- 为什么页面、引擎、规则不应再直接各自读原始配置
### 2.7 故障恢复层
位置:
- [sessionRecovery.ts](D:/dev/cmr-mini/miniprogram/game/core/sessionRecovery.ts)
- [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts)
- [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts)
作用:
- 在对局进行中低频持久化轻量恢复快照
- 在异常退出后提供“继续上一局 / 放弃上一局”入口
- 只恢复核心运行态,不恢复瞬时 UI
这一层回答的是:
- 非正常退出后如何继续比赛
- 为什么恢复只保留核心赛局状态
- 为什么不尝试恢复白卡、答题卡和动效中间态
### 2.8 运行时规则层
位置:
- [classicSequentialRule.ts](D:/dev/cmr-mini/miniprogram/game/rules/classicSequentialRule.ts)
- [scoreORule.ts](D:/dev/cmr-mini/miniprogram/game/rules/scoreORule.ts)
- [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts)
- [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts)
- [resultSummary.ts](D:/dev/cmr-mini/miniprogram/game/result/resultSummary.ts)
- [telemetryRuntime.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryRuntime.ts)
- [playerTelemetryProfile.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/playerTelemetryProfile.ts)
作用:
- 根据运行态定义执行状态流转
- 响应 GPS、点击、打点、答题、结束等事件
- 产出 HUD、反馈、结果页等最终体验
- 明确区分可打目标、引导目标、HUD 目标和展示高亮目标
这一层回答的是:
- 玩家在游戏中的每一步如何被判定
- 积分、答题、结束如何结算
- 页面上显示什么、什么时候显示
- 活动遥测默认值和玩家身体数据如何合并
---
## 3. 规则从设计到运行的链路
当前实际链路如下:
1. 在玩法文档里定义默认规则
2. 在公共配置文档里定义字段与默认值
3. 在玩法目录里确定该玩法使用哪些字段
4.`event/*.json` 中写出样例配置
5. 由配置解析层读取 JSON 并注入模式默认值
6. 由默认值与设置层补齐系统默认行为和玩家本地值
7. 由规则引擎和 `MapEngine` 执行游戏逻辑
8. 由结果页和 HUD 层展示最终状态
其中遥测相关链路补充为:
`系统默认值 -> 活动 telemetry 配置 -> 玩家线上身体数据 -> TelemetryRuntime -> HUD 第 2 页`
设置相关链路补充为:
`系统设置默认值 -> 玩家本地持久化值 -> 当前运行时锁态 -> map.ts / MapEngine`
也可以简化成:
`规则文档 -> 配置文档 -> 样例 JSON -> 配置解析 -> 规则引擎 -> 运行结果`
当前已补一层轻量 smoke tests用于卡住高风险回归
- 配置继承
- 起点 / 终点规则
- 积分赛自由打点
- 锁态生命周期
- 超时结束
命令:
- `npm run test:runtime-smoke`
---
## 4. 当前目录分工
### 4.1 `doc/config`
职责:
- 公共配置全集
- 公共字段字典
- 公共模板
- 发布和后台管理方案
这一层不应该存放某个玩法自己的专属规则文档。
### 4.2 `doc/gameplay`
职责:
- 通用玩法方法论
- 玩法设计模板
- 玩法构想和规则架构说明
这一层不应该长期存放具体玩法的正式规则文档。
### 4.3 `doc/games/<游戏名称>`
职责:
- 某个具体玩法的完整文档集合
- 玩法说明
- 规则说明
- 最小 / 最大模板
- 全局配置项子集
- 游戏配置项子集
这一层是具体玩法的唯一正式维护入口。
### 4.4 `event/*.json`
职责:
- 当前可运行样例配置
- 联调、验证、远端发布的直接输入
这一层必须和玩法目录中的文档保持一致。
---
## 5. 当前已落地的玩法实例
### 5.1 顺序打点
正式文档入口:
- [顺序打点/游戏说明文档](D:/dev/cmr-mini/doc/games/顺序打点/游戏说明文档.md)
- [顺序打点/规则说明文档](D:/dev/cmr-mini/doc/games/顺序打点/规则说明文档.md)
- [顺序打点/最小配置模板](D:/dev/cmr-mini/doc/games/顺序打点/最小配置模板.md)
- [顺序打点/最大配置模板](D:/dev/cmr-mini/doc/games/顺序打点/最大配置模板.md)
- [顺序打点/全局配置项](D:/dev/cmr-mini/doc/games/顺序打点/全局配置项.md)
- [顺序打点/游戏配置项](D:/dev/cmr-mini/doc/games/顺序打点/游戏配置项.md)
- [classic-sequential.json](D:/dev/cmr-mini/event/classic-sequential.json)
### 5.2 积分赛
正式文档入口:
- [积分赛/游戏说明文档](D:/dev/cmr-mini/doc/games/积分赛/游戏说明文档.md)
- [积分赛/规则说明文档](D:/dev/cmr-mini/doc/games/积分赛/规则说明文档.md)
- [积分赛/最小配置模板](D:/dev/cmr-mini/doc/games/积分赛/最小配置模板.md)
- [积分赛/最大配置模板](D:/dev/cmr-mini/doc/games/积分赛/最大配置模板.md)
- [积分赛/全局配置项](D:/dev/cmr-mini/doc/games/积分赛/全局配置项.md)
- [积分赛/游戏配置项](D:/dev/cmr-mini/doc/games/积分赛/游戏配置项.md)
- [score-o.json](D:/dev/cmr-mini/event/score-o.json)
---
## 6. 文档与代码同步约定
后续每次规则变化,建议至少检查 4 层是否同步:
1. 公共配置文档是否要补字段或默认值
2. 对应玩法目录下的规则文档是否要改
3. 对应 `event/*.json` 样例是否要改
4. 配置解析和规则引擎是否要改
如果只改了其中一层,就容易出现文档、配置、代码不一致。
建议统一按下面顺序维护:
1. 先改规则文档
2. 再改配置文档和样例 JSON
3. 再改解析代码和规则代码
4. 最后回头核对结果页、HUD 和提示是否与文档一致
---
## 7. 和后续后台管理的关系
当前本地项目里,规则主要由文档和样例 JSON 驱动。
后续接后台后,分工仍然建议保持不变:
- 玩法文档负责定义规则
- 公共配置文档负责定义字段
- 后台负责编辑、版本、校验、装配、发布
- 客户端继续消费静态 JSON
也就是说,后台是管理工具,不是规则源头。
---
## 8. 一句话总结
当前这套实际架构可以概括为:
**`doc/config` 管公共规则全集,`doc/games/<游戏名称>` 管玩法规则与配置子集,`event/*.json` 管可运行样例,客户端解析配置后交给规则引擎执行,并由轻量恢复层处理异常退出后的续局。**

View File

@@ -0,0 +1,411 @@
# 玩法设计文档模板
本文档用于定义后续所有玩法设计文档的**统一写法**,保证玩法规则、全局规则块、配置落点和最小样例能够一起沉淀,为后续 JSON 配置管理和后台装配提供稳定输入。
目标:
- 统一玩法设计文档结构
- 避免只写“玩法创意”,不写“配置落点”
- 让后续玩法都能自然接到配置文件和后台管理方案
说明:
- 本文档是模板,不是具体玩法规则
- 后期 JSON 配置管理以专门后台方案承接
- 本文档负责沉淀“设计输入”
- 后台方案负责沉淀“编辑、版本、发布、装配”
建议配合阅读:
- [全局规则与配置维度清单](D:/dev/cmr-mini/doc/config/全局规则与配置维度清单.md)
- [配置选项字典](D:/dev/cmr-mini/doc/config/配置选项字典.md)
- [后台配置管理方案V2](D:/dev/cmr-mini/doc/config/后台配置管理方案V2.md)
---
## 1. 使用方式
后续每新增一个玩法,都建议新建一份玩法文档,并按本模板完整填写。
推荐存放方式:
- `doc/games/<游戏名称>/游戏说明文档.md`
- `doc/games/<游戏名称>/规则说明文档.md`
- `doc/games/<游戏名称>/最小配置模板.md`
- `doc/games/<游戏名称>/最大配置模板.md`
- `doc/games/<游戏名称>/全局配置项.md`
- `doc/games/<游戏名称>/游戏配置项.md`
如果玩法还处于发散阶段,可以先写“构想方案”;
一旦进入可开发阶段,就应该补齐本模板里的正式章节。
---
## 2. 标准章节清单
后续每个玩法设计文档,建议至少包含以下章节:
1. 文档定位
2. 一句话规则
3. 设计目标
4. 适用范围
5. 局流程
6. 核心对象模型
7. 判定与状态机
8. 计分与胜负规则
9. 全局规则块选型
10. 配置落点
11. 最小可跑配置样例
12. 验收清单
13. 后续扩展点
---
## 3. 模板正文
以下为推荐模板正文,可直接复制新建玩法文档后填写。
---
# `玩法名称`
## 1. 文档定位
本文档用于定义 `玩法模式标识` 的正式规则、默认行为、配置落点和最小可跑样例。
当前阶段目标:
- 说明玩法怎么玩
- 说明系统如何判定
- 说明配置文件如何承载
- 说明哪些沿用系统默认,哪些需要玩法覆盖
---
## 2. 一句话规则
请用一句话写清:
- 玩家要做什么
- 怎样推进
- 怎样结束
- 怎样赢 / 怎样结算
示例:
> 玩家需要按顺序完成控制点打卡,允许跳点;普通点打卡后回答限时数学题获取奖励分,打完终点后结算成绩。
---
## 3. 设计目标
建议至少说明:
- 这个玩法的核心乐趣是什么
- 它和已有玩法的主要差异是什么
- 它优先验证哪种系统能力
建议回答:
- 是否偏竞技
- 是否偏探索
- 是否偏轻量复玩
- 是否偏实时压力
---
## 4. 适用范围
建议至少说明:
- 适用于单人还是多人
- 是否依赖真实 GPS
- 是否依赖模拟器
- 是否依赖心率等遥测能力
- 是否依赖路线型场地还是对象集型场地
建议落成明确语句:
- 支持:`单人 / 多人`
- 输入依赖:`GPS / 心率 / 模拟输入`
- 场地类型:`course / control-set / region-set / object-set`
---
## 5. 局流程
建议按时间顺序写完整流程:
### 5.1 进入游戏
- 初始看到什么
- 哪些对象显示,哪些隐藏
- 是否立即开始计时
- 是否先弹提示
### 5.2 正式开始
- 什么事件触发正式开局
- 是否初始化数据
- 是否显示全图 / 全路线
- 是否弹开始提示
### 5.3 进行中推进
- 玩家如何推进
- 当前目标如何变化
- 是否允许跳点 / 切目标 / 自由选点
- 进行中有哪些关键反馈
### 5.4 结束
- 什么条件触发结束
- 是否立即停止计时
- 是否弹结束提示
- 是否进入结算页
---
## 6. 核心对象模型
建议列清玩法运行依赖的对象类型。
示例格式:
| 对象类型 | 作用 | 是否必需 | 备注 |
| --- | --- | --- | --- |
| `start` | 起始触发点 | 是 | 用于正式开赛 |
| `control` | 普通推进目标 | 是 | 可按玩法扩展属性 |
| `finish` | 结束点 | 视玩法而定 | 用于结束比赛 |
| `bonus` | 奖励对象 | 否 | 可选 |
| `danger-zone` | 危险区域 | 否 | 可选 |
还建议说明:
- 对象来源于 `KML` 还是运行时生成
- 对象是静态的还是动态变化的
- 对象是否有状态切换
---
## 7. 判定与状态机
### 7.1 局状态
建议明确列出局状态,例如:
1. `ready`
2. `running`
3. `paused`
4. `finished`
5. `settled`
### 7.2 关键判定
建议逐条写清:
- 打点成功判定
- 跳点判定
- 得分判定
- 失败判定
- 完赛判定
### 7.3 状态切换条件
建议写成表:
| 当前状态 | 触发事件 | 下一状态 | 备注 |
| --- | --- | --- | --- |
| `ready` | 起点打卡成功 | `running` | 正式开始计时 |
| `running` | 结束条件满足且终点打卡 | `finished` | 停止计时 |
| `finished` | 关闭结束提示 | `settled` | 进入结算页 |
---
## 8. 计分与胜负规则
建议明确:
- 基础分怎么来
- 奖励分怎么来
- 扣分怎么来
- 跳过点如何处理
- 最终成绩显示哪些指标
推荐格式:
| 规则项 | 说明 | 默认值 | 备注 |
| --- | --- | --- | --- |
| 基础分 | 成功打点获得 | `1` | 示例 |
| 奖励分 | 答题答对获得 | `1` | 示例 |
| 跳过点得分 | 是否得分 | `0` | 示例 |
| 结算指标 | 总用时 / 总分 / 成功点数 | - | 示例 |
如果玩法没有积分,也要写清:
- 胜负依据是什么
- 排名依据是什么
- 是否只有完赛状态
---
## 9. 全局规则块选型
本节必须回答“这个玩法用了哪些全局能力块,以及默认选型是什么”。
建议按下表填写:
| 规则块 | 是否使用 | 选型 / 配置方向 | 默认值策略 | 备注 |
| --- | --- | --- | --- | --- |
| 地图底座 | 是 | 自定义地图 + KML | 沿用系统默认 | |
| 点位表现 | 是 | 传统定向紫红系 | 可局部覆盖 | |
| 腿线表现 | 是 | `classic-leg` + 动效 | 顺序类沿用默认 | |
| 轨迹表现 | 是 | `full` / `tail` / `none` | 玩法指定 | |
| 定位点表现 | 是 | `beacon` / `dot` 等 | 沿用系统默认或玩法覆盖 | |
| 引导显示 | 是 | 是否显示腿线、是否允许选点 | 玩法指定 | |
| 可见性策略 | 是 | 开局隐藏 / 起点后全显 | 玩法指定 | |
| 内容体验 | 否 / 是 | 原生 / H5 / 不启用 | 玩法指定 | |
| 反馈系统 | 是 | 音效 / 震动 / UI 档位 | 玩法指定 | |
| 遥测参数 | 否 / 是 | 是否用心率 | 沿用默认或玩法覆盖 | |
| 调试能力 | 是 | 是否开放模拟器 | 开发期指定 | |
本节的目的不是重复字段字典,而是明确:
- 这个玩法到底用了哪些公共块
- 哪些沿用默认
- 哪些要做玩法覆盖
---
## 10. 配置落点
本节用于把规则翻译成配置结构。
建议写成表:
| 设计项 | 配置落点 | 是否已有字段 | 默认值 | 是否建议后台表单化 |
| --- | --- | --- | --- | --- |
| 起点后显示全路线 | `game.visibility.revealFullPlayfieldAfterStartPunch` | 是 | `true` | 是 |
| 跳点开关 | `game.sequence.skip.enabled` | 是 | `true` | 是 |
| 答题倒计时 | `待补字段` | 否 | `10` | 先走 JSON 区 |
| 目标点样式 | `game.presentation.sequential.controls.current` | 是 | 玩法指定 | 可后续表单化 |
这里建议把字段分成两类:
### 10.1 稳定字段
适合后续做后台正式表单:
- 模式
- 半径
- 是否必须起点 / 终点
- 是否显示腿线
- 是否显示轨迹
- 是否允许跳点
- 是否启用内容体验
### 10.2 易变字段
更适合先放 JSON 编辑区:
- 实验性计分细则
- 特殊答题规则
- 视觉实验参数
- 单点特殊表现
- 复杂状态映射
本节要和后续后台管理方案衔接,避免字段落点混乱。
---
## 11. 最小可跑配置样例
本节建议给出一个最小可跑 JSON 样例。
要求:
- 只保留当前玩法最关键字段
- 能作为联调和测试入口
- 和规则说明保持一致
建议结构:
```json
{
"schemaVersion": "1",
"version": "2026.03.31",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
如果已有运行中的 `event/*.json`,应在本节明确引用对应样例。
---
## 12. 验收清单
建议至少覆盖:
| 验收项 | 是否通过 | 备注 |
| --- | --- | --- |
| 开局流程与文档一致 | | |
| 打点判定与文档一致 | | |
| 计分规则与文档一致 | | |
| 点位表现与文档一致 | | |
| 轨迹与定位点表现一致 | | |
| 结算页指标与文档一致 | | |
| 样例配置可跑通 | | |
| 文档与配置字段口径一致 | | |
---
## 13. 后续扩展点
本节建议专门写:
- 哪些能力当前先不做
- 哪些字段未来可能配置化
- 哪些地方后续会交给后台表单化管理
示例:
- 第一阶段先固定答题倒计时为 `10` 秒,后续再配置化
- 第一阶段先固定基础分 / 奖励分,后续再补 `game.scoring` 细项
- 第一阶段样式先走 profile后续再细化到按状态表单配置
---
## 14. 文档产出要求
后续新增一个正式玩法文档时,建议至少同步产出以下内容:
1. 一份玩法规则文档
2. 一份对应最小样例配置
3. 如有新增公共能力,更新 [全局规则与配置维度清单](D:/dev/cmr-mini/doc/config/全局规则与配置维度清单.md)
4. 如有新增字段,更新 [配置选项字典](D:/dev/cmr-mini/doc/config/配置选项字典.md)
---
## 15. 和后台方案的关系
后期 JSON 配置文档建议通过专门后台管理,但要注意分工:
- 玩法文档:
负责定义规则、默认值、配置落点、最小样例
- 配置字典:
负责定义字段含义、可选项和默认值
- 后台方案:
负责对象、版本、校验、装配、发布
也就是说:
**玩法文档是“设计源头”,后台系统是“管理和发布工具”。**
不要让后台方案反过来决定玩法规则结构。

View File

@@ -0,0 +1,439 @@
# 程序默认规则基线
本文档用于定义当前客户端在**不依赖活动配置细项**时,程序层应该内建的默认规则。
目标:
- 先把程序能力层的默认行为定住
- 让代码实现优先对齐这一套基线
- 等默认行为稳定后,再决定哪些能力值得开放成配置
说明:
- 本文档讲的是**程序默认规则**
- 它先于活动配置存在
- 它不讨论后台录入方式
- 它不追求一次把所有参数开放出去
- 当前这批玩法默认值已开始集中收口到 [gameModeDefaults.ts](D:/dev/cmr-mini/miniprogram/game/core/gameModeDefaults.ts)
- 设置页默认值与锁态已开始集中收口到 [systemSettingsState.ts](D:/dev/cmr-mini/miniprogram/game/core/systemSettingsState.ts)
---
## 1. 总体原则
当前规则收敛顺序固定为:
1. 先定程序默认能力
2. 再定玩法默认差异
3. 最后才开放活动配置覆盖
程序层默认规则要满足 3 个要求:
- 默认可玩:不给额外配置也能顺畅跑完一局
- 默认统一:相同类型行为在不同玩法下表达一致
- 默认克制:不把调试、测试、历史试验行为带进最小流程
补充一条设置页原则:
- 设置值和锁态分离管理,避免运行时状态被历史缓存污染
---
## 2. 程序默认层次
前台默认只保留 5 类可见反馈层。
同时,程序默认值分成两条并行基线:
- 对局规则默认值
- 系统设置默认值
### 2.1 黑底引导提示条
职责:
- 只负责告诉玩家“下一步该做什么”
规则:
- 自动消失
- 可手动关闭
- 文案变化时可有轻动画
- 默认只配轻震动
- 不承担结果反馈
- 不承担内容说明
### 2.2 彩色短反馈条
职责:
- 只负责告诉玩家“刚刚发生了什么”
规则:
- 短暂出现
- 不带按钮
- 不显示长说明
- 不承担下一步引导
### 2.3 白色内容卡
职责:
- 只承载显式配置的点位内容
规则:
- 默认不进入最小流程
- 默认关闭
- 仅当某个点位明确启用时才参与流程
- 浏览型卡片默认短时自动消失
- 交互型卡片只在显式开启时出现
### 2.4 答题卡
职责:
- 承载显式启用的答题玩法
规则:
- 属于强交互层
- 最小模板下默认关闭
- 仅当点位显式配置 `quiz` CTA 时才进入
- 不依赖白色内容卡作为前置
### 2.5 成绩总览页
职责:
- 承载结算结果
规则:
- 终点完成后直接进入
- 不再额外叠终点白卡
---
## 3. 打点默认规则
### 3.1 开始点
程序默认行为:
- 必须先打开始点才能正式开赛
- 成功打开始点后开始计时
- 起点完成后只给短反馈,并更新引导和 HUD
- 默认不弹白色开始卡
- 默认不弹答题卡
### 3.2 普通点
程序默认行为:
- 成功打点后先完成基础结算
- 最小模板下默认不弹答题卡
- 如需答题,必须显式配置点位 CTA
- 默认不先弹白色内容卡
- 默认不重复得分
- 默认不重复出题
### 3.3 结束点
程序默认行为:
- 成功打终点后先给完成短反馈
- 随后直接进入结果页
- 默认不弹白色终点卡
- 默认不弹答题卡
### 3.4 关门时间
程序默认行为:
- 默认关门时间为开赛后 `2` 小时
- 距离关门时间小于等于 `10` 分钟时HUD 第 1 页时间区切换为倒计时显示
- 倒计时显示需有区别于正常计时的强调样式
- 到达关门时间后,系统自动结束当前对局
- 超时结束必须和正常完赛、主动退出区分开
---
## 4. 玩法默认差异
程序只内建少量玩法差异,不把所有东西做成配置。
### 4.1 顺序打点 `classic-sequential`
默认差异:
- 按顺序推进
- 默认允许跳点
- 默认跳点半径 = 打点半径的 2 倍
- 默认跳点前弹出确认
- 当前目标点由系统自动推进
- 终点默认需要在中间点都“成功或跳过”后才生效
- 普通点基础分默认 `1`
- 普通点默认不附带答题奖励
### 4.2 积分赛 `score-o`
默认差异:
- 自由打点
- 默认不存在跳点
- 默认不要求先选中目标点
- 点击某个积分点时,默认只更新当前目标和 HUD 信息
- 未点击选中时,也允许直接进入任意积分点范围完成打点
- 默认至少完成 `1` 个普通积分点后,终点才解锁
- 普通点基础分默认取该点分值
- 普通点默认不附带答题奖励
---
## 5. HUD 默认规则
HUD 属于公共程序能力,不属于某个玩法专属实现。
### 5.1 固定结构
- HUD 固定为 2 页
- 第 1 页为比赛主信息页
- 第 2 页为心率 / 遥测页
- 异型壳子布局固定,不因玩法改变结构
### 5.2 第 1 页默认职责
- 时间
- 里程
- 动作标签
- 目标摘要
- 目标距离
- 进度摘要
- 速度
- 临近关门时间时,时间槽位切换为倒计时
### 5.3 第 2 页默认职责
- 心率
- 卡路里
- 平均速度
- 精度或相关遥测值
### 5.4 遥测身体数据来源
程序默认口径:
- HUD 第 2 页使用统一遥测运行时
- 活动配置里的 `game.telemetry.*` 只作为活动默认值
- 玩家年龄、静息心率、体重等身体数据,后续允许由线上接口覆盖
- 线上身体数据一旦到位,应高于活动配置生效
默认优先级:
`系统默认值 -> 活动遥测默认值 -> 玩家线上身体数据`
### 5.5 玩法映射默认口径
- 顺序打点:
- 目标摘要显示当前目标点
- 进度摘要显示完成进度和跳点数
- 积分赛:
- 目标摘要显示当前选中目标点
- 进度摘要显示总分和收集进度
### 5.6 目标角色默认口径
程序默认把目标拆成 4 类:
- 可打目标:当前进入范围后可真正完成打点的对象
- 引导目标:用于距离音效、接近提示和弱引导的对象
- HUD 目标:用于底部信息面板距离与摘要显示的对象
- 展示高亮目标:用于地图上重点高亮的对象
约束:
- 这 4 类目标不能再混用
- 积分赛里“选中目标”默认只影响 HUD 目标
- 距离音效默认只跟随引导目标,不跟随选中状态
---
## 6. 距离反馈默认规则
距离反馈和黑底引导提示条分离管理。
### 6.1 黑底引导提示条
- 默认只走轻震动
- 不绑定提示音
### 6.2 距离提示
默认分为 3 档:
1. `distant`
2. `approaching`
3. `ready`
默认口径:
- `ready`:进入可打点范围
- `approaching`:接近目标
- `distant`:较远但仍处于有效提醒范围
- 更远距离默认静默
默认阈值:
- `distantDistanceMeters = 80`
- `approachDistanceMeters = 20`
- `readyDistanceMeters = 5`
默认节奏:
- `distant`:弱提醒,默认间隔 `4800ms`
- `approaching`:较明确提醒,默认间隔 `950ms`
- `ready`:确认提醒,默认间隔 `650ms`
---
## 7. 系统设置默认规则
设置页属于程序公共能力,不属于某个玩法专属逻辑。
### 7.1 设置项结构
每个设置项默认由两部分组成:
- `value`:设置值
- `isLocked`:是否允许玩家修改
### 7.2 默认值规则
程序默认要求:
- 每个设置项都必须有系统默认值
- 玩家未手动修改时,直接使用系统默认值
- 默认值应集中维护,不散落在页面逻辑里
### 7.3 持久化规则
程序默认要求:
- `value` 需要持久化
- `isLocked` 不持久化
- 页面每次进入时,锁态都应重新按当前运行时规则计算
- 锁态只受系统默认值与活动配置影响,玩家不能在页面中修改锁态
- 设置页中的锁态徽标只做状态展示,不提供点按切换能力
- 锁态生存期:从当前游戏配置载入并进入该局开始,到本局正常结束、超时结束或主动退出为止
默认优先级:
`系统设置默认值 -> 玩家本地持久化值`
锁态优先级:
`系统锁态默认值 -> 当前运行时或活动规则覆盖`
### 7.4 当前已集中维护的设置基线
当前已在 [systemSettingsState.ts](D:/dev/cmr-mini/miniprogram/game/core/systemSettingsState.ts) 集中维护:
- 轨迹显示
- GPS 点显示
- 侧边按钮习惯
- 自动旋转
- 指北针调校
- 北向参考
- 中央标尺显示与锚点
- 各设置项的默认锁态
---
## 8. 最小流程基线
### 8.1 顺序打点最小流程
1. 进入游戏,只显示开始点
2. 打开始点,开赛并显示全场
3. 按顺序推进普通点
4. 普通点打点后默认只做基础结算
5. 可触发跳点
6. 打终点后直接进入结果页
7. 如果超过 `2` 小时仍未结束,系统按超时结束处理
### 8.2 积分赛最小流程
1. 进入游戏,只显示开始点
2. 打开始点,开赛并显示全部积分点和终点
3. 可直接前往任意积分点,或点击某个点更新当前目标
4. 进入积分点范围后成功打点
5. 普通点打点后默认只做基础结算
6. 默认至少完成 `1` 个普通积分点后可打终点结束
7. 如果超过 `2` 小时仍未结束,系统按超时结束处理
---
## 9. 暂不开放为配置的内容
当前先不优先配置化的内容:
- 弹层体系层级
- 起点是否弹白卡
- 普通点是否先白卡后答题
- 终点是否白卡后结算
- HUD 是否双页
- HUD 异型壳子结构
- 黑条提示与距离反馈的职责边界
这些内容应先作为程序默认能力稳定下来。
---
## 10. 故障恢复基线
当前故障恢复按“轻量快照恢复”处理:
- 仅恢复进行中的对局
- 仅恢复核心赛局状态、遥测累计值和地图基础视口
- 不恢复白卡、答题卡、临时动效、短反馈和提示层
- 恢复后由规则层和展示层重新计算 HUD、按钮文案、目标提示和音效状态
当前默认恢复内容:
- 当前配置入口
- `startedAt / completedControlIds / skippedControlIds / currentTargetControlId / score / modeState`
- 累计里程、基础心率/卡路里累计、最后 GPS 点
- `zoom / centerTile / rotation / gpsLock`
当前默认不恢复内容:
- 详情卡
- 答题中间态
- 待查看内容入口
- 所有瞬时动效和提示队列
---
## 11. 后续开放配置的原则
后续只有满足以下条件的内容,才建议开放成配置:
- 运营确实会频繁改
- 改动不会破坏主流程一致性
- 改完不会引入一组新的层级冲突
优先可配置的内容应是:
- 点位分值
- 点位内容是否启用
- 点位样式
- 三档距离提示阈值
- 三档距离提示间隔
---
## 12. 一句话结论
当前阶段应以这份文档作为**程序默认能力基线**先把最小流程、弹层职责、HUD 结构和距离反馈定死,再决定哪些内容值得进入配置层。

View File

@@ -0,0 +1,245 @@
# 运行时编译层总表
本文档用于定义当前项目推荐的“运行时编译层”结构,也就是把系统默认值、玩法默认值、活动配置、玩家设置编译成统一运行时 profile 的中间层。
目标:
- 避免页面、引擎、规则层到处直接读取原始配置
- 明确各种默认值和覆盖值的合并顺序
- 为后续继续收口代码提供统一目标
说明:
- 本文档讲的是“运行时编译结构”
- 它不替代字段字典和玩法规则文档
- 当前已先落第一版代码骨架,后续会逐步把更多模块并到这一层
---
## 1. 为什么需要编译层
当前系统里至少有 4 类来源:
1. 系统默认值
2. 玩法默认值
3. 活动配置
4. 玩家设置
如果页面、引擎、规则层分别自己去判断这些来源,问题会很快出现:
- 默认值散落
- 覆盖顺序不一致
- 同一个字段在不同页面里表现不同
- 后续后台接入后更难维护
所以推荐做法是:
先编译,再运行。
也就是:
`默认值 / 配置 / 玩家设置 -> Runtime Profile -> MapEngine / Rule / HUD`
---
## 2. 当前推荐编译顺序
### 2.1 规则与玩法相关
推荐顺序:
`系统默认值 -> 玩法默认值 -> 活动配置`
适用:
- 对局流程
- 打点规则
- 跳点规则
- 完赛规则
- 分值默认值
### 2.2 系统设置相关
推荐顺序:
`系统设置默认值 -> 活动 settings 默认值 -> 玩家本地设置值`
锁态单独处理:
`系统默认锁态 -> 活动 settings 锁态 -> 本局锁态生命周期`
说明:
- 设置值可以持久化
- 锁态不持久化
- 锁态只在当前游戏配置运行生命周期内生效
### 2.3 遥测相关
推荐顺序:
`系统遥测默认值 -> 活动 telemetry 默认值 -> 玩家身体数据`
适用:
- 年龄
- 静息心率
- 体重
---
## 3. 当前推荐 profile 结构
当前建议至少编译成以下几块:
### 3.1 `map`
负责地图底座级运行参数:
- 标题
- 控制点绘制半径
- 投影
- 磁偏角
- 初始缩放
### 3.2 `game`
负责玩法与规则层运行参数:
- 玩法模式
- 关门时间
- 关门预警时间
- 打点方式
- 打点半径
- 是否先选目标点
- 是否允许跳点
- 跳点半径
- 跳点确认
- 是否自动结束
- 默认点位分值
### 3.3 `settings`
负责系统设置页相关运行参数:
- 设置值最终结果
- 锁态最终结果
- 锁态生命周期是否激活
### 3.4 `telemetry`
负责遥测层运行参数:
- 合并后的遥测配置
- 当前玩家身体数据快照
### 3.5 `presentation`
负责表现层 profile
- 控制点样式
- 轨迹样式
- GPS 点样式
### 3.6 `feedback`
负责反馈层 profile
- 音效配置
- 震动配置
- UI 动效配置
---
## 4. 当前代码落点
第一版运行时编译骨架已落在:
- [runtimeProfileCompiler.ts](D:/dev/cmr-mini/miniprogram/game/core/runtimeProfileCompiler.ts)
当前已经开始编译的内容:
- `game`
- `settings`
- `telemetry`
- `presentation`
- `feedback`
- `map`
当前已接入页面 / 引擎的部分:
- 设置页运行时 profile
- 遥测层运行时 profile
- 反馈层运行时 profile
- 表现层 runtime profile
- 规则层中一部分 `game profile` 字段
当前接入说明:
- `settings / telemetry / feedback` 已开始作为日常运行入口使用
- 设置页大部分引擎型设置项已改成“先更新玩家设置,再统一走 `settings profile` 回灌 `MapEngine`
- `settings profile` 现已通过 `MapEngine.applyCompiledSettingsProfile(...)` 统一应用,不再由页面逐项调用各类 `handleSet...`
- `presentation` 已开始通过编译层回写 `MapEngine` 表现参数
- `game` 当前已先接入模式、关门时间、打点和跳点这组核心字段
- `map` 已开始接入地图底座参数和配置状态文本这组基础字段
- `MapEngine.applyRemoteMapConfig(...)` 已开始退回为原始场地与资源入口,不再继续双写已编译字段
后续建议继续并入:
- `MapEngine.applyRemoteMapConfig(...)` 的配置归一入口
- `map profile`
- 更多 `game profile` 字段
- `courseToGameDefinition`
---
## 5. 当前推荐落地步骤
建议按以下顺序继续收代码:
1. 先让剩余设置层完全只吃 `settings profile`
2. 再让遥测层只吃 `telemetry profile`
3. 再让反馈层只吃 `feedback profile`
4. 最后把玩法规则入口和表现入口都改成吃统一 profile
补充:
- 规则层中的目标角色也应尽量由统一运行态承载,而不是散落在 HUD 和地图展示字段里反推
- 当前已先明确 4 类目标角色:
- 可打目标
- 引导目标
- HUD 目标
- 展示高亮目标
这样风险最小,也最容易逐步验证。
---
## 6. 这层的边界
运行时编译层应该只做两件事:
1. 合并来源
2. 产出最终运行态 profile
不应该做的事:
- 不直接承载页面状态
- 不直接承载本局临时分数和目标点
- 不直接写回配置
- 不承担玩法执行逻辑本身
也就是说:
- 编译层负责“准备好”
- 引擎和规则层负责“执行”
---
## 7. 当前结论
后续推荐统一按这条链走:
`系统默认值 -> 玩法默认值 -> 活动配置 -> 玩家设置 -> 运行时编译层 -> 引擎 / 页面 / 规则`
这样配置越多,系统越不容易乱;后续后台做复杂了,也还是有一层中间结构兜住。