diff --git a/readme-develop.md b/readme-develop.md new file mode 100644 index 0000000..6f103cd --- /dev/null +++ b/readme-develop.md @@ -0,0 +1,1253 @@ +# CMR Mini 开发架构阶段总结 + +本文档用于记录当前阶段小程序的整体架构、分层原则、事件驱动链路、模拟器体系,以及后续继续扩展时应遵守的边界。 + +当前阶段的核心目标已经从“把地图画出来”升级为“建立一套可长期扩展的运动地图游戏底座”。 +这套底座已经具备以下关键能力: + +- 地图引擎与玩法规则解耦 +- 通用 telemetry 信息层独立 +- 声音 / 震动 / UI 动效统一走反馈层 +- 真实与模拟 GPS 双源并存 +- 真实与模拟心率双源并存 +- 顺序赛与积分赛两套规则可共存 +- 外部模拟器可以在室内驱动定位与心率联调 + +--- + +## 1. 项目当前总体分层 + +当前工程可以概括为 6 层: + +1. 内容与配置层 +2. 地图引擎层 +3. 规则运行时层 +4. Telemetry 信息层 +5. Presentation 展示适配层 +6. Feedback 反馈层 + +此外,还有一条独立的开发调试链: + +- 小程序调试面板 +- 外部模拟器 `tools/mock-gps-sim` + +这两者不参与业务规则,只作为开发辅助工具。 + +--- + +## 2. 目录结构说明 + +### 2.1 小程序主目录 + +位于 [miniprogram](D:/dev/cmr-mini/miniprogram): + +- [engine](D:/dev/cmr-mini/miniprogram/engine) +- [game](D:/dev/cmr-mini/miniprogram/game) +- [pages](D:/dev/cmr-mini/miniprogram/pages) +- [utils](D:/dev/cmr-mini/miniprogram/utils) +- [assets](D:/dev/cmr-mini/miniprogram/assets) + +### 2.2 游戏相关目录 + +位于 [miniprogram/game](D:/dev/cmr-mini/miniprogram/game): + +- [core](D:/dev/cmr-mini/miniprogram/game/core):规则运行时核心模型 +- [rules](D:/dev/cmr-mini/miniprogram/game/rules):玩法插件 +- [presentation](D:/dev/cmr-mini/miniprogram/game/presentation):展示适配状态 +- [telemetry](D:/dev/cmr-mini/miniprogram/game/telemetry):通用信息层 +- [feedback](D:/dev/cmr-mini/miniprogram/game/feedback):反馈统一入口 +- [audio](D:/dev/cmr-mini/miniprogram/game/audio):声音层 +- [content](D:/dev/cmr-mini/miniprogram/game/content):课程内容转游戏定义 + +### 2.3 地图引擎目录 + +位于 [miniprogram/engine](D:/dev/cmr-mini/miniprogram/engine): + +- [map](D:/dev/cmr-mini/miniprogram/engine/map):宿主编排与地图主入口 +- [renderer](D:/dev/cmr-mini/miniprogram/engine/renderer):WebGL/2D 渲染 +- [sensor](D:/dev/cmr-mini/miniprogram/engine/sensor):GPS、心率、罗盘、模拟源 +- [tile](D:/dev/cmr-mini/miniprogram/engine/tile):瓦片缓存与加载 +- [camera](D:/dev/cmr-mini/miniprogram/engine/camera):相机与坐标换算 +- [overlay](D:/dev/cmr-mini/miniprogram/engine/overlay):地图叠加层相关 +- [layer](D:/dev/cmr-mini/miniprogram/engine/layer):图层定义 + +### 2.4 外部模拟器目录 + +位于 [tools/mock-gps-sim](D:/dev/cmr-mini/tools/mock-gps-sim): + +- [server.js](D:/dev/cmr-mini/tools/mock-gps-sim/server.js):本地 HTTP + WebSocket 服务 +- [public/index.html](D:/dev/cmr-mini/tools/mock-gps-sim/public/index.html):模拟器 UI +- [public/simulator.js](D:/dev/cmr-mini/tools/mock-gps-sim/public/simulator.js):地图、路径、心率模拟逻辑 +- [public/style.css](D:/dev/cmr-mini/tools/mock-gps-sim/public/style.css):布局与样式 + +--- + +## 3. 设计原则 + +### 3.1 地图引擎只负责地图能力 + +地图引擎负责: + +- 地图缩放、平移、旋转 +- 真实 GPS 与模拟 GPS 的接入 +- 真实心率带与模拟心率的接入编排 +- 路线、控制点、轨迹、编号的绘制 +- WebGL 与 2D 文本层的渲染同步 +- 把规则层产出的 map presentation 转成地图显示 + +地图引擎不负责: + +- 当前玩法规则判定 +- 计分 +- 完成条件 +- 打点策略 +- 游戏胜负 + +核心入口文件是 [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts)。 + +### 3.2 规则层只负责玩法状态推进 + +规则层只关心: + +- 游戏事件输入 +- 当前 session state +- 规则推进后的 next state +- 产出展示所需的 presentation +- 产出反馈所需的 effects + +当前已实现两种玩法: + +- `classic-sequential` +- `score-o` + +对应规则文件: + +- [classicSequentialRule.ts](D:/dev/cmr-mini/miniprogram/game/rules/classicSequentialRule.ts) +- [scoreORule.ts](D:/dev/cmr-mini/miniprogram/game/rules/scoreORule.ts) + +### 3.3 通用运行信息独立为 telemetry + +Telemetry 层不属于具体玩法,也不属于地图引擎。 + +它负责: + +- 用时 +- 距离 +- 当前速度 +- 平均速度 +- 当前目标距离 +- 心率 +- 卡路里 +- 精度 +- HUD 的通用体能颜色分级 + +入口文件: + +- [telemetryRuntime.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryRuntime.ts) + +### 3.4 展示状态独立为 presentation + +当前展示层已经拆成两块: + +- `map presentation` +- `hud presentation` + +这样规则层可以分别决定: + +- 地图该怎么画 +- HUD 该显示什么 + +而不让渲染器自己猜玩法语义。 + +文件: + +- [presentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/presentationState.ts) +- [mapPresentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/mapPresentationState.ts) +- [hudPresentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/hudPresentationState.ts) + +### 3.5 反馈统一走 effect 消费 + +声音、震动、UI 动效不是直接写在规则里,也不是直接写在页面里,而是由规则层产出 `GameEffect[]`,再交给反馈层消费。 + +反馈层入口: + +- [feedbackDirector.ts](D:/dev/cmr-mini/miniprogram/game/feedback/feedbackDirector.ts) + +目前已经挂入: + +- 声音层 +- 震动层 +- 页面动效层 +- 地图瞬时特效层 + +--- + +## 4. 运行主链路 + +当前整套系统的主链路如下: + +```text +远程配置 / KML / 静态内容 +-> GameDefinition +-> 传感输入 (GPS / 心率 / 罗盘 / 模拟源) +-> MapEngine 编排 +-> GameRuntime / RulePlugin +-> GameSessionState + Presentation + Effects +-> Renderer / HUD / FeedbackDirector +``` + +更细一点可以拆成两条并行链。 + +### 4.1 模块关系图 + +```mermaid +flowchart LR + RC["远程配置 / KML / 静态内容"] --> GD["GameDefinition"] + GD --> GR["GameRuntime / RulePlugin"] + + GPS["真实 GPS"] --> LC["LocationController"] + MGPS["模拟 GPS"] --> LC + BLE["真实心率带"] --> HR["HeartRateInputController"] + MHR["模拟心率"] --> HR + COMP["罗盘 / Heading"] --> ME["MapEngine"] + + LC --> ME + HR --> ME + + ME --> GE["GameEvent"] + GE --> GR + + ME --> TE["TelemetryEvent"] + TE --> TR["TelemetryRuntime"] + + GR --> GS["GameSessionState"] + GR --> GP["GamePresentation"] + GR --> FX["GameEffect[]"] + + GS --> ME + GP --> ME + + TR --> TP["TelemetryPresentation"] + TP --> HUD["HUD / 状态色 / 体能面板"] + + ME --> RENDER["Renderer / WebGL / Label Canvas"] + FX --> FB["FeedbackDirector"] + FB --> SOUND["声音"] + FB --> HAPTIC["震动"] + FB --> UIFX["UI / 地图特效"] + + EXT["外部模拟器"] --> MGPS + EXT --> MHR +``` + +### 4.2 玩法链 + +```text +地图点击 / GPS 更新 / 打点按钮 +-> GameEvent +-> GameRuntime.dispatch() +-> RulePlugin.reduce() +-> nextState + presentation + effects +``` + +### 4.3 信息链 + +```text +GPS / 心率 / session 状态 +-> TelemetryEvent +-> TelemetryRuntime +-> TelemetryState +-> TelemetryPresentation +-> HUD / 颜色 / 卡路里 / 距离 +``` + +--- + +## 5. 核心模块说明 + +## 5.1 MapEngine + +文件: + +- [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts) + +职责: + +- 宿主编排器 +- 不直接写玩法判断 +- 管理地图视图状态 +- 编排 `LocationController` +- 编排 `HeartRateInputController` +- 编排 `CompassHeadingController` +- 编排 `GameRuntime` +- 编排 `TelemetryRuntime` +- 编排 `FeedbackDirector` +- 汇总成 `MapEngineViewState` 透传给页面 + +这里是全局协调中心,但不是业务大杂烩。 + +应继续坚持: + +- 新玩法不要往这里塞规则逻辑 +- 新传感器/模拟器可以继续从这里编排接入 + +## 5.2 GameRuntime + +文件: + +- [gameRuntime.ts](D:/dev/cmr-mini/miniprogram/game/core/gameRuntime.ts) + +职责: + +- 载入 `GameDefinition` +- 根据 `mode` 解析具体规则插件 +- 对外提供统一 `dispatch(event)` +- 维护: + - `state` + - `presentation` + - `mapPresentation` + - `hudPresentation` + +当前支持: + +- 顺序打点规则 +- 积分赛规则 + +这是后续继续加玩法的入口。 + +## 5.3 TelemetryRuntime + +文件: + +- [telemetryRuntime.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryRuntime.ts) + +职责: + +- 消费 `gps_updated` +- 消费 `heart_rate_updated` +- 同步 session 状态 +- 计算: + - elapsed + - mileage + - distanceToTarget + - speed + - averageSpeed + - calories + - heart rate tone + +当前心率 / 速度逻辑: + +- 有心率时,颜色和卡路里优先走心率逻辑 +- 无心率时,自动回落到速度代理区间 + +## 5.4 FeedbackDirector + +文件: + +- [feedbackDirector.ts](D:/dev/cmr-mini/miniprogram/game/feedback/feedbackDirector.ts) + +职责: + +- 消费 `GameEffect[]` +- 分发到: + - `SoundDirector` + - `HapticsDirector` + - `UiEffectDirector` + +当前后台音频方案已经暂时回退成前台-only。 +原因是小程序后台音频 loop 行为不稳定,当前阶段不再强行实现。 + +## 5.5 LocationController + +文件: + +- [locationController.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/locationController.ts) + +职责: + +- 管理真实 GPS 与模拟 GPS 双源 +- 暴露统一的位置更新 +- 管理 mock bridge URL、连接状态、调试状态 + +当前支持: + +- `real` +- `mock` + +并且 mock GPS 已经可以通过外部模拟器驱动。 + +## 5.6 HeartRateInputController + +文件: + +- [heartRateInputController.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/heartRateInputController.ts) + +这是最近新增的一层,职责是统一真实心率带与模拟心率源。 + +它内部编排: + +- `HeartRateController`:真实 BLE 心率带 +- `MockHeartRateBridge`:模拟心率桥 + +对上游暴露统一接口: + +- 心率值 +- 状态文本 +- 连接状态 +- 设备列表 +- 源模式调试状态 + +当前支持: + +- `real` +- `mock` + +这样 telemetry 和 HUD 不需要知道心率是从 BLE 来的还是模拟器来的。 + +--- + +## 6. 玩法层当前状态 + +### 6.1 顺序赛 classic-sequential + +核心特征: + +- 开始点先打 +- 打完开始点才显示完整路线 +- 按顺序推进控制点 +- 最后打终点结束 + +地图语义: + +- 单目标高亮 +- 当前目标点闪动 +- 当前目标腿流动动画 +- 已完成点和线灰化 + +### 6.2 积分赛 score-o + +核心特征: + +- 自由选择未完成点 +- 所有未收集点可见且高亮 +- 用户可点击某个点设为 focus +- HUD 显示选中点或最近未完成点距离 +- 终点可随时选中并结束比赛 + +地图语义: + +- 不显示顺序导航腿 +- 所有未完成点为多目标态 +- 选中点有额外强化动画 +- 圈内数字当前默认直接使用序号作为积分数字 + +### 6.3 modeState 设计 + +当前规则层已经预留并开始使用 `modeState`。 + +含义: + +- 通用 `GameSessionState` 只放跨玩法共用字段 +- 各玩法自己的私有状态放 `modeState` + +这为后续新增玩法提供了稳定扩展入口。 + +--- + +## 7. 当前 HUD 设计 + +目前 HUD 有两屏: + +### HUD 1 + +- 打点主信息 +- 用时 +- 里程 +- 当前目标距离 +- 当前速度 +- 打点进度 + +### HUD 2 + +- 心率 +- 卡路里 +- 平均速度 +- 精度 +- 心率区间名称与区间说明 + +HUD 当前颜色由 telemetry 驱动。 + +规则: + +- 有心率:按心率分区 +- 无心率:按速度代理区间 fallback + +当前 6 档对应: + +- 蓝:激活放松 +- 紫:动态热身 +- 绿:脂肪燃烧 +- 黄:糖分消耗 +- 橙:心肺训练 +- 红:峰值锻炼 + +--- + +## 8. 心率带体系当前设计 + +当前心率带链路已经相对完整。 + +### 8.1 单设备连接,多设备发现 + +当前不是“多设备同时连接”,而是: + +- 扫描发现多条设备 +- 用户手选一条连接 +- 连接成功后成为首选设备 +- 后续自动回连只针对这条首选设备 + +原因: + +- 避免误连附近别人的心率带 +- 保持 telemetry 和 HUD 语义简单 + +### 8.2 首选设备持久化 + +已实现: + +- 首选设备持久化到本地 storage +- 重进页面后仍可识别 +- 自动回连优先使用首选设备 + +### 8.3 BLE 断连重连处理 + +这一段实现过多轮修正,目前逻辑重点是: + +- 手动断开与意外断开分离 +- 再连接前,先清理 BLE 残留连接 +- 必要时刷新 Bluetooth Adapter +- 再重新扫描 / 建连 + +这是目前心率带重连稳定的关键。 + +--- + +## 9. 模拟器体系当前设计 + +### 9.1 模拟器总目标 + +解决“GPS / 心率类 App 每次都要出去跑才能测”的问题。 + +当前外部模拟器已经支持: + +- 加载 `game.json` +- 加载瓦片 +- 加载 KML 控制点 +- 地图点击与拖动 +- 实时 GPS 发送 +- 路径编辑与回放 +- 导入轨迹文件回放 +- 心率模拟发送 + +### 9.2 GPS 模拟 + +消息协议: + +```json +{ + "type": "mock_gps", + "timestamp": 1711267200000, + "lat": 31.2304, + "lon": 121.4737, + "accuracyMeters": 6, + "speedMps": 2.4, + "headingDeg": 135 +} +``` + +### 9.3 心率模拟 + +消息协议: + +```json +{ + "type": "mock_heart_rate", + "timestamp": 1711267200000, + "bpm": 148 +} +``` + +### 9.4 当前心率模拟能力 + +外部模拟器当前支持: + +- 固定 BPM 发送 +- 连续发送 +- 六档分区样本 +- 真实样本模式 + +真实样本模式又细分成: + +- 慢跑样本 +- 节奏跑样本 +- 间歇跑样本 +- 恢复走样本 + +### 9.5 模拟器布局原则 + +当前已经调整为: + +- 左侧控制面板独立滚动 +- 右侧地图固定不动 + +这样更适合长面板配置和路径编辑,不会让地图区跟着滚动。 + +--- + +## 10. 当前事件驱动模型 + +当前系统已经较明确地进入了事件驱动模型。 + +### 10.1 规则事件 + +规则层的输入是 `GameEvent`。 + +典型事件: + +- `session_started` +- `gps_updated` +- `punch_requested` +- `control_focused` +- `session_ended` + +规则层输出: + +- `nextState` +- `presentation` +- `effects` + +### 10.2 Telemetry 事件 + +Telemetry 层的输入是 `TelemetryEvent`。 + +典型事件: + +- `session_state_updated` +- `target_updated` +- `gps_updated` +- `heart_rate_updated` + +### 10.3 反馈事件 + +反馈层消费的是 `GameEffect[]`。 + +这样: + +- 声音 +- 震动 +- UI 动效 +- 地图脉冲 + +都可以走统一 effect 通道,而不是各处散写。 + +--- + +## 11. 当前字段归属原则 + +这是当前项目后续扩展最重要的边界之一。 + +### 11.1 放 Telemetry + +适合放: + +- 速度 +- 距离 +- 心率 +- 卡路里 +- 精度 +- 当前目标距离 + +这些是通用运行信息。 + +### 11.2 放 GameSessionState + +适合放: + +- 当前玩法状态 +- 已完成点 +- 当前目标点 +- 得分 +- 玩法私有状态 + +这些会影响规则推进。 + +### 11.3 放 Presentation + +适合放: + +- HUD 文案 +- 当前激活腿 +- 哪些点高亮 +- 哪些点闪动 +- 按钮状态 + +这些只是为了显示。 + +后面开发新增字段时,必须先判断它属于哪层,避免再次耦合。 + +--- + +## 12. 当前已验证成立的扩展能力 + +当前架构已经通过以下场景验证过方向正确: + +1. 从单一顺序赛扩成顺序赛 + 积分赛 +2. 从真实 GPS 扩成真实 + 模拟 GPS +3. 从真实心率带扩成真实 + 模拟心率 +4. 从单一 HUD 扩成双 HUD +5. 从单一音效扩成统一 feedback 模型 +6. 从单设备心率带扩成多设备发现 + 单设备选择 + +这说明当前架构不是只能跑一个 Demo,而是已经具备继续扩展的基础。 + +--- + +## 13. 当前已知现实约束 + +### 13.1 微信开发工具层级模拟不可靠 + +目前已经确认: + +- 真机正常 +- 开发工具在 `webgl canvas + 普通 view` 上的层级模拟并不稳定 + +因此后续验收原则是: + +- 真机为准 +- 开发工具仅做辅助观察 + +### 13.2 小程序后台音频能力有限 + +已经尝试过后台 guidance 音频方案,但当前阶段决定先回退: + +- 前台音频正常 +- 后台不再强做 loop guidance + +后续如果重新开启,需要: + +- 更适合循环的音频素材 +- 更稳定的后台音频策略 + +### 13.3 BLE 生命周期比 JS 状态更慢 + +心率带重连问题已经证明: + +- 逻辑没错,不等于 BLE 底层状态已释放 + +所以后续涉及 BLE 时,必须继续保留: + +- 清场 +- 延迟 +- 状态同步 + +--- + +## 14. 当前阶段的结论 + +到目前为止,这个项目已经完成了从“功能堆叠”到“可扩展结构”的第一阶段转变。 + +现阶段最重要的成果不是某一个按钮或某一个玩法,而是以下架构能力已经成立: + +- 地图引擎与规则解耦 +- 规则事件驱动 +- telemetry 独立成层 +- presentation 拆成 map/hud +- feedback 统一消费 effects +- 真实 / 模拟传感器源可插拔 +- 外部模拟器可驱动整条业务链 + +这意味着后续继续开发时: + +- 新玩法主要加 rule plugin +- 新通用统计主要加 telemetry +- 新反馈主要加 effect 消费端 +- 新传感器 / 模拟器主要加 sensor source + +而不应该再把所有逻辑堆回 `MapEngine` 或页面里。 + +--- + +## 15. 后续建议 + +当前阶段之后,建议按以下优先级推进: + +1. 继续增加玩法插件 +2. 增强调试面板与模拟器联调能力 +3. 给更多玩法留 `modeState` 专属状态 +4. 逐步丰富 telemetry 项 +5. 继续打磨地图引擎的路线渲染能力 + +但无论怎么扩,建议始终遵守本文档里的边界原则。 + +--- + +## 16. 新玩法扩展流程 + +后续新增玩法时,建议始终按下面这条路径落地,而不是直接往 `MapEngine` 或页面里塞逻辑。 + +```mermaid +flowchart TD + A["新增玩法需求"] --> B["确定规则目标
完成条件 / 得分 / 失败条件 / 解锁逻辑"] + B --> C["定义 modeState
只放玩法私有状态"] + C --> D["新增 RulePlugin
reduce + buildPresentation"] + D --> E["补充 GameDefinition 配置项
让远程配置可描述此玩法"] + E --> F["拆 presentation
map / hud 分别表达"] + F --> G{"是否需要新通用统计?"} + G -- "是" --> H["扩 TelemetryRuntime
仅加入跨玩法可复用信息"] + G -- "否" --> I["保持 telemetry 不动"] + H --> J{"是否需要新反馈?"} + I --> J + J -- "是" --> K["新增 GameEffect 消费端
声音 / 震动 / UI / 地图特效"] + J -- "否" --> L["保持 feedback 不动"] + K --> M["调试面板补入口
只做该层对应的调试能力"] + L --> M + M --> N["外部模拟器补样本
仅在确有联调价值时添加"] + N --> O["真机联调与验收"] +``` + +### 16.1 新玩法落地时的边界检查 + +新增玩法前,建议先问下面几个问题: + +1. 这是规则状态,还是通用信息? + - 规则状态放 `modeState` + - 通用信息放 `telemetry` + +2. 这是地图显示语义,还是 HUD 文案语义? + - 地图相关放 `map presentation` + - HUD 相关放 `hud presentation` + +3. 这是玩法逻辑,还是地图能力? + - 玩法逻辑放 rule plugin + - 地图能力放 engine / renderer + +4. 这是单玩法专属能力,还是跨玩法复用能力? + - 专属能力优先局部实现 + - 复用能力再提升为全局层 + +### 16.2 建议的新增玩法最小模板 + +后续新增一个玩法时,建议至少补这些文件或模块: + +- `game/rules/Rule.ts` +- `game/content/courseToGameDefinition.ts` 中对应模式的定义装配 +- `game/core/gameDefinition.ts` 中新增 mode 支持 +- `game/presentation/*` 中新增该玩法需要的 map/hud 字段 +- `pages/map/map.wxml` 只在确有显示需求时接新 HUD 文案 +- 调试面板里补最小测试入口 + +如果一个新玩法一上来就需要大改: + +- `MapEngine` +- `TelemetryRuntime` +- `Renderer` + +那通常说明这次设计边界还没想清楚,应该先回头重审玩法抽象。 + +--- + +## 17. 关键文件索引 + +### 地图与宿主 + +- [mapEngine.ts](D:/dev/cmr-mini/miniprogram/engine/map/mapEngine.ts) +- [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts) +- [map.wxml](D:/dev/cmr-mini/miniprogram/pages/map/map.wxml) + +### 规则层 + +- [gameRuntime.ts](D:/dev/cmr-mini/miniprogram/game/core/gameRuntime.ts) +- [classicSequentialRule.ts](D:/dev/cmr-mini/miniprogram/game/rules/classicSequentialRule.ts) +- [scoreORule.ts](D:/dev/cmr-mini/miniprogram/game/rules/scoreORule.ts) + +### Presentation + +- [presentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/presentationState.ts) +- [mapPresentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/mapPresentationState.ts) +- [hudPresentationState.ts](D:/dev/cmr-mini/miniprogram/game/presentation/hudPresentationState.ts) + +### Telemetry + +- [telemetryRuntime.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryRuntime.ts) +- [telemetryConfig.ts](D:/dev/cmr-mini/miniprogram/game/telemetry/telemetryConfig.ts) + +### Feedback + +- [feedbackDirector.ts](D:/dev/cmr-mini/miniprogram/game/feedback/feedbackDirector.ts) +- [soundDirector.ts](D:/dev/cmr-mini/miniprogram/game/audio/soundDirector.ts) + +### 传感层 + +- [locationController.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/locationController.ts) +- [heartRateController.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/heartRateController.ts) +- [heartRateInputController.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/heartRateInputController.ts) +- [mockLocationBridge.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/mockLocationBridge.ts) +- [mockHeartRateBridge.ts](D:/dev/cmr-mini/miniprogram/engine/sensor/mockHeartRateBridge.ts) + +### 外部模拟器 + +- [server.js](D:/dev/cmr-mini/tools/mock-gps-sim/server.js) +- [index.html](D:/dev/cmr-mini/tools/mock-gps-sim/public/index.html) +- [simulator.js](D:/dev/cmr-mini/tools/mock-gps-sim/public/simulator.js) +- [style.css](D:/dev/cmr-mini/tools/mock-gps-sim/public/style.css) + +--- + +## 18. 调试与模拟体系 + +当前项目已经不再依赖“必须出门跑一遍”才能测通主流程。 +围绕调试和联调,已经形成了两条互补链路: + +- 小程序内调试面板 +- 外部模拟器 + +二者是互补关系,不是替代关系。 + +### 18.1 调试体系关系图 + +```mermaid +flowchart LR + DEV["开发者"] --> MP["小程序调试面板"] + DEV --> SIM["外部模拟器"] + + MP --> MGPS["切换 mock GPS"] + MP --> MHR["切换 mock 心率"] + MP --> MODE["切换玩法 / 调试动作"] + + SIM --> WS["WebSocket 桥"] + WS --> GPSMSG["mock_gps"] + WS --> HRMSG["mock_heart_rate"] + + GPSMSG --> LC["LocationController"] + HRMSG --> HRC["HeartRateInputController"] + + LC --> ME["MapEngine"] + HRC --> ME + MODE --> ME + + ME --> GR["GameRuntime"] + ME --> TR["TelemetryRuntime"] + GR --> HUD["HUD / 地图渲染"] + TR --> HUD + + DEV --> REAL["真机联调"] + REAL --> BLE["真实心率带"] + REAL --> GPS["真实 GPS"] + BLE --> HRC + GPS --> LC +``` + +### 18.2 小程序调试面板的职责 + +调试面板位于 [map.wxml](D:/dev/cmr-mini/miniprogram/pages/map/map.wxml) 和 [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts)。 + +它的职责是: + +- 查看当前局状态 +- 查看 GPS / 心率 / 罗盘状态 +- 切换真实源 / 模拟源 +- 触发局部调试动作 +- 展示设备列表与连接状态 +- 辅助验证 HUD 和地图渲染状态 + +调试面板的原则是: + +- 只操作已经存在的系统能力 +- 不直接篡改规则内部状态 +- 调试动作尽量对应单一层 + +也就是说: + +- 连接心率带,属于 sensor 调试 +- 切换玩法,属于 rule 调试 +- 切 mock bridge,属于传感输入源调试 +- 心率分区测试,属于 telemetry 调试 + +### 18.3 外部模拟器的职责 + +外部模拟器位于 [tools/mock-gps-sim](D:/dev/cmr-mini/tools/mock-gps-sim)。 + +它的职责是: + +- 室内模拟 GPS 位置流 +- 模拟路径回放 +- 模拟心率值和心率曲线 +- 复用真实 `game.json / KML / tiles` +- 在电脑侧快速联调地图、HUD、规则 + +当前它已经支持: + +- 载入 `game.json` +- 载入 KML 控制点 +- 载入瓦片模板 +- 实时 GPS 发送 +- 路径编辑 +- 路径回放 +- 导入轨迹文件 +- 固定心率发送 +- 分区样本心率发送 +- 真实样本心率模板发送 + +### 18.4 当前 mock 通信协议 + +GPS: + +```json +{ + "type": "mock_gps", + "timestamp": 1711267200000, + "lat": 31.2304, + "lon": 121.4737, + "accuracyMeters": 6, + "speedMps": 2.4, + "headingDeg": 135 +} +``` + +心率: + +```json +{ + "type": "mock_heart_rate", + "timestamp": 1711267200000, + "bpm": 148 +} +``` + +两者当前共用同一个 WebSocket 入口: + +- `.../mock-gps` + +这是当前阶段为了降低复杂度做的统一通道设计,后面如果模拟消息种类继续增加,再考虑独立通道或消息总线拆分。 + +### 18.5 当前推荐的联调顺序 + +如果要联调一个完整玩法,建议按这个顺序: + +1. 先载入配置与控制点 +2. 小程序切到 mock GPS / mock heart rate +3. 外部模拟器连接桥接 +4. 先用固定值测试最小闭环 +5. 再切路径回放和心率样本 +6. 最后再上真机 + 真实 GPS / BLE 心率带验收 + +这个顺序的好处是: + +- 先验证链路 +- 再验证动态过程 +- 最后再验证真实设备行为 + +### 18.6 为什么调试体系很重要 + +对这类 GPS / 心率 / 定向玩法类项目来说,最大的开发瓶颈往往不是代码本身,而是: + +- 需要空间移动 +- 需要真实设备 +- 需要时间成本 +- 需要复现复杂路径 + +因此调试与模拟体系本身就是底座能力的一部分,而不是临时工具。 + +后续建议继续把以下能力都优先放在这条体系里: + +- 轨迹回放模板 +- 心率样本模板 +- 特殊玩法样本 +- 多阶段规则验证入口 +- 关键状态可视化 + +--- + +本文档用于当前阶段开发总结。 +后续如果架构继续升级,建议直接在本文件上持续迭代,而不是另起多份架构说明,避免信息分散。 + +--- + +## 19. 当前开发约定 + +为了避免后续开发过程中重新把边界做乱,当前阶段建议固定以下约定。 + +### 19.1 修改代码前先判断归属层 + +新增需求时,先判断它属于哪一层,再落代码: + +- 地图能力问题:优先进 `engine` +- 玩法判定问题:优先进 `game/rules` +- 通用运动信息问题:优先进 `game/telemetry` +- 纯展示文案或 HUD 结构问题:优先进 `game/presentation` 或页面层 +- 声音、震动、地图脉冲等反馈问题:优先进 `game/feedback` +- 调试动作与仿真工具问题:优先进调试面板或外部模拟器 + +如果一个需求同时改动太多层,通常说明边界还没想清楚。 + +### 19.2 先扩事件,再扩功能 + +当前架构是事件驱动主链,因此新增能力时优先顺序建议为: + +1. 先定义输入事件 +2. 再定义规则输出的 `effect` 或 `presentation` +3. 最后补具体的 UI / 声音 / 动效消费端 + +不要直接在页面里写死业务副作用,也不要让渲染器自己猜业务状态。 + +### 19.3 build 版本号约定 + +当前小程序页面 build 号统一写在: + +- [map.ts](D:/dev/cmr-mini/miniprogram/pages/map/map.ts) + +约定为: + +- 每次发生用户可感知的页面 / 地图 / 调试 / 玩法改动时,自增 1 +- 只做文档或纯注释修改时,可以不变 + +这样方便现场确认当前安装包和工作副本是否一致。 + +### 19.4 提交约定 + +当前开发分支约定使用: + +- `codex/*` + +提交时建议遵守: + +- 一次提交只围绕一个相对完整的目标 +- 不把开发工具噪声配置混进业务提交 +- 优先提交可运行的阶段闭环,而不是半成品 + +当前项目里一个典型例子是: + +- [project.config.json](D:/dev/cmr-mini/project.config.json) + +这类本地开发工具配置,不应该默认混入功能提交。 + +### 19.5 自测约定 + +当前阶段每次改动后的最低自测要求建议是: + +- 代码改动后执行 `npm run typecheck` +- 外部模拟器脚本改动后,额外执行 `node --check` +- 涉及地图 / HUD / 层级表现的问题,以真机为准 +- 涉及 BLE / GPS / 后台行为的问题,必须至少走一遍真机联调 + +原因是: + +- 微信开发工具对 `webgl canvas`、原生层级、蓝牙、后台音频的模拟都不完全可靠 + +### 19.6 当前阶段已知现实约束 + +这些不是 bug,但开发时要心里有数: + +- 微信开发工具里,`webgl canvas` 与普通视图层级表现可能和真机不一致 +- 后台音频能力当前不稳定,因此已经回退为前台策略 +- BLE 心率带连接存在底层资源释放延迟,重连逻辑必须保守处理 +- 模拟器与真机联调时,公网 / 局域网 WebSocket 地址要明确区分 + +### 19.7 当前阶段最重要的判断标准 + +如果新增一个功能后出现以下现象,就要停下来重新审视设计: + +- 为了做一个玩法,不得不大改 `MapEngine` +- 为了做一个 HUD 文案,不得不改 `RuleEngine` +- 为了做一个声音效果,不得不改地图渲染逻辑 +- 为了做一个模拟输入,不得不绕过传感层直接写 telemetry + +出现这些情况,通常说明实现绕过了当前架构,应优先回到分层原则重新整理。 + +--- + +## 20. 后续演进路线图 + +当前架构已经从“能跑”进入“可持续扩展”阶段。 +后续建议不要无序加功能,而是按时间层次推进。 + +### 20.1 近期目标 + +近期目标的重点不是再造新架构,而是继续把现在这套底座打磨稳。 + +建议优先推进: + +- 继续完善顺序赛与积分赛的细节体验 +- 继续扩充调试面板,但保持分组清晰 +- 继续扩充外部模拟器,让常见流程都能室内复现 +- 持续清理地图引擎与规则层之间可能重新耦合的点 +- 对关键链路补更多真机验证经验 + +这个阶段的目标是: + +- 日常开发大部分功能都能在室内完成联调 +- 新玩法加入时不需要再反复返工底层分层 + +### 20.2 中期目标 + +中期更适合开始扩充玩法族,而不是继续只做当前两种玩法。 + +建议方向: + +- 增加更多 `RulePlugin` +- 开始让 `modeState` 真正承载玩法私有状态 +- 继续把玩法专属 HUD / 地图表现做成独立 adapter +- 把玩法配置进一步从“少量字段”扩成更完整的 `game` 段 +- 让反馈系统支持更清晰的事件 profile + +这个阶段的核心判断标准是: + +- 新玩法主要新增规则与 presentation 文件 +- 而不是大改已有底层模块 + +### 20.3 远期目标 + +远期不建议先追求花哨表现,而是继续把底座能力做成可复用资产。 + +可能的方向包括: + +- 玩法模板化 +- 外部模拟器支持更多传感器类型 +- 赛后回放与事件日志分析 +- 多人 / 团队玩法 +- 更完整的内容配置系统 +- 更稳定的后台能力方案 + +到这个阶段,理想状态是: + +- 这套底座不只服务当前项目 +- 也能被后续其它运动地图类 App 直接复用 + +### 20.4 当前最值得持续投入的底座能力 + +如果要挑几项最值得长期投入的底座能力,当前建议是: + +- `RulePlugin` 扩展能力 +- `Telemetry` 的稳定性和通用性 +- 外部模拟器 +- 调试面板 +- 地图路线符号系统 +- 反馈事件体系 + +这些能力的共同价值在于: + +- 一次投入,可以被多个玩法复用 + +### 20.5 当前不建议过早投入的方向 + +以下方向并不是不做,而是不建议当前阶段优先投入: + +- 为了开发工具显示偏差而重构正式页面层级 +- 一上来做复杂多人同步 +- 一上来做过重的后台音频方案 +- 在还没有足够玩法前就过度抽象成庞大平台 + +当前更重要的是: + +- 把已经证明有效的主链打磨稳 +- 再逐步扩展玩法和能力