整理文档并接入 H5 体验测试链路

This commit is contained in:
2026-03-27 15:36:27 +08:00
parent 0e025c3426
commit 0e0a724025
55 changed files with 4177 additions and 55 deletions

50
doc/GeminiAnlysis.md Normal file
View File

@@ -0,0 +1,50 @@
# CMR-Mini 项目深度分析报告 (GeminiAnalysis.md)
## 1. 项目定位与核心愿景
**CMR-Mini** 是一个运行在微信小程序环境中的高性能**定向越野 (Orienteering)** 实时竞赛/练习引擎。其核心竞争力在于通过自研的 **WebGL 地图渲染管线** 提供流畅的地图交互并结合高精度多传感器融合技术GPS、罗盘、心率、加速度计等实现精准的运动反馈。
## 2. 核心系统架构分析
### 2.1 地图渲染引擎 (Map Engine)
* **渲染技术**:采用 `Single WebGL Pipeline`。相比微信原生地图组件具有更高的定制化能力特别是在“Heading-Up”朝向朝上模式下的性能表现。
* **瓦片管理**:通过 `TileStore` 实现三级缓存(内存 -> 磁盘 -> 网络),并支持 `tilePersistentCache`
* **投影逻辑**:采用 `WGS84 -> WorldTile -> Camera -> Screen` 的标准 GIS 变换链,能够精准处理地理坐标到屏幕像素的映射。
### 2.2 传感器融合系统 (Sensor System)
* **CompassHeadingController**:核心逻辑在于罗盘数据 (`wx.onCompassChange`) 与设备姿态 (`wx.onDeviceMotionChange`) 的协同。
* **LocationController**:支持真实 GPS 数据与 Mock 模拟器(通过 WebSocket 连接 `mock-gps-sim` 工具)的无缝切换。
* **TelemetryRuntime**:实现了运动参数的实时计算,包括速度、距离目标点距离、心率分区等指标。
### 2.3 游戏逻辑与规则 (Game Logic)
* **GameRuntime**:驱动对局状态机,支持“顺序赛 (Classic Sequential)”与“积分赛 (Score-O)”。
* **PunchPolicy**:实现了自动进入检查点范围触发、手动打点、跳过点位等业务逻辑。
## 3. 指北针 (Compass) 平滑度瓶颈分析
根据目前的实现,指北针的卡顿感主要源于以下三个层面:
1. **采样频率与插值逻辑**
* 目前使用 `interpolateHeadingDeg` 进行线性差值,且 `ABSOLUTE_HEADING_CORRECTION` 为固定系数 (0.44)。这种静态系数在“静态微调”时显得不够敏锐,在“快速旋转”时又显得滞后。
2. **Android/iOS 差异化丢帧**
* Android 传感器回调频率不稳定。
* 逻辑中对 `direction` 进行了严格的数值有效性判断,若系统由于硬件抖动返回短时异常值,会导致视觉上的“跳帧”。
3. **UI 同步周期限制**
* `MapEngine``UI_SYNC_INTERVAL_MS` 设置为 80ms这意味着视觉反馈的最高帧率仅为 12.5Hz,远低于屏幕刷新率,导致指针转动不够丝滑。
## 4. 优化技术路线建议
### 4.1 引入指数加权移动平均 (EWMA) 的动态系数
建议根据旋转角速度动态调整平滑系数。当检测到瞬时角位移较大时,降低平滑度以追求响应速度;当位移较小时,增加平滑度以过滤手抖带来的噪声。
### 4.2 视觉平滑:使用 CSS Transform 或 WebGL 帧间补偿
目前数据是由控制器下发到 UI 的。建议:
* **方案 A (推荐)**:在 UI 层(`.wxml`/`.wxss`)利用 `transition: transform 0.1s linear;` 实现视觉层面的自动补帧。
* **方案 B**:在 WebGL 渲染循环内进行帧间插值,将数据的 12.5Hz 提升到 渲染循环的 60Hz。
### 4.3 预测与死区 (Dead-zone) 过滤
`CompassHeadingController` 中加入微小位移的死区过滤逻辑,避免由于硬件高频微小抖动导致的视图高频重绘,降低系统功耗的同时提升视觉稳定性。
## 5. 结论
CMR-Mini 已经建立了一个非常坚实的专业定向越野引擎基础。后续的优化重点应从“功能的实现”转向“交互的极致平滑”,特别是针对指北针这类核心导向组件,需要更精细化的信号处理策略。
---
*Generated by Gemini CLI Analysis Tool*

4
doc/MyToDo.md Normal file
View File

@@ -0,0 +1,4 @@
结果页会根据客户的要求不停的变换,用什么方案能实现这个需求,其实其他的弹出内容也都存在这个问题,样式,内容都时根据客户需求变化的,怎样一种方案设计比较好呢?

View File

@@ -0,0 +1,450 @@
# 动效系统设计方案
本文档用于整理当前项目后续的动画 / 动效建设方案,目标不是单纯“让界面更花”,而是把动画正式纳入现有架构,成为:
- 状态感知工具
- 空间注意力引导工具
- 操作反馈工具
- 节奏增强工具
当前系统已经具备:
- 地图引擎
- 规则引擎
- telemetry
- presentation
- feedback
因此动画系统最合理的做法,不是零散补丁,而是按层管理、按事件驱动、按配置扩展。
---
## 1. 设计原则
后续动画建设建议遵循以下原则:
### 1.1 动画服务于玩法,不只是装饰
动画优先回答这些问题:
- 现在发生了什么
- 用户该看哪里
- 这次操作是否成功
- 当前节奏是在紧张、平稳还是危险
### 1.2 动画要分层
不要把所有动画都堆在页面层的 class 切换里。
后续应按:
- 地图空间动画
- HUD 动画
- 反馈动画
- 页面微交互动画
分层管理。
### 1.3 动画要和事件绑定
动画应该由事件或状态变化触发,而不是页面自己猜。
例如:
- `control_completed`
- `control_skipped`
- `guidance_state_changed`
- `session_started`
- `session_finished`
- `heart_rate_zone_changed`
- `gps_lock_changed`
### 1.4 动画要支持降级
低端机和正式版都需要降级策略。
后续建议统一支持:
- `animationsEnabled`
- `animationLevel = low / medium / high`
---
## 2. 动画分层方案
## 2.1 地图空间动画
这一层最重要,也最贴玩法。
适合放在:
- `MapPresentation`
- `MapScene`
- `WebGL renderer`
典型内容:
- 当前目标点脉冲
- 可打点状态强化
- 已完成点过渡
- 已跳过点灰态过渡
- 地图 pulse
- 危险区呼吸
- 迷雾 reveal 扩散
- 金币收集爆点
- 幽灵感知圈变化
### 这一层的特点
- 与地图空间对象绑定
- 最不适合用 WXML 硬拼
- 应由渲染层持续驱动
---
## 2.2 HUD 动画
这一层用于数值和状态提示,不直接改地图对象。
适合放在:
- 页面层
- HUD 组件层
典型内容:
- 目标距离数字滑变
- 进度数字跳变
- 心率区间颜色过渡
- 计时器关键时刻闪烁
- 按钮状态点亮 / 失活过渡
- 玩法专属状态块的显隐和强调
### 这一层的特点
- 更适合 CSS / WXSS animation
- 应避免过重
- 高优先级字段可以做轻动画,避免全屏大动作
---
## 2.3 反馈动画
这一层最适合和声音、震动一起看,属于事件消费型动画。
适合放在:
- `FeedbackDirector`
- `UIEffectDirector`
典型内容:
- 打点成功 toast
- 警告 shake
- 成功 burst
- stage flash
- 局部 pulse
- 失败 / 结束反馈
### 当前已有雏形
目前系统已经有一些反馈类动效基础:
- `punchFeedbackFxClass`
- `mapPulse`
- `stageFx`
这条线后续最值得继续系统化。
---
## 2.4 页面微交互动画
这一层优先级最低。
典型内容:
- 按钮轻微过渡
- 面板弹入弹出
- 信息卡展开收起
- 调试面板展开收起
### 原则
- 可以做,但不要先重投入
- 不要让它抢过地图和玩法本身的注意力
---
## 3. 当前最值得优先打磨的动画
如果要开始投入动画,我建议先做这 4 组。
## 3.1 打点成功动画体系
这是当前项目最值得优先打磨的一组。
建议包含:
- 控制点本体状态变化
- 地图局部 pulse
- HUD 进度跳变
- 成功提示 toast
- 声音与震动协同
### 为什么优先
- 高频
- 用户感知强
- 直接决定“打点有没有爽感”
---
## 3.2 目标点状态动画体系
建议把目标点的几种状态做清晰区分:
- 未完成
- 当前目标
- 可打点
- 已完成
- 已跳过
每个状态至少应在:
- 颜色
- 脉冲
- 强弱
上有明显区别。
### 为什么优先
- 这是地图玩法的核心视觉语言
- 对理解规则和空间注意力引导都很关键
---
## 3.3 锁定 / 自动转图状态动画
建议补强以下体验:
- 开启 GPS 锁定时的吸附反馈
- 锁定关闭时的提示
- 自动转图切换时的更自然缓动
- 特殊状态下的方向感提示
### 为什么优先
- 当前地图交互已经很强
- 这块稍微打磨就很有“专业感”
---
## 3.4 危险 / 高压状态动画
这条非常适合未来玩法扩展,尤其是:
- 幽灵追逐赛
- 心率驱动玩法
- 高压任务模式
建议后续支持:
- 边缘呼吸
- 危险圈脉冲
- 压力提示颜色递进
- 节奏增强
---
## 4. 事件驱动建议
动画最好不要由页面层直接“看到状态变了就自己猜”,而应由事件或 presentation 状态明确驱动。
建议优先整理以下动画事件:
- `session_started`
- `session_finished`
- `session_cancelled`
- `control_completed:start`
- `control_completed:control`
- `control_completed:finish`
- `control_skipped`
- `guidance_state_changed`
- `gps_lock_changed`
- `heart_rate_zone_changed`
- `danger_state_changed`
这些事件后续可以统一映射到:
- sound
- haptics
- uiEffects
- map animation
---
## 5. 配置化建议
后续动画不应只写死在代码里。
建议逐步走向 profile 化。
例如:
```json
"game": {
"feedback": {
"uiEffectsProfile": "default-race",
"mapAnimationProfile": "default-map"
}
}
```
### 后续 profile 可承载的内容
- 某类事件是否启用动效
- 动效持续时间
- 动效强度
- 颜色风格
- 是否允许低端机降级
---
## 6. 建议增加统一动画配置
建议后续统一支持:
```json
"game": {
"animation": {
"enabled": true,
"level": "medium"
}
}
```
建议值:
- `enabled`
- `level = low / medium / high`
### 用途
- 低端机降级
- 调试关闭
- 正式版保守
---
## 7. 技术落地建议
## 7.1 地图动画
应继续放在地图引擎和 renderer 内处理。
不要让页面层承担:
- 点位 pulse
- 区域 reveal
- 轨迹闪动
- 目标高亮
这些都更适合:
- `MapPresentation`
- `MapScene`
- `WebGL renderer`
---
## 7.2 HUD 动画
适合继续放在页面层。
建议:
- 尽量轻量
- 尽量做过渡,不做大面积复杂动画
- 高优先级字段做细微跃迁即可
---
## 7.3 反馈动画
应继续走:
- `FeedbackDirector`
- `UIEffectDirector`
这条线后续很适合继续统一:
- 哪个事件触发什么动画
- 持续多久
- 是否叠加 sound / haptics
---
## 8. 实施顺序建议
不建议一口气铺太多动画。
推荐顺序:
1. `打点成功动画体系`
2. `目标点状态动画体系`
3. `HUD 数字与状态过渡`
4. `锁定 / 自动转图状态动画`
5. `危险 / 高压反馈动画`
6. 最后再做页面微交互动画
---
## 9. 第一阶段建议任务
如果下一步准备开始做动画,建议第一阶段先只收下面这些:
### 任务 1
整理一份动画事件字典:
- 哪些事件会触发动画
- 动画归属哪一层
- 对应目的是什么
### 任务 2
把打点成功链系统化:
- 点位变化
- HUD 跳变
- pulse
- toast
### 任务 3
统一目标点状态动画:
- 当前目标
- 可打点
- 已完成
- 已跳过
### 任务 4
补一个动画总开关:
- `animationsEnabled`
- `animationLevel`
---
## 10. 当前阶段结论
当前项目已经具备做动画体系的基础。
最正确的方向不是继续零散补动效,而是:
- 先按层组织动画
- 再按事件驱动
- 最后再做配置化和降级
一句话总结:
**后续动画建设应以“打点成功”和“目标状态”两条高频体验为起点,把动画正式纳入现有架构,而不是继续做零散样式补丁。**

357
doc/animation-dictionary.md Normal file
View File

@@ -0,0 +1,357 @@
# 动画字典 v1
## 1. 目的
这份文档用于统一说明:
- 当前系统里**已经存在**的动画触发点
- 哪些事件**适合继续补动画**
- 每个动画应该归属到哪一层
- `lite` 模式下哪些动画应该保留,哪些应该降级
一句话:
**动画字典 = 事件到动画效果的映射表。**
它的价值是:
- 防止动画实现越来越散
- 方便做性能分级
- 方便新玩法复用已有动画语言
- 方便以后做动画 profile / theme
---
## 2. 分层约定
当前动画建议分成 4 层:
### 2.1 地图空间动画
作用在地图对象本身。
例如:
- 当前目标点 pulse
- 可打点外环
- 已完成点 settle
- 已跳过点灰化
- 迷雾 reveal
- 金币收集爆点
归属:
- `MapPresentation`
- `MapScene`
- `WebGL renderer`
### 2.2 HUD 动画
作用在底部 HUD、数字、状态块。
例如:
- 进度跃迁
- 点距变化
- 速度/里程/心率数字轻量过渡
归属:
- 页面层 `WXML / WXSS`
- `MapEngine` 只负责下发状态类名
### 2.3 UI / Feedback 动画
作用在全局提示、局部闪光、反馈层。
例如:
- 顶部提示条出现
- 局部 stage flash
- 成功提示
- 错误/警示提示
归属:
- `feedbackConfig`
- `UiEffectDirector`
- `FeedbackDirector`
- 页面层消费
### 2.4 微交互动画
作用在按钮、弹层、设置面板等。
当前优先级最低。
---
## 3. 已实现动画字典
下面这一部分是当前已经落到代码里的内容。
| 事件/状态 | 地图空间动画 | HUD 动画 | UI/Feedback 动画 | lite 保留 |
|---|---|---|---|---|
| `control_ready` | 当前目标点更亮、ready 强调 | 打点相关状态切换 | 无 | 是 |
| `control_completed:start` | 开始点完成 settle 外环 | 进度/点距轻反馈 | 轻量成功反馈 | 是 |
| `control_completed:control` | 普通检查点完成 settle 外环 | 进度跃迁、点距动效 | 轻量地图 flash / pulse | 是 |
| `control_completed:finish` | 终点完成 settle 外环更明显 | 进度完成反馈 | finish 级反馈更强 | 是 |
| `control_skipped` | 当前点灰化、斜杠标记 | 当前目标推进 | 轻提示(较轻) | 是 |
| `target_changed` | 当前目标高亮切换 | 目标相关 HUD 状态切换 | 无 | 是 |
| `heart_rate_updated` | 无 | 心率数字过渡 | 无 | 是 |
| `timer_tick` | 无 | 计时器数字轻量过渡 | 无 | `lite` 可降级 |
| `mileage_updated` | 无 | 里程数字过渡 | 无 | `lite` 可降级 |
| `speed_updated` | 无 | 速度数字过渡 | 无 | `lite` 可降级 |
| `panel_progress_changed` | 无 | 进度数字 / 状态切换 | 无 | 是 |
| `map_pulse` | 地图局部 pulse | 无 | 可伴随轻 stage flash | `lite` 简化 |
| `stage_flash` | 无 | 无 | 局部屏幕闪光 | `lite` 可关闭 |
---
## 4. 推荐新增动画字典
下面这些是**非常适合继续补齐**的事件。
### 4.1 高优先级
#### `session_started`
建议效果:
- 地图:起点轻脉冲或开场吸附感
- HUD开始态切换
- UI顶部提示条轻弹入
原因:
- 这是对局开始的第一感受
- 很适合统一“进入比赛状态”的语言
#### `target_transition`
指:
- 一个点完成后
- 下一个目标点接管当前目标状态
建议效果:
- 地图:旧点 settle新点接管 pulse
- HUD目标进度轻切换
- UI可以不额外做
原因:
- 当前这条链已经有基础,但还不够“连续”
- 这是顺序赛最值得打磨的高频体验
#### `control_skipped`
建议效果:
- 地图:当前点灰化过渡更明显
- HUD提示“已跳过 x 号点”
- UI轻量 skip toast / 轻闪光
原因:
- 逻辑已经有
- 状态已经有
- 动画语言还不够完整
---
### 4.2 中优先级
#### `gps_lock_changed:on`
建议效果:
- 地图:回中吸附感
- HUD
- UI轻提示“已锁定定位点”
#### `gps_lock_changed:off`
建议效果:
- 地图:无
- HUD
- UI轻提示“已解除锁定”
#### `center_scale_ruler_changed`
建议效果:
- UI比例尺淡入/淡出
原因:
- 比例尺是工具型功能
- 不需要强动画,但可以更自然
#### `north_reference_changed`
建议效果:
- UI小提示
- 指北针顶部文案轻切换
---
### 4.3 下一阶段重点
#### `heart_rate_zone_changed`
建议效果:
- HUD颜色平滑切换
- UI边缘呼吸 / 警示
- 地图:一般不建议直接做
原因:
- 未来心率玩法、危险状态会很依赖这条
#### `danger_state_changed`
适用于未来:
- 幽灵追逐
- 危险区
- 高压状态
建议效果:
- UI边缘呼吸
- HUD局部强调
- 地图:危险区波纹
#### `zone_entered / zone_cleared`
适用于未来:
- 区域玩法
- 迷雾玩法
- 领地玩法
建议效果:
- 地图:区域高亮 / reveal
- HUD区域状态变化
- UI轻量提示
---
## 5. 不建议过度动画化的节点
下面这些不建议做太重:
- 每次 GPS 更新
- 每次地图缩放
- 每次地图拖动
- 每次 compass heading 变化
- 每次 telemetry 微小变化
原因:
- 高频
- 成本高
- 极易拖垮低端机
原则:
**高频输入只做平滑,不做明显动画。**
---
## 6. 动画等级建议
当前已有两级:
- `standard`
- `lite`
建议动画字典里每项都标清楚在 `lite` 下如何处理。
### `standard`
- 完整 pulse
- 完整 settle
- HUD 数字过渡正常
- 局部 flash 保留
### `lite`
- pulse 层数减少
- settle 更轻
- HUD 数字动画缩短或关闭
- flash 关闭或极轻
---
## 7. 下一步推荐落地顺序
建议按这个顺序继续推进:
1. `session_started`
2. `target_transition`
3. `control_skipped`
4. `gps_lock_changed`
5. `heart_rate_zone_changed`
6. `danger_state_changed`
---
## 8. 执行原则
后面继续做动画时,遵循这几条:
### 8.1 先看事件,再决定动画
不要先想“做个好看的效果”,而是先问:
- 哪个事件需要被用户感知?
- 这个事件最适合地图 / HUD / UI 哪一层?
### 8.2 只强化高价值反馈
优先强化:
- 开始
- 打点成功
- 目标切换
- 跳点
- 危险状态
### 8.3 低端机优先
任何新增动画都要先问:
- `lite` 下要不要保留?
- 不保留是否影响信息传达?
### 8.4 动画不替代状态
动画只做状态的表现,不能替代状态本身。
也就是:
- 先有状态
- 再有动画
---
## 9. 阶段结论
当前动画系统已经具备:
- 主链
- 分层
- 高价值基础动画
- 性能等级
下一步的重点不是继续零散加动效,而是:
**按动画字典逐项补齐高频体验链。**

View File

@@ -0,0 +1,163 @@
# 动画接入规格模板
## 1. 用途
这份模板用于:
- 设计公司交付动画时填写
- 开发接入前确认规格
- 作为动画实现与验收依据
建议:一个动画一条记录。
---
## 2. 基础信息模板
```md
动画 ID
动画名称:
所属页面/模块:
所属层级:地图空间 / HUD / UI反馈 / 过场
优先级:高 / 中 / 低
触发事件:
触发条件:
是否高频:
作用对象:
起始状态:
结束状态:
动画形式:程序动画 / Lottie / 序列帧 / 视频 / 其他
资源文件:
时长:
延迟:
缓动:
是否循环:
是否可中断:
重复触发策略:覆盖 / 忽略 / 重启 / 排队
standard 表现:
lite 表现:
是否允许关闭:
设计说明:
开发备注:
验收标准:
```
---
## 3. 字段说明
### 动画 ID
要求:
- 全局唯一
- 使用英文或稳定标识
例如:
- `control_complete_flash`
- `target_ready_pulse`
- `session_intro_banner`
### 所属层级
建议四选一:
- 地图空间
- HUD
- UI反馈
- 过场
### 触发事件
必须明确写出触发它的事件。
例如:
- `control_completed:control`
- `control_skipped`
- `session_started`
- `heart_rate_zone_changed:red`
### 重复触发策略
必须提前约定:
- `覆盖`
- `忽略`
- `重启`
- `排队`
否则高频触发时容易行为不一致。
### standard / lite
每个动画都必须给出两档建议。
例如:
- `standard`:完整 pulse + 外环
- `lite`:保留单层 pulse去掉外环
---
## 4. 推荐填写示例
```md
动画 IDcontrol_complete_flash
动画名称:打点成功轻闪光
所属页面/模块:地图页
所属层级UI反馈
优先级:高
触发事件control_completed:control
触发条件:普通检查点打点成功
是否高频:是
作用对象:地图主舞台
起始状态:正常地图状态
结束状态:恢复正常地图状态
动画形式:程序动画
资源文件:无
时长320ms
延迟0ms
缓动ease-out
是否循环:否
是否可中断:是
重复触发策略:重启
standard 表现:淡白色局部 flash透明度较明显
lite 表现:透明度降低 50%,时长缩短到 220ms
是否允许关闭:是
设计说明:突出“完成打点”的即时成功感
开发备注:通过 UiEffectDirector 下发 stageFx
验收标准:普通打点时稳定触发,连续打点不拖尾
```
---
## 5. 对设计公司的要求
建议以后明确告诉设计公司:
- 不只要演示稿
- 必须提供参数规格
- 必须提供资源清单
- 必须说明低配降级方案
---
## 6. 结论
这份模板的目的是把动画从“视觉稿”变成“工程规格”。
只有规格明确,程序才能稳定接入。

View File

@@ -0,0 +1,268 @@
# 动画接入工作流
## 1. 目的
这份文档用于说明:
- 设计公司提供的动画稿,应该如何转成程序可执行的内容
- 我方应该如何组织这类工作
- 动画接入前、中、后的协作方式
一句话:
**不要把动画稿直接塞进程序,要先把它转成“工程规格”。**
---
## 2. 核心原则
### 2.1 交付的是规格,不只是效果
设计公司不能只给:
- 视频
- GIF
- 一张效果图
至少还需要给:
- 动画名称
- 触发时机
- 作用对象
- 起始状态
- 结束状态
- 时长
- 延迟
- 缓动
- 是否循环
- 是否可中断
- 资源形式
- 降级方案
### 2.2 先分类,再决定技术实现
不是所有动画都适合用同一种实现方式。
必须先判断它属于:
- 地图空间动画
- HUD 动画
- UI / Feedback 动画
- 过场 / Cutscene 动画
### 2.3 先看信息价值,再看视觉效果
优先实现:
- 帮助用户理解状态的动画
- 引导视线的动画
- 强化反馈的动画
延后实现:
- 纯装饰性动画
- 对玩法理解没有帮助的长演出
### 2.4 低端机优先
任何动画都要先问:
- `lite` 模式下怎么降级?
- 不做时是否影响信息表达?
---
## 3. 推荐流程
建议每个动画都按下面这条流程走:
### 第一步:设计交付
设计提供:
- 动画视觉稿
- 动画规格参数
- 资源文件
- 适配说明
### 第二步:整理进动画字典
先把动画纳入事件字典,而不是直接实现。
例如:
- 什么事件触发它
- 作用在哪一层
- `standard / lite` 如何处理
### 第三步:技术归类
判断该动画由哪一层承接:
- `MapPresentation / Renderer`
- 页面 `WXML / WXSS`
- `UiEffectDirector`
- 单独的 `Cutscene`
### 第四步:选择实现方式
例如:
- 纯程序动画
- 序列帧 / sprite
- Lottie
- 视频
### 第五步:做性能分级
明确:
- `standard` 怎么做
- `lite` 怎么做
- 是否允许完全关闭
### 第六步:联调与验收
至少验证:
- 触发正确
- 状态切换正确
- 多次连续触发是否正常
- 可中断行为是否正确
- 低端机是否流畅
---
## 4. 动画实现方式建议
### 4.1 纯程序动画
适合:
- 地图 pulse
- 数字过渡
- HUD 状态变化
- 按钮反馈
优点:
- 最轻
- 最稳定
- 最适合小程序
### 4.2 Lottie / 骨骼类动画
适合:
- 局部高质量 UI 动效
- 成功反馈
- 勋章 / 特殊提示
风险:
- 小程序里复杂 Lottie 可能卡
### 4.3 序列帧 / Sprite
适合:
- 爆点
- 金币收集
- 局部地图特效
风险:
- 占内存
- 占包体
### 4.4 视频 / 过场动画
适合:
- 章节开场
- 比赛结果页
- 特殊演出
不适合:
- 地图过程中的高频反馈
---
## 5. 当前项目里的推荐落点
### 地图空间动画
放在:
- `MapPresentation`
- `MapScene`
- `WebGL Renderer`
### HUD 动画
放在:
- 页面层 `WXML / WXSS`
### Feedback 动画
放在:
- `feedbackConfig`
- `UiEffectDirector`
- `FeedbackDirector`
### 过场动画
建议未来独立一层:
- `Transition`
- `Cutscene`
---
## 6. 当前建议的协作方式
以后和设计公司协作时,建议这样分工:
### 设计公司负责
- 视觉稿
- 动画节奏
- 参数规格
- 资源文件
- 降级建议
### 我方负责
- 事件归类
- 技术选型
- 动画字典维护
- 分层接入
- 性能分级
---
## 7. 推荐的工作顺序
建议以后优先接这类动画:
1. 打点成功
2. 目标切换
3. 跳点
4. 高压 / 危险反馈
5. 章节开场 / 过场动画
---
## 8. 结论
动画接入的正确方式不是:
**拿到设计稿 -> 直接写代码**
而是:
**拿到设计稿 -> 转成动画规格 -> 纳入动画字典 -> 选技术实现 -> 做性能分级 -> 再接程序**

View File

@@ -0,0 +1,192 @@
# 动画体系阶段性小结
## 1. 当前定位
目前动画体系已经从“页面里临时加 class”的阶段进入了**有主链、有分层、有性能分级**的阶段。
当前主链可以概括为:
- 事件触发
- `feedbackConfig`
- `UiEffectDirector`
- `FeedbackDirector`
- `MapEngine`
- 页面层 / 渲染层消费
也就是:
**事件 -> 效果配置 -> 宿主提交 -> 页面 / Renderer 落地**
这说明动画已经不再是零散实现,而开始进入架构化管理。
---
## 2. 已经完成的内容
### 2.1 HUD 动效
已经完成:
- 打点成功后的 `进度` 动效
- 打点成功后的 `点距` 动效
- HUD 数字轻量过渡:
- 计时
- 里程
- 速度
- 心率
这些动效已经接入正式链路,不是页面单独临时处理。
### 2.2 地图空间动画
已经完成:
- 当前目标点状态强调
- 可打点状态强调
- 已完成点状态过渡
- 已跳过点灰态与标记
- 开始点 / 终点完成后的 settle 外环
- 轻量地图 pulse
### 2.3 局部 UI / Stage 动效
已经完成:
- 轻量 stage flash
- 顶部提示和局部反馈的基础动画承载链
### 2.4 动画性能分级
已经完成 2 级动画分级:
- `standard`
- `lite`
当前 `lite` 的主要策略包括:
- 减少 pulse 层数
- 降低几何分段
- 降低渲染动画频率
- 关闭部分 HUD 动画
- 关闭或减弱某些 stage/UI 动效
这意味着动画体系已经开始考虑**低端机表现**,不是只追求效果。
---
## 3. 当前架构上的价值
动画体系现在已经带来了几个明确收益:
- 动效不再散落在多个页面细节里
- 高频状态变化有了统一反馈语言
- 地图状态和 HUD 状态开始形成一致体验
- 性能分级已经进入体系,可服务低端机
从架构角度看,这意味着:
**动画已经成为正式能力层,而不是临时视觉补丁。**
---
## 4. 当前还不够完整的地方
虽然主链已经成型,但当前还没有完全形成“动画字典”和完整 profile 体系。
目前仍然存在这些不足:
- 哪些事件触发哪些动画,还没有整理成统一字典
- 部分高频状态切换还不够连续
- `跳点` 已有逻辑和状态,但动画语言还不完整
- 危险/高压状态动画还没有正式开始
- 动画 profile 还没有真正配置化
所以当前阶段可以定义为:
**第一阶段后半段:主链已成型,但还需要把高频体验打磨完整。**
---
## 5. 下一阶段最值得做的事情
### 5.1 先整理动画字典
建议先把动画按事件梳理出来,例如:
- `session_started`
- `control_ready`
- `control_completed:start`
- `control_completed:control`
- `control_completed:finish`
- `control_skipped`
- `gps_lock_changed`
- `guidance_state_changed`
- `heart_rate_zone_changed`
并明确每个事件对应:
- 地图动画
- HUD 动画
- UI 动画
- `lite` 下是否保留
这一步是当前最值得优先完成的工作。
### 5.2 补完整“目标状态切换连续感”
继续打磨:
- 当前目标
- 进入可打点
- 打点成功
- 切到下一个目标
让这一整段切换更连贯、更有节奏。
### 5.3 补齐“跳点”动画
建议下一步把跳点也正式纳入动画体系:
- 跳点确认后
- 当前点灰化
- 下一个目标接管强调
- HUD 给出轻量反馈
### 5.4 再做危险 / 高压反馈
这部分适合进入下一阶段:
- 高心率反馈
- 危险区反馈
- 幽灵追逐反馈
- 边缘呼吸 / 紧张感动效
这条线很适合后续玩法扩展。
---
## 6. 建议的实施顺序
推荐继续推进的顺序:
1. 动画字典整理
2. 目标切换连续感补齐
3. 跳点动画补齐
4. 危险 / 高压状态动画
5. 更进一步的配置化 profile
---
## 7. 结论
当前动画体系已经是一个明确的阶段性成果:
- 有主链
- 有分层
- 有高频核心动画
- 有性能分级
接下来最该做的不是“继续零散加动画”,而是:
**把现有能力收成动画字典,并优先打磨目标切换与跳点这两条高频体验链。**

View File

@@ -0,0 +1,162 @@
# 动画接入评审清单
## 1. 用途
这份清单用于在接设计稿或准备开发前,快速判断:
- 这个动画能不能接
- 应该接到哪一层
- 有没有性能风险
- 有没有交付缺口
---
## 2. 设计稿评审
### 2.1 动画目标是否明确
- 这个动画是为了表达什么?
- 它是状态反馈,还是纯装饰?
- 用户不看它,会不会影响理解?
### 2.2 触发条件是否明确
- 由哪个事件触发?
- 是否会高频触发?
- 是否允许重复触发?
### 2.3 交付是否完整
- 是否有参数规格?
- 是否有资源文件?
- 是否有尺寸 / 比例说明?
- 是否有 `lite` 降级说明?
---
## 3. 技术评审
### 3.1 该动画属于哪一层
- 地图空间
- HUD
- UI反馈
- 过场
### 3.2 最合适的实现方式是什么
- 程序动画
- Lottie
- 序列帧
- 视频
### 3.3 是否真的需要资源文件
很多动画其实可以纯程序实现,不需要额外资源。
如果只是:
- pulse
- 渐隐
- 平移
- 数字过渡
优先用程序动画。
---
## 4. 性能评审
### 4.1 是否高频
如果是高频事件,不适合做重动画:
- GPS 更新
- compass heading 更新
- 拖动 / 缩放
- telemetry 微小变化
### 4.2 lite 模式怎么处理
必须明确:
- 保留
- 简化
- 关闭
### 4.3 是否会增加页面层负担
要判断:
- 会不会引入高频 `setData`
- 会不会创建大数组
- 会不会增加持续循环动画
- 会不会增加桥接成本
---
## 5. 交互评审
### 5.1 是否可中断
- 用户切页面怎么办?
- 状态瞬间变化怎么办?
- 连续触发怎么办?
### 5.2 是否会和现有动画冲突
- 同一事件是否已有动画?
- 是否会重复表达同一个信息?
- 是否和现有地图 pulse / HUD 动效叠加过重?
---
## 6. 当前项目特别注意项
### 6.1 地图过程中的动画必须克制
因为当前项目:
- 地图是主舞台
- 低端机性能敏感
- 页面桥接成本高
所以:
- 地图上的高频动画必须轻量
- 尽量减少页面层大范围动画
### 6.2 优先动画化高价值节点
优先做:
- 打点成功
- 目标切换
- 跳点
- 危险 / 高压反馈
延后做:
- 纯装饰性微动效
- 复杂长演出
---
## 7. 验收清单
动画接入完成后,至少确认:
- 触发时机正确
- 结束时机正确
- 多次连续触发稳定
- `standard / lite` 都可用
- 低端机可接受
- 不破坏现有状态链
---
## 8. 结论
动画接入前,只要这份清单里有明显回答不出来的问题,就不应该直接开做。
先补规格,再接程序。

View File

@@ -0,0 +1,416 @@
# 配置驱动应用的后台管理方案建议
本文用于整理当前这类“配置驱动型地图游戏应用”的后台管理建议,面向:
- PostgreSQL 数据库
- Go 中间层
- 后台管理系统
- 客户端静态配置发布
目标是解决一个核心问题:
**配置文件会越来越大,如何在后台可维护、可复用、可审核、可发布、可回滚。**
---
## 1. 总体原则
最稳的方案不是“数据库直接存一大份 `game.json` 给客户端读”,而是:
**数据库管理编辑态,发布时编译成运行态静态配置文件。**
也就是两套形态:
### 编辑态
- 存在 PostgreSQL
- 适合后台表单编辑
- 支持版本管理
- 支持对象复用
- 支持审核、比对、回滚
### 运行态
- 由 Go 中间层装配生成
- 输出为静态 JSON
- 上传到 OSS/CDN
- 客户端只读取发布后的静态配置
这条路线最适合当前项目。
---
## 2. 不建议的做法
不建议把后台做成:
- 一张表里存一个超大的 `jsonb`
- 后台直接编辑整份 `game.json`
- 客户端通过 API 动态拼装所有配置
这样后面会遇到这些问题:
- 配置复用困难
- diff 难看
- 回滚困难
- 审核困难
- 局部编辑体验差
- 客户端运行态不稳定
---
## 3. 推荐的核心对象
建议后台和数据库先固定这 5 个核心对象:
### `Map`
地图底座。
负责:
- 瓦片资源
- meta 信息
- 磁偏角
- 初始视角
### `Playfield`
玩法空间对象定义。
负责:
- KML 来源
- 控制点覆盖信息
- 区域对象
- 危险区
- 采集物
- 起终点信息
说明:
- `Playfield` 是上位概念
- `course` 只是其中一种特化形式
### `GameMode`
玩法模板。
负责:
- 顺序赛
- 积分赛
- 后续幽灵赛、迷雾赛、金币赛等
也就是:
- `game.mode`
- `session`
- `punch`
- `scoring`
- `guidance`
- `visibility`
- `finish`
- `telemetry`
- `feedback`
### `ResourcePack`
资源包。
负责:
- 音效 profile
- 文创内容
- 图标
- HUD 主题
- 动效 profile
### `Event`
最终活动实例。
负责引用:
- 一个 `Map`
- 一个 `Playfield`
- 一个 `GameMode`
- 一个 `ResourcePack`
并允许少量活动级覆盖。
一句话:
**Event = Map + Playfield + GameMode + ResourcePack + EventOverrides**
---
## 4. 数据库建模建议
建议每个核心对象都分成:
- 主表
- version 表
### 4.1 主表
主表存稳定元信息:
- `id`
- `slug`
- `name`
- `status`
- `current_version_id`
- `created_at`
- `updated_at`
### 4.2 version 表
version 表存每个版本的具体内容:
- `id`
- `parent_id`
- `version_no`
- `schema_version`
- `content_jsonb`
- `created_by`
- `created_at`
- `change_note`
### 4.3 推荐表
建议至少有:
- `maps`
- `map_versions`
- `playfields`
- `playfield_versions`
- `game_modes`
- `game_mode_versions`
- `resource_packs`
- `resource_pack_versions`
- `events`
- `event_versions`
---
## 5. 为什么要做版本表
版本表的价值非常大:
- 支持草稿
- 支持发布版
- 支持 diff
- 支持回滚
- 支持审计
- 支持多人协作
如果没有版本表,后面后台管理一定会越来越难维护。
---
## 6. JSONB 的使用建议
推荐策略是:
- 稳定字段结构化
- 变化快的配置内容放 `jsonb`
例如主表中:
- `slug`
- `name`
- `status`
放结构化列。
而玩法具体配置、资源清单、覆盖字段,放在 `content_jsonb`
这样兼顾:
- 查询效率
- 结构灵活性
- 配置扩展性
---
## 7. 后台编辑方式建议
后台不要直接给运营一个大 JSON 编辑框作为主要方式。
推荐做法:
- 地图编辑页
- Playfield 编辑页
- 玩法规则页
- 资源包页
- 活动编排页
按模块表单化编辑。
最后由 Go 中间层负责装配成最终配置 JSON。
也就是:
**后台是“编辑结构化对象”,不是“手工拼最终运行文件”。**
---
## 8. 发布机制建议
发布时建议按下面流程:
1. 后台选定某个 `Event Version`
2. Go 中间层读取它引用的:
- `Map Version`
- `Playfield Version`
- `GameMode Version`
- `ResourcePack Version`
3. 做装配
4. 做校验
5. 生成最终运行态 JSON
6. 上传 OSS/CDN
7. 记录一条 release
客户端只读:
- 已发布的静态配置 URL
不要让客户端直接查数据库 API 动态拼。
---
## 9. 推荐增加 Release 层
建议增加:
- `event_releases`
字段例如:
- `id`
- `event_id`
- `event_version_id`
- `release_no`
- `manifest_url`
- `published_by`
- `published_at`
- `status`
它的作用:
- 一键回滚
- 客户端锁定某次 release
- 管理历史发布记录
- 灰度验证
---
## 10. Go 中间层建议职责
Go 中间层不要只做 CRUD。
建议它至少承担这 4 类职责:
### 10.1 校验
- schema 校验
- 引用存在校验
- 字段完整性校验
- 规则约束校验
### 10.2 装配
把:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event Overrides`
装配成最终配置结构。
### 10.3 发布
- 生成最终静态 JSON
- 上传到 OSS/CDN
- 记录 release
### 10.4 对比与预览
- 给后台显示 diff
- 给发布前做预览
一句话:
**Go 中间层本质上是配置编译器。**
---
## 11. 校验建议
建议尽量做强校验。
至少包括:
- schemaVersion 合法
- 引用对象存在
- KML 路径存在
- 地图 meta 存在
- 玩法字段完整
以及玩法特定约束,例如:
- 顺序赛必须有 start / finish
- 积分赛 control set 需要 score 或可派生 score
- `punch.radiusMeters > 0`
- `skip.radiusMeters > punch.radiusMeters`
这样能把很多错误挡在发布前。
---
## 12. 和当前静态目录的关系
当前你已经有类似目录:
- `map/`
- `kml/`
- `event/`
这很好,可以继续保留。
建议把它理解成:
- 数据库 = 编辑态
- 这些目录 = 发布产物态
也就是后台发布后Go 中间层继续生成:
- `event/classic-sequential.json`
- `event/score-o.json`
- `map/...`
- `kml/...`
客户端保持现有读取方式不变。
---
## 13. 推荐的后续实施顺序
建议按这个顺序落地:
### 第一步
先建 5 个核心对象模型:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
### 第二步
为每个对象补版本表。
### 第三步
Go 中间层实现“装配成最终 JSON”。
### 第四步
实现“发布到 OSS/CDN”。
### 第五步
后台逐步从 JSON 编辑过渡到模块化表单编辑。
---
## 14. 一句话总结
这类配置驱动应用最稳的后台方案是:
**PostgreSQL 管结构化、可版本化的编辑态对象Go 中间层负责校验、装配和发布;客户端只消费发布后的静态 JSON。**
这样才能做到:
- 可复用
- 可扩展
- 可审核
- 可回滚
- 可稳定运行

View File

@@ -0,0 +1,406 @@
# 配置频繁变更场景下的后台管理方案
本文用于整理一套更适合“配置项变化很频繁”的后台方案。
适用前提:
- 配置驱动型应用
- 游戏规则和字段会持续变化
- PostgreSQL 作为主数据库
- Go 作为中间层
- 客户端最终读取静态 JSON
核心目标是:
**在保证后端稳定的前提下,让前端和玩法配置可以持续快速迭代。**
---
## 1. 核心原则
这版方案的核心思想只有一句:
**后端管理“容器、版本、引用、发布”,不要深度管理每个细字段。**
也就是说:
- 后端负责管理对象关系
- 后端负责管理版本和发布
- 后端负责做基础校验
- 后端尽量不要写死每个玩法里的所有字段细节
---
## 2. 总体结构
推荐分成 3 层:
### 2.1 编辑层
后台管理系统面向的是“对象”,不是最终运行文件。
建议核心对象仍然是:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
### 2.2 装配层
Go 中间层负责:
- 读取对象
- 合并引用
- 基础校验
- 生成最终运行态配置
### 2.3 发布层
装配完成后,生成静态 JSON 上传到 OSS/CDN。
客户端只读取:
- 已发布的静态配置
---
## 3. 数据库存什么
数据库建议只存两类数据:
### 3.1 稳定元信息
结构化列保存:
- `id`
- `slug`
- `name`
- `status`
- `current_version_id`
- `created_at`
- `updated_at`
### 3.2 易变配置内容
使用 `jsonb` 保存:
- `content_jsonb`
也就是说,每个对象都建议拆成:
- 主表
- version 表
例如:
- `maps` / `map_versions`
- `playfields` / `playfield_versions`
- `game_modes` / `game_mode_versions`
- `resource_packs` / `resource_pack_versions`
- `events` / `event_versions`
这套结构最适合承接频繁变化的配置字段。
---
## 4. 为什么要用 version 表
配置频繁变化时,版本表非常重要:
- 支持草稿
- 支持当前版
- 支持发布版
- 支持历史回滚
- 支持 diff
- 支持审计
如果没有版本表,配置演进到后面会越来越难控。
---
## 5. 后端真正该负责的内容
后端建议强管理下面这 4 件事:
### 5.1 对象关系
例如:
- Event 引用哪个 Map
- Event 引用哪个 Playfield
- Event 引用哪个 GameMode
- Event 引用哪个 ResourcePack
### 5.2 版本机制
例如:
- 草稿
- 当前版本
- 发布版本
- 回滚历史
### 5.3 基础校验
只做真正稳定的校验:
- 顶层结构是否合法
- 引用是否存在
- schemaVersion 是否兼容
- 必填对象是否齐全
### 5.4 发布装配
把编辑态对象装配成最终运行态 JSON。
---
## 6. 后端不要过度负责的内容
后端不要把下面这些写死:
- 每个玩法的小规则字段
- 每个 HUD 开关
- 每个实验性参数
- 每个视觉细节配置
- 每次快速迭代里新增的小配置项
这些变化太频繁,应该优先放在 `jsonb` 内容里,由前端消费。
一句话:
**后端不要成为“所有细字段的业务解释器”。**
---
## 7. 配置校验的推荐分层
建议分成 3 层校验。
### 7.1 通用结构校验
所有配置都校验:
- `schemaVersion`
- `map`
- `playfield`
- `game`
### 7.2 公共字段校验
只校验稳定公共字段,例如:
- `game.mode` 必须存在
- `game.punch.radiusMeters > 0`
### 7.3 玩法校验器
`game.mode` 分发,例如:
- `classic-sequential` validator
- `score-o` validator
但这里有个重要原则:
**未识别字段默认允许透传。**
也就是说:
- 不要因为多了一个新字段就发布失败
- 只有破坏基础结构或关键规则时才拦截
---
## 8. 后台编辑策略
后台不要追求“一开始把所有字段都做成完美表单”。
建议分成两类:
### 8.1 稳定字段
做正式表单:
- 名称
- 状态
- 模式
- 地图引用
- Playfield 引用
- 资源包引用
- 关键半径
- 是否必须起点/终点
### 8.2 易变字段
先保留模块化 JSON 编辑区:
- `game.sequence`
- `game.guidance`
- `game.visibility`
- `game.feedback`
- `playfield.controlOverrides`
- 其他试验性字段
等这些字段稳定后,再逐步升级成正式表单。
这会比一开始硬做全表单更现实。
---
## 9. 推荐的发布模型
建议增加一层:
- `event_releases`
推荐字段:
- `id`
- `event_id`
- `event_version_id`
- `release_no`
- `manifest_url`
- `published_by`
- `published_at`
- `status`
发布流程:
1. 后台选择某个 `event_version`
2. Go 层装配最终配置
3. Go 层校验
4. 上传 OSS/CDN
5. 写入 release 记录
客户端只消费:
- 某次 release 对应的静态 JSON
---
## 10. Go 中间层的职责
Go 中间层建议承担 4 类职责:
### 10.1 装配器
负责把:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event Overrides`
装配成最终运行态配置。
### 10.2 校验器
负责:
- 通用校验
- 公共字段校验
- 按玩法分发的插件式校验
### 10.3 发布器
负责:
- 生成静态 JSON
- 上传 OSS/CDN
- 写入 release
### 10.4 预览 / Diff
负责:
- 给后台看发布前的预览
- 对比不同版本差异
一句话:
**Go 中间层本质上是配置编译器,不只是 CRUD 服务。**
---
## 11. 这套方案为什么适合当前项目
因为当前项目的真实情况就是:
- 配置字段变化快
- 玩法在持续演进
- 前端经常需要新增规则项
- 客户端更适合消费静态配置
如果后端每次都跟着细字段改表、改结构、改接口,成本会非常高。
这套方案可以避免:
- 频繁 migration
- 后端字段爆炸
- 每次小字段变更都改很多 Go 代码
---
## 12. 推荐你现在就定死的原则
### 原则 1
**数据库结构稳定,配置内容灵活。**
### 原则 2
**后端强管理对象关系,不强管理每个细字段。**
### 原则 3
**未知字段默认允许透传。**
### 原则 4
**客户端消费细规则,后端负责发布与校验。**
### 原则 5
**最终运行态永远是静态 JSON。**
---
## 13. 和当前目录结构的关系
如果当前静态目录是:
- `map/`
- `kml/`
- `event/`
这套可以继续保留。
理解方式是:
- 数据库 = 编辑态
- Go 装配 = 发布态转换
- OSS 目录 = 运行态产物
也就是说后台发布后,继续生成:
- `event/classic-sequential.json`
- `event/score-o.json`
- `map/...`
- `kml/...`
客户端现有读取逻辑无需推翻。
---
## 14. 推荐实施顺序
建议按下面顺序推进:
### 第一步
先建 5 个核心对象:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
### 第二步
为每个对象补 version 表。
### 第三步
Go 中间层先做最小装配功能。
### 第四步
实现发布到 OSS/CDN。
### 第五步
后台逐步把稳定字段表单化。
### 第六步
把易变字段继续保留为 JSON 编辑区。
---
## 15. 一句话总结
这套更适合频繁变化配置项的后台方案是:
**PostgreSQL 存“版本化对象 + jsonb 内容”Go 中间层做“装配 + 校验 + 发布”,客户端只读静态发布结果。**

View File

@@ -0,0 +1,96 @@
# 沟通协作建议
这份文档用于约定后续在 UI 微调、交互细改、规则补充时,怎样沟通最有效,减少来回修改。
## 1. 需求描述的推荐格式
后续尽量按下面 4 个点描述需求:
### 改动对象
明确指出这次只改什么。
例如:
- 比例尺和指北针顶部数字之间的距离
- 某个按钮的高亮状态
- 某条提示文案
### 不要改
明确指出哪些相邻元素不要动。
例如:
- 不要改顶部数字和小箭头之间的距离
- 不要动比例尺刻度算法
- 不要改地图引擎逻辑
### 目标效果
说明你想达到什么视觉或交互结果。
例如:
- 更靠近一点
- 改成 4 到 5 像素的空隙
- 只在可点击时高亮
### 验证标准
说明你用什么标准判断“改对了”。
例如:
- 看起来贴近,但不要重叠
- 指北针仍然盖住比例尺
- 缩放时要实时变化,不要手势结束后再跳
## 2. 推荐的需求表达模板
可以直接按这个模板发:
```text
改动对象:
不要改:
目标:
验证标准:
```
例如:
```text
改动对象:比例尺和顶部角度数字之间的距离
不要改:顶部角度数字和小箭头之间的距离
目标:再近一点
验证标准:看起来大约 4~5px不重叠
```
## 3. 为什么这样最有效
很多来回修改,通常不是功能做不了,而是:
- 一次需求里混了两层甚至三层改动
- 没说清楚“哪块不要动”
- 验收标准只有感觉,没有边界
一旦把“改什么”和“不要改什么”拆开,误改概率会明显下降。
## 4. 开发执行的约定
后续默认按下面方式执行:
- 先复述这次只改哪一层
- 真实执行时只改这一层
- 不顺手带改别的部分
- 改完明确说明:
- 改了什么
- 没改什么
## 5. 最适合哪些场景
这套方式尤其适合:
- UI 间距微调
- 按钮状态和图标切换
- HUD 显示调整
- 地图表现修正
- 规则字段补充
## 6. 一句话原则
后续最有效的协作方式是:
**需求把边界说死,修改一次只动一层。**

View File

@@ -0,0 +1,212 @@
# 罗盘问题排查记录
## 背景
本项目在微信小程序中使用罗盘驱动:
- 指北针针头
- 指北针顶部角度数字
- `heading-up` 自动转图
在一次围绕顶部提示窗、传感器显示链和性能优化的修改后,出现了以下问题:
- iOS 端偶发正常,偶发异常
- Android 端罗盘长期无样本
- 指北针不转
- `heading-up` 自动转图一起失效
## 最终结论
这次问题的主因不是算法本身,而是:
**Android 微信环境下,罗盘监听需要被持续保活;之前将多处看似冗余的 `compassController.start()` 清理掉后Android 的罗盘样本链被破坏了。**
也就是说:
- iOS 对罗盘监听更宽容
- Android 对罗盘监听更脆弱
- 之前稳定,不是因为链路更“干净”,而是因为老代码里存在一条实际有效的“罗盘保活链”
## 现象总结
### 失效期
- Android 调试面板里 `Compass Source``无数据`
- iOS 仍可能有 `罗盘` 样本
- 若强行用 `DeviceMotion` 兜底,会出现:
- 指针会转
- 但方向不准
- 自动转图方向错误
### 恢复后
- Android `Compass Source` 恢复为 `罗盘`
- 指北针针头恢复
- 顶部角度数字恢复
- `heading-up` 恢复
## 误判过的方向
以下方向在本次排查中都被考虑过,但最终不是根因或不是主要根因:
### 1. `DeviceMotion` 兜底方案
问题:
- `DeviceMotion` 可以给出设备姿态角
- 但不能稳定代替“指向北”的绝对罗盘
- 用它兜底会导致:
- 能转
- 但方向明显不准
结论:
**`DeviceMotion` 不能作为正式指北针来源。**
### 2. 加速度计 / 其他传感器互斥
曾排查:
- `Accelerometer`
- `Gyroscope`
- `DeviceMotion`
- `Compass`
结论:
- 加速度计在当前微信 Android 环境下不稳定,已放弃
- 但这不是这次罗盘彻底失效的主因
### 3. 算法问题
曾尝试调整:
- 角度平滑
- 设备方向单位解释
- motion fallback 算法
结论:
这些会影响“顺不顺”、“准不准”,但**不能解释 Android 完全无罗盘样本**。
## 真正修复的方法
将之前被清理掉的多处 `this.compassController.start()` 恢复回去。
这些调用点主要分布在:
- `commitViewport(...)`
- `handleTouchStart(...)`
- `animatePreviewToRest(...)`
- `normalizeTranslate(...)`
- `zoomAroundPoint(...)`
- `handleRecenter(...)`
- `handleRotateStep(...)`
- `handleRotationReset(...)`
这些调用在代码审美上看起来像“重复启动”,但在 Android 微信环境里,它们实际上承担了:
**重新拉起 / 保活罗盘监听**
的作用。
## 当前工程判断
本项目当前应当采用以下原则:
### 1. 罗盘主来源只使用 `Compass`
不要再让:
- `DeviceMotion`
- 其它姿态角
参与正式指北针和自动转图的主方向链。
### 2. `DeviceMotion` 只保留为辅助或调试输入
可用于:
- 调试面板显示
- 设备姿态观察
- 未来原生端姿态融合参考
但不要直接驱动指北针。
### 3. Android 端罗盘需要保活
后续不要再把这些 `compassController.start()` 当成纯冗余逻辑随意清掉。
如果要优化代码,应该:
- 保留现有行为
- 将其收口为有明确语义的方法
例如:
- `ensureCompassAlive()`
- `refreshCompassBinding()`
而不是直接删掉。
## 与生命周期相关的硬约束
以下约束必须保持:
### 单实例
页面层必须保证任意时刻只有一个 `MapEngine` 活跃实例。
### 完整销毁
`MapEngine.destroy()` 中必须完整执行:
- `compassController.destroy()`
- 其它传感器 `destroy()`
防止旧监听残留。
### 调试状态不应影响罗盘主链
调试面板开关不应再控制:
- 罗盘是否启动
- 罗盘是否停止
否则容易再次引入平台差异问题。
## 推荐保留的调试字段
以下字段建议长期保留,便于后续定位:
- `Compass Source`
- `sensorHeadingText`
- 顶部角度数字
- `heading-up` 开关状态
其中 `Compass Source` 至少应显示:
- `罗盘`
- `无数据`
避免再次将问题误判为算法问题。
## 后续优化建议
如果后面要继续优化这段代码,推荐方向是:
### 可做
- 将分散的 `compassController.start()` 收口成命名明确的方法
- 为 Android 罗盘链补一层更可读的“保活机制”注释
- 保留当前稳定行为前提下做重构
### 不建议
- 再次移除这些重复 `start()` 调用
-`DeviceMotion` 正式兜底指北针
- 让调试开关影响罗盘主链启动
## 一句话经验
**在微信小程序里Android 罗盘监听的稳定性比 iOS 更脆;某些看似冗余的 `start()` 调用,实际是平台兼容补丁,不应该在没有真机回归的情况下清理。**

View File

@@ -0,0 +1,415 @@
# 默认配置模板文档(当前实现版)
本文档提供一份 **当前客户端可直接使用的默认配置模板**
目标是:
- 给服务端/后台一个稳定的起步模板
- 保证即使只填最少字段,也能正常跑起来
- 随开发持续补充和维护
说明:
- 本模板优先保证“可运行”
- 高级字段可以逐步补
- 文创内容和点击内容也已经纳入模板
---
## 1. 顶层默认模板
```json
{
"schemaVersion": "1",
"version": "2026.03.27",
"app": {
"id": "sample-event-001",
"title": "示例活动",
"locale": "zh-CN"
},
"map": {
"tiles": "../map/lxcb-001/tiles/",
"mapmeta": "../map/lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
},
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "../kml/lxcb-001/10/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {},
"metadata": {
"title": "默认路线",
"code": "default-001"
}
},
"game": {
"mode": "classic-sequential",
"rulesVersion": "1",
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false,
"maxDurationSec": 5400
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 5,
"requiresFocusSelection": false
},
"sequence": {
"skip": {
"enabled": false,
"radiusMeters": 30,
"requiresConfirm": true
}
},
"scoring": {
"type": "score",
"defaultControlScore": 10
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"finish": {
"finishControlAlwaysSelectable": false
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 2. 顺序赛推荐默认模板
```json
{
"schemaVersion": "1",
"version": "2026.03.27",
"app": {
"id": "sample-classic-001",
"title": "顺序赛示例",
"locale": "zh-CN"
},
"map": {
"tiles": "../map/lxcb-001/tiles/",
"mapmeta": "../map/lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
},
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "../kml/lxcb-001/10/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {
"start-1": {
"title": "比赛开始",
"body": "从这里出发,先熟悉地图方向。",
"autoPopup": true,
"once": true,
"priority": 1,
"clickTitle": "起点说明",
"clickBody": "点击起点可再次查看起跑说明。"
},
"control-1": {
"title": "第一检查点",
"body": "完成这个点后沿主路继续前进。",
"autoPopup": true,
"once": false,
"priority": 1,
"clickTitle": "第一检查点",
"clickBody": "点击查看该点位的补充说明。"
},
"finish-1": {
"title": "比赛结束",
"body": "恭喜完成本次路线。",
"autoPopup": true,
"once": true,
"priority": 2,
"clickTitle": "终点说明",
"clickBody": "点击终点可再次查看结束说明。"
}
},
"metadata": {
"title": "顺序赛路线示例",
"code": "classic-001"
}
},
"game": {
"mode": "classic-sequential",
"rulesVersion": "1",
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false,
"maxDurationSec": 5400
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 5,
"requiresFocusSelection": false
},
"sequence": {
"skip": {
"enabled": false,
"radiusMeters": 30,
"requiresConfirm": true
}
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"finish": {
"finishControlAlwaysSelectable": false
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 3. 积分赛推荐默认模板
```json
{
"schemaVersion": "1",
"version": "2026.03.27",
"app": {
"id": "sample-score-o-001",
"title": "积分赛示例",
"locale": "zh-CN"
},
"map": {
"tiles": "../map/lxcb-001/tiles/",
"mapmeta": "../map/lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
},
"playfield": {
"kind": "control-set",
"source": {
"type": "kml",
"url": "../kml/lxcb-001/10/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {
"start-1": {
"title": "比赛开始",
"body": "从这里触发,先熟悉地图方向。",
"autoPopup": true,
"once": true,
"priority": 1,
"clickTitle": "积分赛起点",
"clickBody": "点击起点可查看自由打点规则。"
},
"control-1": {
"score": 10,
"clickTitle": "1号点",
"clickBody": "这是一个基础积分点。"
},
"control-2": {
"score": 20,
"autoPopup": false,
"once": true,
"priority": 1,
"clickTitle": "2号点",
"clickBody": "这个点配置成点击查看。"
},
"finish-1": {
"title": "比赛结束",
"body": "恭喜完成本次路线。",
"autoPopup": true,
"once": true,
"priority": 2,
"clickTitle": "终点说明",
"clickBody": "点击终点可再次查看结束说明。"
}
},
"metadata": {
"title": "积分赛控制点示例",
"code": "score-o-001"
}
},
"game": {
"mode": "score-o",
"rulesVersion": "1",
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": false,
"autoFinishOnLastControl": false,
"maxDurationSec": 5400
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 5,
"requiresFocusSelection": false
},
"scoring": {
"type": "score",
"defaultControlScore": 10
},
"guidance": {
"showLegs": false,
"legAnimation": false,
"allowFocusSelection": true
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"finish": {
"finishControlAlwaysSelectable": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 4. 默认逻辑说明
### 4.1 内容展示默认逻辑
- `title/body`
- 未配置时使用系统默认文案
- `clickTitle/clickBody`
- 未配置时回退到 `title/body`
- `autoPopup`
- 默认允许自动弹出
- `once`
- 默认 `false`
- `priority`
- 普通点默认 `1`
- 终点默认 `2`
- 自动打点时:
- 自动打点完成后不自动弹内容
- 点击内容仍可用
### 4.2 玩法默认逻辑
- 顺序赛默认:
- 必须起点
- 必须终点
- 不自动结束
- 跳点默认关闭
- 积分赛默认:
- 必须起点
- 终点可选
- 不自动结束
- 默认分值 `10`
### 4.3 资源默认逻辑
- `audioProfile = default`
- `contentProfile = default`
- `themeProfile = default-race`
---
## 5. 建议维护方式
后续每次配置能力扩展时,建议同步维护:
1. [D:\dev\cmr-mini\config-option-dictionary.md](D:/dev/cmr-mini/doc/doc/config-option-dictionary.md)
2. [D:\dev\cmr-mini\config-default-template.md](D:/dev/cmr-mini/doc/doc/config-default-template.md)
3. [D:\dev\cmr-mini\event\classic-sequential.json](D:/dev/cmr-mini/event/classic-sequential.json)
4. [D:\dev\cmr-mini\event\score-o.json](D:/dev/cmr-mini/event/score-o.json)
这样可以保证:
- 客户端实现
- 服务端配置
- 后台录入
- 联调样例
始终保持一致。

View File

@@ -0,0 +1,587 @@
# 游戏配置文件设计方案(阶段讨论稿)
本文档用于整理当前阶段推荐的配置文件设计方案,供后端、客户端和后台管理设计参考。
目标是让配置真正成为游戏的驱动入口,同时兼顾后续多玩法、多资源、多活动复用。
---
## 1. 设计目标
配置文件系统需要解决以下问题:
- 驱动地图、玩法、资源、调试开关
- 支持顺序赛、积分赛以及后续更多玩法
- 支持将来后台管理系统的内容编排
- 保证地图空间信息与玩法语义分层
- 保证当前阶段可平滑迁移,不推翻已有实现
当前推荐原则:
- 配置只描述,不执行逻辑
- 地图、空间对象、玩法规则、资源包分层
- KML 负责空间底稿,不负责复杂玩法语义
- 主配置先保持单文件,后续再升级为 manifest 组合
---
## 2. 顶层配置结构
当前推荐主入口配置结构如下:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
各层职责如下:
- `app`
活动级或应用级基础信息
- `map`
地图底图和空间底座
- `playfield`
当前玩法使用的空间对象定义
- `game`
当前玩法规则配置
- `resources`
资源包与 profile
- `debug`
调试与开发开关
---
## 3. 为什么不再以 course 作为总抽象
在定向语义里,`course` 是准确术语,表示路线。
但从系统长期扩展看,`course` 并不是所有玩法的上位概念。
例如:
- 顺序赛有明显的 `course`
- 积分赛更像一组控制点与分数
- 金币赛更像可收集点集合
- 幽灵赛可能包含危险区、隐身点、追逐者
- 迷雾赛可能包含 reveal 点、扫描点、区域
因此推荐:
- 将上位内容模型提升为 `playfield`
- `course` 只作为 `playfield.kind` 的一种
例如:
```json
{
"playfield": {
"kind": "course"
}
}
```
或:
```json
{
"playfield": {
"kind": "control-set"
}
}
```
---
## 4. KML 与配置的边界
当前推荐边界非常明确:
### 4.1 KML 负责空间底稿
KML 适合描述:
- 点坐标
- 起点 / 检查点 / 终点
- 顺序号
- 点位名称
- 腿线几何
### 4.2 配置负责玩法解释
配置负责描述:
- 点位分值
- 打点规则
- 显隐规则
- 动态积分
- 道具能力
- 迷雾规则
- 占领规则
- 特殊玩法语义
一句话总结:
**KML 描述空间事实,配置描述玩法解释。**
---
## 5. 推荐的字段结构
### 5.1 `app`
用于活动级基础信息。
示例:
```json
{
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区定向赛",
"locale": "zh-CN"
}
}
```
### 5.2 `map`
用于地图底图与空间底座。
示例:
```json
{
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
}
}
```
### 5.3 `playfield`
用于描述当前玩法使用的空间对象及其来源。
示例:
```json
{
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {},
"metadata": {}
}
}
```
建议后续逐步支持的对象包括:
- `controls`
- `collectibles`
- `zones`
- `hazards`
- `links`
- `spawnPoints`
### 5.4 `game`
用于描述玩法规则。
推荐统一结构如下:
```json
{
"game": {
"mode": "",
"rulesVersion": "1",
"session": {},
"punch": {},
"scoring": {},
"guidance": {},
"visibility": {},
"finish": {},
"telemetry": {},
"feedback": {}
}
}
```
#### `session`
控制一局游戏的流程参数:
- 是否手动开始
- 是否必须打开始点
- 是否必须打结束点
- 是否允许自动结束
- 最大时长
#### `punch`
控制打点规则:
- 打点策略
- 打点半径
- 是否必须选中后打卡
#### `scoring`
控制积分与结算:
- 完成型
- 固定分
- 动态分
#### `guidance`
控制引导方式:
- 是否显示腿线
- 是否显示腿线动画
- 是否允许 focus 选择
#### `visibility`
控制显隐逻辑:
- 是否开始后显示全图
- 是否采用迷雾
#### `finish`
控制结束规则:
- 是否必须打终点
- 是否允许随时结束
#### `telemetry`
控制通用运动信息参数:
- 年龄
- 静息心率
- 体重
#### `feedback`
控制反馈 profile
- 音频
- 震动
- UI 动效
### 5.5 `resources`
用于描述资源 profile。
示例:
```json
{
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
}
}
```
当前阶段建议先保持轻量,后续再逐步拆成资源包 manifest。
### 5.6 `debug`
用于开发和调试相关开关。
示例:
```json
{
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 6. 顺序赛示例配置
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区顺序赛"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91
},
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6
},
"game": {
"mode": "classic-sequential",
"rulesVersion": "1",
"session": {
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false,
"startManually": true
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false
}
}
```
---
## 7. 积分赛示例配置
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区积分赛"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91
},
"playfield": {
"kind": "control-set",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {
"control-1": { "score": 10 },
"control-2": { "score": 20 },
"control-3": { "score": 30 }
}
},
"game": {
"mode": "score-o",
"rulesVersion": "1",
"session": {
"requiresStartPunch": true,
"requiresFinishPunch": false,
"startManually": true
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10,
"requiresFocusSelection": true
},
"guidance": {
"showLegs": false,
"legAnimation": false,
"allowFocusSelection": true
},
"scoring": {
"type": "score"
},
"finish": {
"finishControlAlwaysSelectable": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false
}
}
```
---
## 8. 当前老字段到新结构的迁移建议
### 地图层
- `map` -> `map.tiles`
- `mapmeta` -> `map.mapmeta`
- `declination` -> `map.declination`
### 路线层
- `course` -> `playfield.source.url`
- `CPRadius` -> `playfield.CPRadius`
### 玩法层
- `game.mode` -> `game.mode`
- `game.punchPolicy` -> `game.punch.policy`
- `PunchRadius` -> `game.punch.radiusMeters`
- `game.autoFinishOnLastControl` -> `game.session.autoFinishOnLastControl`
### telemetry 层
- `game.telemetry.age` -> `game.telemetry.heartRate.age`
- `game.telemetry.restingHeartRateBpm` -> `game.telemetry.heartRate.restingHeartRateBpm`
- `game.telemetry.userWeightKg` -> `game.telemetry.heartRate.userWeightKg`
### feedback 层
- `game.audio` -> `game.feedback.audio``resources.audioProfiles`
- `game.haptics` -> `game.feedback.haptics``resources.hapticsProfiles`
- `game.uiEffects` -> `game.feedback.uiEffects``resources.uiEffectsProfiles`
当前建议迁移策略:
- 第一阶段:代码同时兼容老字段和新结构
- 第二阶段:线上配置逐步切换
- 第三阶段:再清理旧字段兼容逻辑
---
## 9. 未来推荐的 manifest 方向
当前阶段主配置建议先保持单文件。
但未来配置规模变大时,推荐升级成多 manifest 组合:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"map": {
"manifest": "maps/lxcb-001/map.json"
},
"playfield": {
"manifest": "playfields/lxcb-001/c01.json"
},
"game": {
"manifest": "modes/score-o/default.json"
},
"resources": {
"manifest": "packs/spring-2026/resources.json"
},
"debug": {}
}
```
这样可以支持:
- 一张地图挂多种玩法
- 一条 playfield 挂多种规则
- 一种玩法切换不同资源包
- 后台管理做拼装式发布
---
## 10. 服务端和后台管理的推荐核心对象
后续从服务端和后台管理的复用角度,建议围绕以下核心对象建模:
- `Map`
- `Playfield`
- `GameMode`
- `ResourcePack`
- `Event`
其中:
- `Map`
地图底图与空间底座
- `Playfield`
当前玩法场景中的空间对象定义
- `GameMode`
玩法规则模板
- `ResourcePack`
资源包与 profile
- `Event`
一次实际发布的活动实例
推荐关系可以理解为:
`Event = Map + Playfield + GameMode + ResourcePack + 发布参数`
---
## 11. 当前阶段推荐结论
当前阶段最推荐的方案是:
- 先保留单个 `game.json`
- 结构升级为 `app / map / playfield / game / resources / debug`
- 保留 KML 作为空间底稿来源
- 不再让 `course` 成为总抽象,而是提升为更通用的 `playfield`
- 让代码先双兼容,再逐步迁移线上配置
一句话总结:
**KML 描述空间事实,配置描述玩法解释;主配置按 `map / playfield / game / resources / debug` 分层,后续再升级成 manifest 组合。**

163
doc/config-docs-index.md Normal file
View File

@@ -0,0 +1,163 @@
# 配置文档索引
本文档用于汇总当前项目所有与**配置设计、配置样例、配置管理**相关的文档,作为统一入口。
适用对象:
- 客户端开发
- 服务端开发
- 后台管理设计
- 配置录入与联调
---
## 1. 配置设计总方案
### [config-design-proposal.md](D:/dev/cmr-mini/doc/config-design-proposal.md)
作用:
- 说明为什么配置要按 `app / map / playfield / game / resources / debug` 分层
- 说明 `KML` 和配置的职责边界
- 说明为什么上位概念用 `playfield`
- 适合做总体架构参考
适合阅读时机:
- 设计配置结构
- 设计客户端读取链
- 和后端讨论顶层模型时
---
## 2. 配置选项字典
### [config-option-dictionary.md](D:/dev/cmr-mini/doc/config-option-dictionary.md)
作用:
- 列出当前客户端已经支持或已预留的配置项
- 说明每个字段的类型、含义、默认逻辑
- 作为后续新增字段时的持续维护文档
适合阅读时机:
- 想知道某个字段是否已实现
- 想知道字段应该怎么写
- 想确认默认行为时
---
## 3. 默认配置模板
### [config-default-template.md](D:/dev/cmr-mini/doc/config-default-template.md)
作用:
- 提供当前推荐的默认配置模板
- 包含顺序赛和积分赛的基础默认示例
- 用于服务端、后台、联调时直接起步
适合阅读时机:
- 新建一份活动配置
- 想直接照着填配置
- 想知道最小可运行模板长什么样
---
## 4. 按玩法拆分的配置模板文档
### [config-template-classic-sequential.md](D:/dev/cmr-mini/doc/config-template-classic-sequential.md)
作用:
- 解释顺序赛配置结构
- 说明顺序赛的必填字段和默认值
- 适合给后端和后台做顺序赛专项参考
### [config-template-score-o.md](D:/dev/cmr-mini/doc/config-template-score-o.md)
作用:
- 解释积分赛配置结构
- 说明积分赛的必填字段和默认值
- 适合给后端和后台做积分赛专项参考
---
## 5. 运行中的样例配置
### [event/classic-sequential.json](D:/dev/cmr-mini/event/classic-sequential.json)
作用:
- 当前顺序赛样例配置
- 可直接联调
- 已包含控制点内容覆盖示例
### [event/score-o.json](D:/dev/cmr-mini/event/score-o.json)
作用:
- 当前积分赛样例配置
- 可直接联调
- 已包含分值、起终点内容、点击内容示例
---
## 6. 后台与服务端配置管理方案
### [backend-config-management-proposal.md](D:/dev/cmr-mini/doc/backend-config-management-proposal.md)
作用:
- 第一版后台配置管理建议
- 适合了解 `Map / Playfield / GameMode / ResourcePack / Event` 这套核心对象
### [backend-config-management-v2.md](D:/dev/cmr-mini/doc/backend-config-management-v2.md)
作用:
- 在“配置项变化频繁”前提下重写的后台方案
- 更强调:
- 稳定骨架
- `jsonb`
- 版本
- 发布
- 透传未知字段
推荐优先看这一份。
---
## 7. 推荐阅读顺序
如果你是第一次接触这套配置体系,建议按这个顺序看:
1. [config-design-proposal.md](D:/dev/cmr-mini/doc/config-design-proposal.md)
2. [config-option-dictionary.md](D:/dev/cmr-mini/doc/config-option-dictionary.md)
3. [config-default-template.md](D:/dev/cmr-mini/doc/config-default-template.md)
4. [event/classic-sequential.json](D:/dev/cmr-mini/event/classic-sequential.json)
5. [event/score-o.json](D:/dev/cmr-mini/event/score-o.json)
6. [backend-config-management-v2.md](D:/dev/cmr-mini/doc/backend-config-management-v2.md)
---
## 8. 维护约定
后续每次新增配置能力时,建议至少同步更新这几处:
1. [config-option-dictionary.md](D:/dev/cmr-mini/doc/config-option-dictionary.md)
2. [config-default-template.md](D:/dev/cmr-mini/doc/config-default-template.md)
3. 对应玩法的 `event/*.json` 样例
4. 如果涉及顶层结构变化,再更新 [config-design-proposal.md](D:/dev/cmr-mini/doc/config-design-proposal.md)
这样可以保证:
- 文档
- 样例
- 代码
- 后台录入
保持一致。

View File

@@ -0,0 +1,556 @@
# 配置选项字典(当前实现版)
本文档用于整理 **当前客户端已经消费或已经预留承载的配置项**,作为事件配置、后台配置和联调时的统一参考。
目标:
- 明确目前哪些字段已经真正生效
- 明确每个字段的含义、类型、默认逻辑
- 给后续扩展留下统一维护入口
说明:
- 本文档优先以“当前代码真实实现”为准
- 未列出的字段,不代表未来不能加,只代表当前客户端未正式消费
- 后续每次新增配置能力,都应同步补充本文件
---
## 1. 顶层结构
当前推荐结构:
```json
{
"schemaVersion": "1",
"version": "2026.03.27",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
---
## 2. 顶层字段字典
### `schemaVersion`
- 类型:`string`
- 说明:配置结构版本
- 建议默认值:`"1"`
### `version`
- 类型:`string`
- 说明:当前配置内容版本
- 建议默认值:日期或发布版本号,例如 `2026.03.27`
### `app`
- 类型:`object`
- 说明:活动级基础信息
### `map`
- 类型:`object`
- 说明:地图底座信息
### `playfield`
- 类型:`object`
- 说明:玩法空间对象与内容覆盖
### `game`
- 类型:`object`
- 说明:玩法规则与局流程
### `resources`
- 类型:`object`
- 说明:资源 profile 引用
### `debug`
- 类型:`object`
- 说明:调试开关
---
## 3. `app` 字段
### `app.id`
- 类型:`string`
- 说明:活动或配置实例 id
- 示例:`"sample-classic-001"`
### `app.title`
- 类型:`string`
- 说明:活动标题 / 比赛名称
- 示例:`"顺序赛示例"`
### `app.locale`
- 类型:`string`
- 说明:语言环境
- 建议默认值:`"zh-CN"`
---
## 4. `map` 字段
### `map.tiles`
- 类型:`string`
- 说明:瓦片根路径
- 必填:是
### `map.mapmeta`
- 类型:`string`
- 说明:地图 meta 文件地址
- 必填:是
### `map.declination`
- 类型:`number`
- 说明:磁偏角
- 示例:`6.91`
- 备注:当前会影响真北/磁北换算
### `map.initialView.zoom`
- 类型:`number`
- 说明:初始缩放级别
- 建议默认值:`17`
---
## 5. `playfield` 字段
### `playfield.kind`
- 类型:`string`
- 说明:空间对象类型
- 当前推荐值:
- `course`
- `control-set`
### `playfield.source.type`
- 类型:`string`
- 说明:空间底稿来源类型
- 当前推荐值:`kml`
### `playfield.source.url`
- 类型:`string`
- 说明KML 地址
- 必填:是
### `playfield.CPRadius`
- 类型:`number`
- 说明:检查点绘制半径
- 建议默认值:`6`
### `playfield.metadata.title`
- 类型:`string`
- 说明:路线或控制点集标题
### `playfield.metadata.code`
- 类型:`string`
- 说明:路线或控制点集编码
---
## 6. `playfield.controlOverrides`
`playfield.controlOverrides` 用于对起点、检查点、终点做内容或分值覆盖。
### 6.1 key 命名规则
- 起点:`start-1`
- 普通检查点:`control-1``control-2``control-3`
- 终点:`finish-1`
### 6.2 当前支持字段
#### `score`
- 类型:`number`
- 说明:积分赛控制点分值
- 适用:积分赛
#### `title`
- 类型:`string`
- 说明:打点完成后自动弹出的标题
#### `body`
- 类型:`string`
- 说明:打点完成后自动弹出的正文
#### `clickTitle`
- 类型:`string`
- 说明:点击控制点时弹出的标题
- 默认逻辑:未配置时回退到 `title`
#### `clickBody`
- 类型:`string`
- 说明:点击控制点时弹出的正文
- 默认逻辑:未配置时回退到 `body`
#### `autoPopup`
- 类型:`boolean`
- 说明:完成该点后是否自动弹出内容
- 建议默认值:`true`
- 特殊逻辑:如果当前玩法是自动打点,即 `game.punch.policy = "enter"`,则无论这里如何配置,**都不自动弹出**
#### `once`
- 类型:`boolean`
- 说明:该内容是否本局只自动展示一次
- 建议默认值:`false`
#### `priority`
- 类型:`number`
- 说明:内容优先级,越大越高
- 建议默认值:
- 普通点:`1`
- 终点:`2`
### 6.3 示例
```json
"controlOverrides": {
"start-1": {
"title": "比赛开始",
"body": "从这里出发,先熟悉地图方向。",
"autoPopup": true,
"once": true,
"priority": 1,
"clickTitle": "起点说明",
"clickBody": "点击起点可再次查看起跑说明。"
},
"control-2": {
"score": 20,
"title": "教学楼南侧",
"body": "这里是重要转折点。",
"autoPopup": false,
"once": true,
"priority": 1,
"clickTitle": "教学楼南侧",
"clickBody": "这个点配置成点击查看。"
},
"finish-1": {
"title": "比赛结束",
"body": "恭喜完成本次路线。",
"autoPopup": true,
"once": true,
"priority": 2,
"clickTitle": "终点说明",
"clickBody": "点击终点可再次查看结束说明。"
}
}
```
---
## 7. `game` 字段
### `game.mode`
- 类型:`string`
- 说明:玩法类型
- 当前支持:
- `classic-sequential`
- `score-o`
### `game.rulesVersion`
- 类型:`string`
- 说明:规则版本
- 建议默认值:`"1"`
---
## 8. `game.session`
### `game.session.startManually`
- 类型:`boolean`
- 说明:是否需要手动点击开始
- 建议默认值:`true`
### `game.session.requiresStartPunch`
- 类型:`boolean`
- 说明:是否必须完成起点打卡
- 建议默认值:
- 顺序赛:`true`
- 积分赛:`true`
### `game.session.requiresFinishPunch`
- 类型:`boolean`
- 说明:是否必须完成终点打卡
- 建议默认值:
- 顺序赛:`true`
- 积分赛:`false`
### `game.session.autoFinishOnLastControl`
- 类型:`boolean`
- 说明:是否打完最后控制点自动结束
- 建议默认值:`false`
### `game.session.maxDurationSec`
- 类型:`number`
- 说明:最大比赛时长,单位秒
- 建议默认值:`5400`
---
## 9. `game.punch`
### `game.punch.policy`
- 类型:`string`
- 说明:打点策略
- 当前支持:
- `enter-confirm`
- `enter`
- 建议默认值:`enter-confirm`
### `game.punch.radiusMeters`
- 类型:`number`
- 说明:打点半径
- 建议默认值:`5`
### `game.punch.requiresFocusSelection`
- 类型:`boolean`
- 说明:积分赛是否需要先选中目标再打卡
- 建议默认值:
- 顺序赛:`false`
- 积分赛:`false`
---
## 10. `game.sequence.skip`
仅顺序赛相关。
### `game.sequence.skip.enabled`
- 类型:`boolean`
- 说明:是否允许跳点
- 建议默认值:`false`
### `game.sequence.skip.radiusMeters`
- 类型:`number`
- 说明:跳点半径
- 建议默认值:`30`
### `game.sequence.skip.requiresConfirm`
- 类型:`boolean`
- 说明:跳点是否需要确认
- 建议默认值:`true`
---
## 11. `game.scoring`
### `game.scoring.type`
- 类型:`string`
- 说明:积分模型
- 当前推荐值:`score`
### `game.scoring.defaultControlScore`
- 类型:`number`
- 说明:积分赛默认控制点分值
- 建议默认值:`10`
---
## 12. `game.guidance`
### `game.guidance.showLegs`
- 类型:`boolean`
- 说明:是否显示腿线
- 建议默认值:
- 顺序赛:`true`
- 积分赛:`false`
### `game.guidance.legAnimation`
- 类型:`boolean`
- 说明:是否显示腿线动画
- 建议默认值:
- 顺序赛:`true`
- 积分赛:`false`
### `game.guidance.allowFocusSelection`
- 类型:`boolean`
- 说明:是否允许地图点击选择目标点
- 建议默认值:
- 顺序赛:`false`
- 积分赛:`true`
---
## 13. `game.visibility`
### `game.visibility.revealFullPlayfieldAfterStartPunch`
- 类型:`boolean`
- 说明:起点打卡后是否显示完整路线/控制点集合
- 建议默认值:`true`
---
## 14. `game.finish`
### `game.finish.finishControlAlwaysSelectable`
- 类型:`boolean`
- 说明:终点是否始终可选
- 建议默认值:
- 顺序赛:`false`
- 积分赛:`true`
---
## 15. `game.telemetry.heartRate`
### `age`
- 类型:`number`
- 说明:年龄
- 建议默认值:`30`
### `restingHeartRateBpm`
- 类型:`number`
- 说明:静息心率
- 建议默认值:`62`
### `userWeightKg`
- 类型:`number`
- 说明:体重
- 建议默认值:`65`
---
## 16. `game.feedback`
### `game.feedback.audioProfile`
- 类型:`string`
- 说明:音频反馈 profile
- 建议默认值:`default`
### `game.feedback.hapticsProfile`
- 类型:`string`
- 说明:震动反馈 profile
- 建议默认值:`default`
### `game.feedback.uiEffectsProfile`
- 类型:`string`
- 说明UI 动效 profile
- 建议默认值:`default`
---
## 17. `resources`
### `resources.audioProfile`
- 类型:`string`
- 建议默认值:`default`
### `resources.contentProfile`
- 类型:`string`
- 建议默认值:`default`
### `resources.themeProfile`
- 类型:`string`
- 建议默认值:`default-race`
---
## 18. `debug`
### `debug.allowModeSwitch`
- 类型:`boolean`
- 建议默认值:`false`
### `debug.allowMockInput`
- 类型:`boolean`
- 建议默认值:`false`
### `debug.allowSimulator`
- 类型:`boolean`
- 建议默认值:`false`
---
## 19. 当前默认逻辑说明
当前客户端对配置的处理原则是:
- 能有默认值的尽量给默认值
- 控制点内容类字段缺失时走默认文案
- `clickTitle/clickBody` 缺失时回退到 `title/body`
- 自动打点模式下不自动弹内容
- 内容优先级未配置时使用普通点 `1`、终点 `2`
也就是说:
**大部分配置项都不是强制必填,先保证主骨架完整即可。**
---
## 20. 维护约定
后续每次新增配置项时,应同步更新:
1. 本文档
2. 默认模板文档
3. `event` 目录下的配置样例
这样可以保证:
- 服务端可对照
- 后台可录入
- 客户端联调时有统一参考

View File

@@ -0,0 +1,313 @@
# 顺序赛配置文档(基础版)
本文档用于给服务端和后台配置设计提供一份可直接落地的顺序赛基础模板。
目标是先把入口配置结构定稳,后续程序功能再逐步细化。
---
## 1. 适用玩法
适用于最基础的顺序打点玩法:
- 手动开始
- 先打开始点
- 按顺序打检查点
- 最后打终点
- 支持正常打点半径
- 预留后续扩展规则空间
---
## 2. 顶层结构
推荐主配置结构如下:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
---
## 3. 完整示例
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区顺序赛",
"locale": "zh-CN"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
},
"playfield": {
"kind": "course",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"metadata": {
"title": "校园经典路线",
"code": "c01"
}
},
"game": {
"mode": "classic-sequential",
"rulesVersion": "1",
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false,
"maxDurationSec": 5400
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10
},
"sequence": {
"skip": {
"enabled": false,
"radiusMeters": 30,
"requiresConfirm": true
}
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"finish": {
"finishControlAlwaysSelectable": false
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 4. 字段说明
### `app`
- `id`
活动或配置实例 id
- `title`
活动标题
- `locale`
语言环境
### `map`
- `tiles`
瓦片根路径
- `mapmeta`
地图 meta 地址
- `declination`
磁偏角
- `initialView.zoom`
初始缩放级别
### `playfield`
- `kind`
当前为 `course`
- `source.type`
当前推荐为 `kml`
- `source.url`
KML 地址
- `CPRadius`
检查点绘制半径,单位米
- `metadata`
路线元数据
### `game.session`
- `startManually`
是否需要先点击开始按钮
- `requiresStartPunch`
是否必须先打开始点
- `requiresFinishPunch`
是否必须打终点
- `autoFinishOnLastControl`
是否打完最后一个检查点自动结束
- `maxDurationSec`
最大比赛时长
### `game.punch`
- `policy`
当前推荐 `enter-confirm`
- `radiusMeters`
正常打点半径
### `game.sequence`
- `skip`
顺序赛跳点规则
- `enabled`
是否允许跳点
- `radiusMeters`
跳点半径,必须大于打点半径
- `requiresConfirm`
是否必须用户确认后跳点
当前基础版建议先关闭:
```json
"enabled": false
```
### `game.guidance`
- `showLegs`
是否显示腿线
- `legAnimation`
是否显示当前腿动画
- `allowFocusSelection`
顺序赛一般为 `false`
### `game.visibility`
- `revealFullPlayfieldAfterStartPunch`
开始点打卡后是否显示完整路线
### `game.finish`
- `finishControlAlwaysSelectable`
顺序赛一般为 `false`
### `game.telemetry`
通用体能参数。
### `game.feedback`
反馈 profile 绑定。
### `resources`
资源 profile 绑定。
### `debug`
调试相关开关。
---
## 5. 当前阶段推荐必填字段
顺序赛当前阶段建议至少保证以下字段存在:
- `map.tiles`
- `map.mapmeta`
- `map.declination`
- `playfield.kind`
- `playfield.source.type`
- `playfield.source.url`
- `playfield.CPRadius`
- `game.mode`
- `game.punch.policy`
- `game.punch.radiusMeters`
- `game.session.requiresStartPunch`
- `game.session.requiresFinishPunch`
---
## 6. 当前阶段建议默认值
如果服务端还没有全部配置细项,建议先采用以下默认值:
```json
{
"game": {
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": true,
"autoFinishOnLastControl": false
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10
},
"sequence": {
"skip": {
"enabled": false,
"radiusMeters": 30,
"requiresConfirm": true
}
},
"guidance": {
"showLegs": true,
"legAnimation": true,
"allowFocusSelection": false
}
}
}
```
---
## 7. 适合当前客户端的迁移原则
当前客户端迁移时,建议服务端先完成:
1. 将老字段逐步迁入 `map / playfield / game / resources / debug`
2. 保持基础字段完整
3. 不急着一次性把所有高级规则上线
优先把“入口结构”夯实,再逐步扩玩法参数。
---
## 8. 一句话结论
顺序赛配置当前阶段建议:
-`playfield.kind = course`
- 用 KML 承载空间底稿
-`game.session / game.punch / game.sequence / game.guidance` 承载玩法规则
- 先把基础入口结构定稳,后续再细化跳点、惩罚、特殊引导等高级规则

View File

@@ -0,0 +1,355 @@
# 积分赛配置文档(基础版)
本文档用于给服务端和后台配置设计提供一份可直接落地的积分赛基础模板。
目标是先把积分赛入口结构定稳,后续程序功能再逐步细化。
---
## 1. 适用玩法
适用于最基础的积分赛玩法:
- 手动开始
- 先打开始点
- 多个检查点自由收集
- 控制点有固定分值
- 可选终点
- 支持选中目标点后打卡
---
## 2. 顶层结构
推荐主配置结构如下:
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {},
"map": {},
"playfield": {},
"game": {},
"resources": {},
"debug": {}
}
```
---
## 3. 完整示例
```json
{
"schemaVersion": "1",
"version": "2026.03.25",
"app": {
"id": "lxcb-001",
"title": "雪熊领秀城区积分赛",
"locale": "zh-CN"
},
"map": {
"tiles": "lxcb-001/tiles/",
"mapmeta": "lxcb-001/tiles/meta.json",
"declination": 6.91,
"initialView": {
"zoom": 17
}
},
"playfield": {
"kind": "control-set",
"source": {
"type": "kml",
"url": "lxcb-001/course/c01.kml"
},
"CPRadius": 6,
"controlOverrides": {
"control-1": {
"score": 10
},
"control-2": {
"score": 20
},
"control-3": {
"score": 30
},
"control-4": {
"score": 40
}
},
"metadata": {
"title": "校园积分赛控制点集",
"code": "score-o-c01"
}
},
"game": {
"mode": "score-o",
"rulesVersion": "1",
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": false,
"autoFinishOnLastControl": false,
"maxDurationSec": 5400
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10,
"requiresFocusSelection": true
},
"scoring": {
"type": "score",
"defaultControlScore": 10
},
"guidance": {
"showLegs": false,
"legAnimation": false,
"allowFocusSelection": true
},
"visibility": {
"revealFullPlayfieldAfterStartPunch": true
},
"finish": {
"finishControlAlwaysSelectable": true
},
"telemetry": {
"heartRate": {
"age": 30,
"restingHeartRateBpm": 62,
"userWeightKg": 65
}
},
"feedback": {
"audioProfile": "default",
"hapticsProfile": "default",
"uiEffectsProfile": "default"
}
},
"resources": {
"audioProfile": "default",
"contentProfile": "default",
"themeProfile": "default-race"
},
"debug": {
"allowModeSwitch": false,
"allowMockInput": false,
"allowSimulator": false
}
}
```
---
## 4. 字段说明
### `app`
- `id`
活动或配置实例 id
- `title`
活动标题
- `locale`
语言环境
### `map`
- `tiles`
瓦片根路径
- `mapmeta`
地图 meta 地址
- `declination`
磁偏角
- `initialView.zoom`
初始缩放级别
### `playfield`
- `kind`
当前推荐 `control-set`
- `source.type`
当前推荐 `kml`
- `source.url`
KML 地址
- `CPRadius`
检查点绘制半径
- `controlOverrides`
每个控制点的积分和后续扩展元数据
### `playfield.controlOverrides`
当前阶段最推荐至少放:
- `score`
示例:
```json
"control-1": {
"score": 10
}
```
这样可以保证:
- KML 只提供点位与空间结构
- 分值由配置控制
### `game.session`
- `startManually`
是否手动开始
- `requiresStartPunch`
是否必须先打开始点
- `requiresFinishPunch`
是否必须打终点
- `autoFinishOnLastControl`
积分赛通常为 `false`
- `maxDurationSec`
最大时长
### `game.punch`
- `policy`
当前推荐 `enter-confirm`
- `radiusMeters`
打点半径
- `requiresFocusSelection`
是否必须先选中目标点后才能打卡
### `game.scoring`
- `type`
当前推荐 `score`
- `defaultControlScore`
如果某个点没单独配置分数时的默认值
### `game.guidance`
- `showLegs`
积分赛基础版建议 `false`
- `legAnimation`
积分赛基础版建议 `false`
- `allowFocusSelection`
建议 `true`
### `game.visibility`
- `revealFullPlayfieldAfterStartPunch`
开始点打卡后是否显示完整控制点集合
### `game.finish`
- `finishControlAlwaysSelectable`
积分赛建议支持随时选终点结束时,设为 `true`
### `game.telemetry`
通用体能参数。
### `game.feedback`
反馈 profile 绑定。
### `resources`
资源 profile 绑定。
### `debug`
调试相关开关。
---
## 5. 当前阶段推荐必填字段
积分赛当前阶段建议至少保证以下字段存在:
- `map.tiles`
- `map.mapmeta`
- `map.declination`
- `playfield.kind`
- `playfield.source.type`
- `playfield.source.url`
- `playfield.CPRadius`
- `game.mode`
- `game.punch.policy`
- `game.punch.radiusMeters`
- `game.punch.requiresFocusSelection`
- `game.scoring.type`
---
## 6. 当前阶段建议默认值
如果服务端还没有全部细项,建议先采用以下默认值:
```json
{
"game": {
"session": {
"startManually": true,
"requiresStartPunch": true,
"requiresFinishPunch": false,
"autoFinishOnLastControl": false
},
"punch": {
"policy": "enter-confirm",
"radiusMeters": 10,
"requiresFocusSelection": true
},
"scoring": {
"type": "score",
"defaultControlScore": 10
},
"guidance": {
"showLegs": false,
"legAnimation": false,
"allowFocusSelection": true
},
"finish": {
"finishControlAlwaysSelectable": true
}
}
}
```
---
## 7. 积分赛当前阶段推荐的设计边界
当前阶段建议坚持以下边界:
- KML 提供点位和几何底稿
- 配置提供分值和玩法解释
- 不把积分逻辑写进 KML
- 不把自由收集逻辑写成固定路线逻辑
也就是说:
- `playfield.kind = control-set`
- `controlOverrides.score` 负责分值
- `game.guidance.allowFocusSelection = true` 负责选中目标逻辑
---
## 8. 适合当前客户端的迁移原则
当前客户端迁移时,建议服务端先完成:
1. 将积分赛点位分值迁入 `playfield.controlOverrides`
2. 将玩法规则迁入 `game.session / game.punch / game.scoring / game.guidance / game.finish`
3. 不急着一次性接入动态积分和复杂规则
先把静态积分赛入口结构定稳,再逐步扩展动态积分和高级玩法能力。
---
## 9. 一句话结论
积分赛配置当前阶段建议:
-`playfield.kind = control-set`
- 用 KML 承载控制点空间底稿
-`playfield.controlOverrides` 承载点位分值
-`game.scoring / game.punch / game.guidance / game.finish` 承载玩法规则
- 先把静态积分赛入口结构定稳,后续再扩动态积分与更复杂玩法

View File

@@ -0,0 +1,328 @@
# 游戏中文创体验层方案
## 1. 目标
为游戏过程中的文创内容建立一层独立承载能力,不把内容弹窗、图文卡片、讲解信息散落在:
- 规则层
- 页面层
- HUD 逻辑
- 反馈层
这层的目标是:
- 在正确时机触发内容体验
- 统一内容展示方式
- 可配置、可复用、可扩展
- 不破坏当前地图与规则主链
一句话:
**把“中途内容体验”从临时弹窗提升为正式能力层。**
---
## 2. 当前现状
当前项目已经具备一部分基础:
- `control.displayContent`
- `UiEffectDirector.showContentCard(...)`
- 页面层已有 `contentCardVisible / contentCardTitle / contentCardBody`
- 打点完成后可展示内容卡
这说明:
- 内容展示能力已经有雏形
- 但触发方式还偏单一
- 内容形式也还比较轻
- 还没有形成正式的“内容体验层”模型
---
## 3. 设计原则
### 3.1 内容体验不等于短反馈
短反馈仍然属于:
- 音效
- 震动
- HUD 提示
- 地图 pulse
文创体验属于更重的一层,应与 `FeedbackDirector` 区分。
### 3.2 内容体验不直接写死在规则里
规则层只负责:
- 是否触发
- 触发什么体验条目
规则层不负责:
- 页面怎么弹
- 卡片长什么样
- 是否带图片、音频、讲解按钮
### 3.3 内容体验必须配置驱动
以后不同活动、不同地图、不同玩法需要不同内容。
所以这层必须可配置:
- 哪个点触发
- 何时触发
- 弹什么
- 是否只弹一次
- 优先级如何
---
## 4. 建议的新层级
建议增加一层:
- `ContentExperienceLayer`
放在概念上与这些层并列:
- `MapPresentation`
- `HUD`
- `Feedback`
- `ResultScene`
职责:
- 接收体验触发
- 管理当前激活内容项
- 控制展示与关闭
- 向页面层输出当前体验模型
---
## 5. 建议的数据模型
### 5.1 ExperienceEntry
```ts
type ExperienceTrigger =
| 'control_completed'
| 'zone_entered'
| 'session_finished'
| 'manual'
type ExperienceDisplayMode =
| 'content-card'
| 'full-panel'
| 'audio-guide'
| 'unlock-card'
interface ExperienceEntry {
id: string
trigger: ExperienceTrigger
controlId?: string
zoneId?: string
title: string
body: string
imageRef?: string
audioRef?: string
displayMode: ExperienceDisplayMode
once: boolean
priority: number
}
```
### 5.2 ExperienceRuntimeState
```ts
interface ExperienceRuntimeState {
activeEntryId: string | null
dismissedEntryIds: string[]
consumedEntryIds: string[]
}
```
---
## 6. 配置建议
建议在配置中增加一段:
```json
{
"resources": {
"contentEntries": {
"cp-3-story": {
"title": "校史地标",
"body": "这里是校园历史演变的重要节点。",
"imageRef": "content/campus-history-01.png",
"displayMode": "content-card"
}
}
},
"game": {
"experience": {
"entries": [
{
"id": "cp-3-story",
"trigger": "control_completed",
"controlId": "control-3",
"once": true,
"priority": 10
}
]
}
}
}
```
这意味着:
- 资源层管理内容资源
- 玩法配置决定何时触发
---
## 7. 触发来源
第一阶段建议支持 3 种触发:
### 7.1 打点完成触发
最适合当前项目,价值最高。
例如:
- 完成某个控制点后弹一张文创卡
- 开始点完成后弹赛事导览卡
- 终点完成后弹纪念卡
### 7.2 区域进入触发
适合后续:
- 地标介绍
- 迷雾探索
- 特定区域故事点
### 7.3 结算后解锁触发
适合后续与结算页联动:
- 收藏卡
- 奖章
- 文创奖励
---
## 8. 页面表现建议
第一阶段先做最小闭环,不追求复杂视觉。
### 8.1 第一阶段
支持:
- 当前已有的 `content-card`
- 标题
- 正文
- 关闭
### 8.2 第二阶段
再支持:
- 图片
- 按钮
- 章节式展开
- 音频讲解
---
## 9. 与当前架构的关系
### 规则层
负责:
- 触发某条体验事件
不负责:
- 具体展示细节
### Feedback
继续负责:
- 短反馈
- 动效
- 音效
### ContentExperienceLayer
负责:
- 中等时长的信息体验
### 页面层
负责:
- 渲染当前体验模型
---
## 10. 第一阶段最小实施范围
建议第一阶段只做:
1. `control_completed -> experience entry`
2. `content-card` 展示
3. `once` 语义
4. 手动关闭
5. 配置驱动
不要一上来做:
- 图片轮播
- 视频
- 复杂音频控制
- 多层交互
---
## 11. 推荐实施顺序
1. 定义 `ExperienceEntry`
2. 在配置解析层接 `game.experience.entries`
3. 在规则完成事件里派发体验触发
4. MapEngine 增加体验状态承载
5. 页面层继续复用当前 `content-card`
6. 再逐步升级 UI
---
## 12. 长期价值
这层建好后,后续可以自然承接:
- 文创卡片
- 地标解说
- 解锁收藏
- 故事节点
- 活动内品牌内容
它不只服务当前顺序赛/积分赛,而是服务整条产品体验链。
---
## 13. 结论
当前最正确的方向不是继续在页面里零散补内容弹窗,而是:
**把游戏中途的文创与故事体验正式抽成一层独立的 `ContentExperienceLayer`。**
第一阶段先用“控制点完成触发内容卡”跑通最小闭环,后面再逐步扩成完整体验系统。

View File

@@ -0,0 +1,441 @@
# 新玩法建议方案
本文档用于整理当前阶段值得考虑的新游戏玩法方向,重点回答以下问题:
- 哪些玩法对用户更有吸引力
- 哪些玩法更适合当前架构
- 哪些玩法适合优先推进
- 新玩法大致会消耗哪些底座能力
当前判断基于现有系统能力:
- 地图引擎
- 规则引擎
- telemetry 信息层
- map / hud presentation
- feedback 反馈层
- 真实 / 模拟 GPS
- 真实 / 模拟心率
---
## 1. 总结结论
当前最值得优先考虑的新玩法,不是简单继续加“顺序赛变体”,而是做更有差异化和传播性的玩法。
综合吸引力、架构适配度、开发投入和可验证性,推荐优先级如下:
1. `幽灵追逐赛`
2. `动态积分赛`
3. `迷雾探索赛`
4. `区域金币冲刺`
5. `蛇尾生存赛`
如果从“最快做出明显新体验”的角度看:
- 最推荐优先试做:`幽灵追逐赛`
- 最容易从现有玩法演化:`动态积分赛`
- 最能体现数字地图优势:`迷雾探索赛`
---
## 2. 当前架构适不适合继续长新玩法
结论是:`适合`
原因在于当前系统已经不是“一个写死的地图页”,而是具备了比较清晰的分层:
- `MapEngine`
- 管地图交互、相机、锁定、缩放、自动转图
- `RuleEngine / RulePlugin`
- 管玩法推进和状态转换
- `TelemetryRuntime`
- 管速度、距离、心率、卡路里等通用信息
- `Presentation`
- 管地图和 HUD 展示态
- `Feedback`
- 管音效、震动、动效
因此后续大多数玩法更像是:
- 新增一个 `RulePlugin`
- 新增一组 `modeState`
- 新增一组 `presentation`
而不是推翻现有主架构。
---
## 3. 推荐玩法清单
### 3.1 幽灵追逐赛
#### 核心乐趣
- 一边找点,一边躲避“幽灵”追踪
- 地图不再只是找路,而是持续有压力感
- 心率越高、速度越乱,越容易“暴露”
#### 玩法示意
- 选定一个虚拟追逐者或 AI 幽灵
- 玩家需要完成打点或收集任务
- 幽灵根据规则靠近、感知或巡逻
- 特定点位可以提供隐身、干扰、冻结幽灵等道具
#### 为什么适合当前架构
这类玩法天然会复用现有能力:
- GPS位置与距离
- 心率:暴露度或危险加成
- HUD危险提示、追逐状态
- Feedback警报音、边缘警示
- 模拟器:可快速室内调试
#### 需要新增的内容
- 新的 `RulePlugin`
- `ghostState / stealthState / alertState`
- 地图上的感知圈、危险圈、幽灵标记
- 特定的提示与反馈
#### 开发判断
- 吸引力:`高`
- 架构适配度:`高`
- 开发成本:`中`
- 推荐优先级:`最高`
---
### 3.2 动态积分赛
#### 核心乐趣
- 同一个点的分值不是固定的
- 玩家不只是拼体力,也要拼判断和策略
- “继续赌高分”还是“见好就收”会成为玩法核心
#### 玩法示意
- 控制点分值随时间变化
- 热门点越多人去,分值越低
- 冷门点无人问津时,分值慢慢上涨
- 可随时结束,也可继续冲刺
#### 为什么适合当前架构
它本质上是现有 `score-o` 的增强版:
- 自由选点
- 点位分值
- HUD 计分
- 地图点位状态变化
主要增加的是“分值更新机制”和“同步能力”。
#### 需要新增的内容
- 动态分值模型
- 分值同步/刷新策略
- HUD 上的分值变化提示
- 地图点位的分值高低表现
#### 开发判断
- 吸引力:`高`
- 架构适配度:`高`
- 开发成本:`低到中`
- 推荐优先级:`高`
---
### 3.3 迷雾探索赛
#### 核心乐趣
- 开局地图是黑的,玩家是在“开图”
- 不是靠死记地图,而是靠探索解锁空间
- 很能体现数字地图的独特优势
#### 玩法示意
- 开局全图被迷雾遮住
- 靠近某些区域后局部解锁
- 某些点位可提供“雷达”或“远程扫描”能力
- 玩家要在有限信息下做探索决策
#### 为什么适合当前架构
这类玩法会复用:
- 位置输入
- 规则状态推进
- presentation 层
但它对地图渲染的要求更高,需要你继续增强地图遮罩和 reveal 系统。
#### 需要新增的内容
- `fogState / revealedAreaState`
- 地图迷雾遮罩
- reveal / scan 类事件
- HUD 上的探索进度提示
#### 开发判断
- 吸引力:`高`
- 架构适配度:`中高`
- 开发成本:`中高`
- 推荐优先级:`中高`
---
### 3.4 区域金币冲刺
#### 核心乐趣
- 上手门槛低
- 节奏快
- 很适合短局、多次复玩
#### 玩法示意
- 在某一区域内自由收集金币
- 连续收集触发倍率
- 特殊金币提供时间奖励或得分加成
- 可设置出口点或终点结算
#### 为什么适合当前架构
这类玩法本质接近积分赛和自由收集:
- 多目标自由采集
- 简单直观的分值逻辑
- 容易做 HUD 和地图高亮
#### 需要新增的内容
- `coin / bonus / exit` 等对象类型
- 收集动效和连击显示
- 区域完成度 / 剩余金币等 HUD 信息
#### 开发判断
- 吸引力:`中高`
- 架构适配度:`高`
- 开发成本:`中`
- 推荐优先级:`中高`
---
### 3.5 蛇尾生存赛
#### 核心乐趣
- 尾巴越来越长
- 视觉反馈强
- 规则新鲜且容易“上头”
#### 玩法示意
- 玩家移动轨迹形成蛇身
- 收集奖励会增长尾巴
- 尾巴过长或撞到自己会触发裁切/失败
- 可叠加危险区、奖励点、加速点等机制
#### 为什么适合当前架构
这类玩法非常依赖:
- GPS 轨迹
- 持续 telemetry
- 地图轨迹绘制
- 规则插件式状态推进
#### 需要新增的内容
- `snakeBody / tailWindow / tailLength`
- 自碰撞检测
- 蛇尾地图表现
- 奖励点 / 危险区
#### 开发判断
- 吸引力:`中高`
- 架构适配度:`中高`
- 开发成本:`中高`
- 推荐优先级:`中`
---
## 4. 其他值得保留的玩法方向
以下玩法同样值得保留为候选,但优先级可以放在上面 5 个之后。
### 4.1 领地争夺战
特点:
- 队伍占点
- 点位变色
- 可被对方夺回
- 强调多人实时对抗和策略
适配判断:
- 架构上可以支持
- 但会推动你补多人实时同步、区域关系判定、队伍状态管理
开发判断:
- 吸引力:`高`
- 架构适配度:`中`
- 开发成本:`高`
---
### 4.2 贪吃蛇式玩法
特点:
- 连续轨迹形成尾巴
- 奖励与风险明显
- 适合做更强游戏化实验
适配判断:
- 适合当前架构
- 主要消耗 `modeState` 和地图表现能力
开发判断:
- 吸引力:`中高`
- 架构适配度:`中高`
- 开发成本:`中高`
---
### 4.3 超级玛丽拾金币式玩法
特点:
- 更偏轻量、直观、上手快
- 类似“区域金币冲刺”的泛化版本
适配判断:
- 也非常适合当前架构
- 本质上就是更多对象类型和收集逻辑
开发判断:
- 吸引力:`中高`
- 架构适配度:`高`
- 开发成本:`中`
---
## 5. 按不同目标如何选玩法
### 如果目标是最快做出“新鲜感”
建议优先:
1. `幽灵追逐赛`
2. `动态积分赛`
### 如果目标是最能体现数字地图优势
建议优先:
1. `迷雾探索赛`
2. `幽灵追逐赛`
### 如果目标是最低理解门槛
建议优先:
1. `区域金币冲刺`
2. `动态积分赛`
### 如果目标是实验性和传播性
建议优先:
1. `蛇尾生存赛`
2. `幽灵追逐赛`
---
## 6. 对当前底座的主要消耗点
不同玩法会主要消耗不同底座能力:
- `幽灵追逐赛`
- 规则层
- telemetry
- 反馈层
- `动态积分赛`
- 规则层
- 分值同步
- HUD
- `迷雾探索赛`
- 地图引擎
- map presentation
- reveal 机制
- `区域金币冲刺`
- 内容模型
- presentation
- HUD
- `蛇尾生存赛`
- modeState
- 轨迹与碰撞
- 地图表现
---
## 7. 当前最值得优先投入的方向
综合判断,当前最推荐的推进顺序是:
1. `幽灵追逐赛`
2. `动态积分赛`
3. `迷雾探索赛`
原因:
- `幽灵追逐赛`
最能发挥你当前已经做好的 GPS / 心率 / HUD / feedback / 模拟器价值
- `动态积分赛`
最容易从现有 `score-o` 演化,投入小、效果明显
- `迷雾探索赛`
最能体现你地图引擎的差异化能力
---
## 8. 实际推进建议
建议后续不要同时推进多个重玩法,而是按“先验证新体验,再加深系统支撑”的节奏来。
推荐顺序:
1. 先做一个高吸引力但逻辑较清晰的玩法样板
- 推荐:`幽灵追逐赛`
2. 用这个玩法进一步验证:
- `RulePlugin`
- `modeState`
- `Presentation`
- `Feedback`
3. 再继续补底座能力:
- 更通用的对象模型
- 更强的地图表现
- 更清晰的玩法事件字典
---
## 9. 一句话结论
当前这套架构已经不只是适合传统顺序赛和积分赛,也适合继续承载更游戏化、更有传播性的运动玩法。
如果只优先选一个最值得推进的新玩法,建议先做:`幽灵追逐赛`

View File

@@ -0,0 +1,384 @@
# H5 体验接入方案
本文档用于定义当前项目中 **原生小程序 + H5 定制内容** 的混合接入方案。
目标:
- 保留原生地图与实时游戏主流程
- 把高频变化、强定制的内容页交给 H5
- 保证 H5 失败时,原生仍可完整兜底
- 为后续客户定制、品牌包装、互动任务扩展留出稳定接口
---
## 1. 结论
当前最合适的方向不是“所有定制都 H5 化”,而是:
**原生负责核心游戏层H5 负责定制体验层。**
也就是:
- 地图、打点、GPS、指北针、HUD、规则状态机继续原生
- 结算页、文创详情、拍照任务、语音留言、小游戏、品牌包装页交给 H5
---
## 2. 适合 H5 化的内容
当前最适合 H5 承接的是:
- 结算页
- 打点后的定制内容页
- 文创详情页
- 活动品牌页
- 富图文任务页
- 拍照上传 / 语音留言 / 小游戏类互动页
- 表单、问卷、抽奖、作品提交页
不建议 H5 化的部分:
- 地图主界面
- 打点逻辑
- 自动转图
- 指北针
- HUD
- GPS / 心率等实时能力主链
- 需要强实时状态同步的高频游戏弹层
一句话:
**核心实时游戏层保留原生,变化快的定制内容层交给 H5。**
---
## 3. 总体架构
推荐分成三层:
### 3.1 原生层
职责:
- 地图与渲染
- GPS / 指北针 / 自动转图
- 打点状态机
- HUD
- 心率 / telemetry
- 原生内容卡兜底
- 原生结果页兜底
- 核心状态与本地缓存
### 3.2 H5 体验层
职责:
- 定制内容展示
- 品牌包装
- 富交互任务
- 定制结算页
- 富图文与媒体内容
### 3.3 Bridge 层
职责:
- 原生向 H5 注入上下文
- H5 向原生请求能力
- H5 把结果回传原生
---
## 4. 两种 H5 页面类型
### 4.1 Content Experience Page
用于游戏中途的内容体验页。
典型场景:
- 控制点打卡后弹文创详情
- 控制点点击后查看图文内容
- 拍照上传任务
- 语音留言任务
- 小游戏互动页
- 问答/表单类互动页
### 4.2 Result Experience Page
用于游戏结束后的定制结算页。
典型场景:
- 活动定制结算
- 奖章 / 解锁内容
- 排名 / 分享
- 作品提交
- 品牌化结束页
---
## 5. 原生兜底原则
这是最重要的约束。
### 原则 1核心流程先在原生完成
例如:
- 打点成功必须先由原生确认
- 比赛结束必须先由原生确认
- H5 只是附加体验,不拥有核心状态
### 原则 2H5 打不开时回退原生
如果:
- 网络失败
- H5 地址失效
- 加载超时
- Bridge 初始化失败
则直接回退:
- 原生内容卡
- 原生结果页
### 原则 3H5 不控制比赛状态
H5 可以展示、收集信息、提交任务结果。
但不能决定:
- 是否打卡成功
- 是否比赛完成
- 是否跳点成功
这些只能由原生控制。
### 原则 4H5 是可选增强,不是主流程依赖
即使 H5 没有打开:
- 游戏仍应可继续
- 用户仍能完成路线
- 用户仍能看到最小内容或最小结果
---
## 6. 配置模型建议
后续建议对“内容体验”和“结果体验”都支持两种类型:
- `native`
- `h5`
### 6.1 内容体验配置示例
```json
{
"contentExperience": {
"type": "h5",
"url": "https://example.com/content/control-3",
"bridge": "content-v1",
"fallback": "native"
}
}
```
或:
```json
{
"contentExperience": {
"type": "native"
}
}
```
### 6.2 结果页配置示例
```json
{
"resultExperience": {
"type": "h5",
"url": "https://example.com/result/score-o",
"bridge": "result-v1",
"fallback": "native"
}
}
```
### 6.3 建议扩展字段
后续还可以逐步加入:
- `template`
- `theme`
- `timeoutMs`
- `allowClose`
- `prefetch`
- `requiresNetwork`
---
## 7. 内容页与结果页的推荐职责
### 原生最小内容卡
负责:
- 最小图文说明
- 最小点击查看
- 自动弹出兜底
### H5 内容页
负责:
- 强样式定制
- 多媒体内容
- 任务型互动
- 客户活动包装
### 原生最小结果页
负责:
- 结果一定可见
- 成绩一定可回顾
- 无网络也能展示基础结果
### H5 结果页
负责:
- 品牌化包装
- 排名/分享
- 作品提交
- 奖章、解锁、收集册
---
## 8. 性能与体验要求
H5 接入时必须注意:
- 不阻塞原生主流程
- 不把高频实时状态强行桥接到 H5
- 不在地图进行中频繁开重页面
- 低端机上优先简化交互和媒体资源
推荐策略:
- 内容详情页可以 H5
- 地图中高频反馈继续原生
- 结算增强页可以 H5
- 结果最小摘要必须原生兜底
---
## 9. 当前建议实施顺序
### 第一步
先实现:
- 原生最小兜底内容卡
- 原生最小结果页
### 第二步
新增一个通用 H5 容器页,用于承接:
- 内容页
- 结果页
### 第三步
定义 Bridge 协议,并先支持最核心动作:
- 关闭
- 获取上下文
- 拍照
- 录音
- 提交结果
### 第四步
再让配置决定:
- 当前活动走原生
- 还是走 H5
### 第五步
最后再逐步扩到:
- 上传能力
- 分享能力
- 小游戏任务
- 作品提交
---
## 10. 下一步建议
当前最适合的下一步不是直接写复杂 H5 页面,而是:
1. 先定义原生与 H5 的统一入口模型
2. 先把 Bridge 协议做小而稳
3. 先做一个通用 H5 容器页
4. 先让一个简单内容页或一个简单结果页跑通
---
## 11. 当前建议结论
最稳的方案不是“把定制内容都做成 H5”而是
**原生保底H5 承接定制体验。**
这样既能支持客户高频变化需求,也不会破坏核心游戏体验。
---
## 12. 当前主体能力约束补充
最近实际排查已经确认:
- 当前最初使用的是**个人主体**小程序
在这个前提下,`web-view` 能力可能直接受到限制。
这意味着:
- H5 页面本身可在浏览器打开
- 小程序里仍然可能无法通过 `web-view` 打开
因此当前 H5 接入方案需要增加一个现实前提:
### 12.1 个人主体下
可以先做:
- 容器页
- Bridge 协议
- 配置结构
- 原生兜底逻辑
但不要指望所有 H5 内容页都能在当前环境稳定跑通。
### 12.2 企业主体下
企业主体审核通过后,再优先回归:
- 最小 `web-view` 测试页
- 内容体验页 H5
- 结果页 H5
也就是说:
当前 H5 方案仍然成立,但在企业主体生效前,应按“预留 + 待验证”看待。
详细说明见:
- [platform-capability-notes.md](D:/dev/cmr-mini/doc/platform-capability-notes.md)

View File

@@ -0,0 +1,421 @@
# 混合体验架构方案
本文档用于说明当前项目在 **结果页、文创内容页、客户定制体验页** 上的长期承载方案。
核心结论:
**不做“原生还是 H5 二选一”,而是采用三层混合方案:**
- 原生模板
- 原生有限 DSL
- H5 扩展页
---
## 1. 为什么需要混合方案
当前项目已经确认:
- 结果页经常会变
- 打点弹出内容经常会变
- 样式、内容、交互形式会随着客户需求变化
- 有些内容还会带动作:
- 拍照上传
- 语音留言
- 小游戏
- 表单与任务
如果全部原生写死:
- 改版成本高
- 每次都要改页面代码
- 客户变化一多就会拖慢开发
如果全部交给 H5
- 核心体验不稳
- 地图主流程会割裂
- 性能和权限整合更麻烦
因此最适合的方案是:
**核心高频体验保留原生,灵活变化部分分层处理。**
---
## 2. 总体结构
建议未来的体验承载层分成三层:
### 2.1 原生模板层
用于承接:
- 最小结果页
- 最小内容卡
- 高频、必须稳的体验页
特点:
- 结构固定
- 数据可变
- 主题可换
- 性能最好
- 原生能力最完整
### 2.2 原生有限 DSL 层
用于承接:
- 结构变化较多,但组件种类有限的页面
- 结果页区块组合
- 内容页区块组合
特点:
- 不是万能布局引擎
- 只支持有限区块和有限顺序配置
- 比固定模板灵活
- 比 H5 更稳
### 2.3 H5 扩展层
用于承接:
- 强定制内容
- 富图文内容
- 品牌化包装
- 富交互任务页
- 定制结算页
特点:
- 自由度最高
- 改版速度最快
- 最适合客户高频定制
- 但必须有原生兜底
---
## 3. 三层分别适合什么
## 3.1 原生模板适合
- 高频结果页
- 高频内容卡
- 游戏过程中的即时反馈页
- 必须流畅、必须可离线的页面
示例:
- 最小顺序赛结算页
- 最小积分赛结算页
- 控制点原生内容卡
- 默认结束页
## 3.2 原生有限 DSL 适合
- 区块顺序常变
- 字段组合常变
- 样式变化不至于重做到 H5
示例:
- 结果页:
- 先显示成绩还是先显示摘要
- 哪些统计项出现
- 操作区放几个按钮
- 内容页:
- 是否显示图片
- 是否显示引用说明
- 是否显示附加说明区块
## 3.3 H5 适合
- 品牌化结算页
- 长图文故事页
- 拍照上传任务
- 语音留言页
- 小游戏互动页
- 活动专题页
- 高自由度客户定制页
---
## 4. 边界原则
## 4.1 原生负责核心游戏体验
原生必须继续负责:
- 地图
- GPS / 指北针 / 自动转图
- 打点逻辑
- HUD
- 核心状态机
- 最小结果页
- 最小内容页
## 4.2 H5 只负责增强体验
H5 不应该负责:
- 打点是否成功
- 比赛是否结束
- 核心状态推进
- 实时地图交互
H5 只负责:
- 内容展示
- 任务互动
- 品牌包装
- 富交互增强体验
## 4.3 原生永远保底
无论 H5 是否接入,原生都必须保证:
- 打点后至少能看到原生内容
- 结束后至少能看到原生结果页
- H5 打不开时,主流程不受影响
---
## 5. 推荐的数据流
建议统一做成:
```text
游戏状态 / 内容数据 / 结果数据
ViewModel
Native Template / Native DSL / H5
用户界面
```
这里最关键的是:
- 数据模型稳定
- 展示方式可换
也就是:
**先稳定 ViewModel再让模板与承载方式变化。**
---
## 6. ViewModel 的作用
ViewModel 是原生模板、原生 DSL、H5 都共用的中间层。
例如结果页:
```json
{
"type": "result-summary",
"title": "比赛结束",
"subtitle": "校园积分赛",
"hero": {
"label": "总分",
"value": "120"
},
"stats": [
{ "key": "duration", "label": "用时", "value": "23:18" },
{ "key": "distance", "label": "里程", "value": "3.2km" },
{ "key": "controls", "label": "完成点", "value": "8/8" }
],
"actions": [
{ "key": "restart", "label": "再来一局" },
{ "key": "close", "label": "返回地图" }
]
}
```
例如内容页:
```json
{
"type": "content-card",
"title": "湖边步道",
"body": "这里适合短暂停留观察周边地形。",
"image": "",
"cta": "继续"
}
```
这样以后无论:
- 原生模板
- 原生 DSL
- H5
都可以消费同一份结构化数据。
---
## 7. 原生模板层建议
建议先做有限几个模板:
### 结果页模板
- `result-minimal`
- `result-rich`
### 内容页模板
- `content-card-minimal`
- `content-card-story`
模板负责:
- 布局
- 区块顺序
- 基础动画与交互
模板不负责:
- 业务逻辑
- 数据计算
---
## 8. 原生有限 DSL 建议
不要做万能布局引擎,只做有限区块编排。
例如结果页:
```json
{
"templateType": "result-native-dsl",
"sections": [
{
"type": "hero",
"field": "score"
},
{
"type": "stats-grid",
"fields": ["duration", "distance", "controls"]
},
{
"type": "actions",
"items": ["restart", "close"]
}
]
}
```
例如内容页:
```json
{
"templateType": "content-native-dsl",
"sections": [
{ "type": "title" },
{ "type": "body" },
{ "type": "image" },
{ "type": "actions", "items": ["continue", "openH5"] }
]
}
```
原则是:
- 区块种类有限
- 顺序可配置
- 不支持无限自由布局
---
## 9. H5 扩展层建议
H5 主要用于高自由度需求。
建议先只承接两类页面:
### 9.1 Content Experience Page
用于:
- 打点后的定制内容页
- 富图文详情
- 拍照上传任务
- 语音留言
- 小游戏互动
### 9.2 Result Experience Page
用于:
- 定制结算页
- 成绩包装页
- 奖章 / 排名 / 分享页
但要注意:
- H5 只是增强页
- 原生始终保留最小兜底
---
## 10. 推荐优先级
不建议直接跳到 H5 大量实现。
推荐顺序:
### 第一步
先把原生模板层打稳:
- 原生最小结果页
- 原生最小内容卡
### 第二步
再做原生有限 DSL
- 支撑中等复杂的客户差异化需求
### 第三步
最后再把 H5 扩展层接稳:
- 处理真正高自由度场景
---
## 11. 对当前项目的建议
结合当前项目现状,建议:
### 当前优先做
- 原生内容卡继续完善
- 原生结果页继续完善
- 先把 ViewModel 定稳
### 中期做
- 原生结果页的有限 DSL
- 原生内容页的有限 DSL
### 后期做
- H5 内容页
- H5 结果页
- Bridge 能力逐步扩充
---
## 12. 一句话结论
最适合当前项目的长期方案不是单押 H5也不是所有东西都原生写死而是
**原生模板保底、原生有限 DSL 承担中度变化、H5 承担高定制内容。**
这三层结合起来,既能保证核心体验稳定,也能承接客户高频变化需求。

View File

@@ -0,0 +1,381 @@
# 原生与 H5 Bridge 协议草案
本文档定义当前项目中 **原生小程序****H5 定制内容页** 之间的基础通信协议。
目标:
- 让 H5 能获取当前游戏上下文
- 让 H5 能请求原生能力
- 让原生能接收 H5 的结果回传
- 保持协议简单、稳定、可版本化
- 为后续拍照、录音、小游戏、结果页等扩展留出空间
---
## 0. 当前适用前提
本规范当前属于:
- 协议与实现预留
- 容器与回退机制先行
最近排查已经确认,当前最初使用的是**个人主体**小程序。
在这个前提下,`web-view` 能力本身可能受限。
因此:
- Bridge 规范仍然应该先定义
- 容器页与回退机制也应该先实现
- 但在企业主体审核通过前,不应把 H5 接入是否成功完全归因于 bridge 代码本身
详细说明见:
- [platform-capability-notes.md](D:/dev/cmr-mini/doc/platform-capability-notes.md)
---
## 1. 协议原则
### 原则 1Bridge 要版本化
建议先固定:
- `content-v1`
- `result-v1`
后续升级时:
- 新增 `content-v2`
- 新增 `result-v2`
不要直接改旧协议。
### 原则 2请求能力最小化
先只开放真正需要的能力,不要一开始做成“大而全总线”。
### 原则 3原生控制核心状态
Bridge 只能做:
- 展示
- 上报
- 请求能力
不能让 H5 直接改比赛核心状态。
### 原则 4消息必须可回执
每个请求都应有明确成功/失败返回,不允许 H5 靠超时猜测。
---
## 2. 通道模型
建议统一按“请求 / 响应 / 事件”三类消息组织:
- `request`
H5 请求原生能力
- `response`
原生返回能力执行结果
- `event`
原生主动推送状态变化
推荐消息外壳:
```json
{
"id": "req-001",
"channel": "request",
"type": "getGameContext",
"payload": {}
}
```
响应:
```json
{
"id": "req-001",
"channel": "response",
"type": "getGameContext",
"ok": true,
"payload": {}
}
```
---
## 3. 原生注入给 H5 的基础上下文
建议至少包含:
```json
{
"bridgeVersion": "content-v1",
"eventId": "sample-score-o-001",
"mode": "score-o",
"sessionId": "session-001",
"sessionStatus": "running",
"controlId": "control-3",
"controlKind": "control",
"title": "湖边步道",
"body": "这里适合短暂停留观察周边地形。",
"theme": "default-race"
}
```
对于结果页,可扩展为:
```json
{
"bridgeVersion": "result-v1",
"eventId": "sample-score-o-001",
"mode": "score-o",
"sessionId": "session-001",
"summary": {
"title": "比赛结束",
"heroValue": "120",
"rows": []
}
}
```
---
## 4. H5 -> 原生:第一阶段推荐动作
建议第一阶段只支持这几个:
### `close`
作用:
- 关闭当前 H5 页面
示例:
```json
{
"id": "req-001",
"channel": "request",
"type": "close",
"payload": {}
}
```
### `getGameContext`
作用:
- 让 H5 主动获取最新上下文
### `takePhoto`
作用:
- 请求原生拍照
### `recordAudio`
作用:
- 请求原生录音
### `submitResult`
作用:
- 把 H5 内的任务结果、表单或作品结果提交回原生
示例:
```json
{
"id": "req-002",
"channel": "request",
"type": "submitResult",
"payload": {
"taskId": "photo-task-1",
"status": "completed",
"assetId": "img-001"
}
}
```
---
## 5. 建议第二阶段可扩展动作
等第一阶段跑稳后,再逐步加入:
- `uploadImage`
- `uploadAudio`
- `getLocation`
- `openMiniGame`
- `submitForm`
- `share`
- `restartSession`
这些先不要第一阶段全开。
---
## 6. 原生 -> H5统一返回结构
建议统一返回:
```json
{
"id": "req-002",
"channel": "response",
"type": "takePhoto",
"ok": true,
"payload": {
"assetId": "img-001",
"url": "https://example.com/assets/img-001.jpg"
}
}
```
失败时:
```json
{
"id": "req-002",
"channel": "response",
"type": "takePhoto",
"ok": false,
"error": {
"code": "USER_CANCELLED",
"message": "用户取消拍照"
}
}
```
---
## 7. 原生 -> H5推荐事件
原生可按需主动推送轻量事件:
- `contextUpdated`
- `sessionFinished`
- `sessionExited`
- `networkChanged`
但第一阶段要克制,避免高频推送。
不建议第一阶段主动高频推:
- GPS 实时位置流
- 指北针实时角度
- HUD 高频数字
这些不适合让 H5 主导。
---
## 8. 错误码建议
建议第一阶段统一几类错误:
- `USER_CANCELLED`
- `PERMISSION_DENIED`
- `NETWORK_ERROR`
- `UNSUPPORTED_ACTION`
- `BRIDGE_NOT_READY`
- `INTERNAL_ERROR`
这样 H5 侧更容易统一处理。
---
## 9. 安全与边界
### 9.1 H5 不直接改核心比赛状态
H5 不能直接决定:
- 是否打点成功
- 是否跳点成功
- 是否比赛结束
### 9.2 H5 只能请求能力
原生决定是否执行:
- 拍照
- 录音
- 上传
- 页面关闭
### 9.3 Bridge 能力按页面类型开放
例如:
- 内容页开放 `takePhoto`
- 结果页不一定开放
后续可做按 `bridgeVersion``pageType` 的能力白名单。
---
## 10. 第一阶段推荐支持范围
建议第一阶段只正式支持:
- `close`
- `getGameContext`
- `takePhoto`
- `recordAudio`
- `submitResult`
这样足够承接:
- 文创详情
- 拍照任务
- 语音留言
- 结果页回传动作
---
## 11. 不建议第一阶段支持的内容
先不要一上来开放:
- 任意写比赛状态
- 任意切换玩法
- 任意修改地图行为
- 任意控制打点
- 高频实时 telemetry 推送
这些都属于核心状态,应该继续由原生掌控。
---
## 12. 当前建议实施顺序
1. 先实现一个通用 H5 容器页
2. 先跑通 `content-v1`
3. 先支持 5 个最小动作
4. 再跑通一个简单结果页
5. 最后再扩桥接能力
---
## 13. 当前建议结论
Bridge 的第一阶段目标,不是做成万能总线,而是:
**稳定承接定制内容页与结果页的最小需求。**
先把:
- 关闭
- 获取上下文
- 拍照
- 录音
- 结果提交
这 5 条做稳,就足够支撑第一波客户定制需求。

View File

@@ -0,0 +1,144 @@
# 平台能力与主体限制说明
本文档用于记录当前项目在 **微信小程序平台能力** 上已经确认的边界,避免后续把环境或主体限制误判成代码问题。
---
## 1. 当前已确认的关键事实
当前项目最初使用的是**个人主体**小程序。
在这个前提下,已经出现并确认了以下问题:
- `web-view` 无法打开指定 H5 页面
- 某些传感器能力在不同设备上表现异常或不稳定
- 部分能力在 `iOS``Android` 上差异极大
这些问题在排查后,已经基本确认不完全是代码链路问题,而与 **小程序主体能力边界** 直接相关。
---
## 2. 当前确认受影响的能力
### 2.1 WebView / H5 定制内容
现象:
- 浏览器中 H5 页面可正常打开
- 小程序 `web-view` 中提示:
- `无法打开该页面`
- `不支持打开 https://...`
当前结论:
- 这不代表 H5 页面本身有问题
- 也不代表内容体验链路本身有问题
- 在个人主体下,即使同域名下配置可读,`web-view` 仍可能不可用或受限
### 2.2 传感器相关能力
现象:
- `Compass` 在不同平台表现不一致
- `Accelerometer` 启动异常
- 某些 Android 设备上指北针样本不稳定
当前结论:
- 这类问题不能简单归因为算法
- 其中一部分和小程序主体能力、平台能力边界有关
- 在企业主体完成前,不宜继续对这类问题做过度代码优化
---
## 3. 为什么“同域名能读配置”不代表“能开 H5”
这是排查中最容易误判的点。
在微信小程序里:
- 读取配置、请求接口,依赖的是:
- `request` 相关域名能力
- 打开 `web-view`,依赖的是:
- `业务域名`
这两者不是同一条能力链。
因此会出现:
- 配置文件能正常读取
- 同域名 H5 页面却无法在 `web-view` 中打开
这个现象本身并不矛盾。
---
## 4. 当前开发策略
在企业主体审核完成前,建议采用以下策略:
### 4.1 原生能力优先
继续优先开发:
- 地图主流程
- 打点逻辑
- HUD
- 指北针与自动转图
- 原生内容卡兜底
- 原生结果页兜底
### 4.2 H5 与高级传感器相关能力先按“接口预留”处理
当前阶段可以继续做:
- 文档
- 模型
- 配置结构
- Bridge 设计
- 容器页
但不要再花大量时间试图用当前个人主体把所有能力彻底打通。
### 4.3 企业主体通过后再做专项回归
企业主体切换完成后,应专项回归:
- `web-view`
- `Compass`
- `Accelerometer`
- 其它之前表现异常的能力
---
## 5. 企业主体切换后的回归建议
建议回归顺序:
1. 最小 `web-view` 测试页
2. H5 内容体验页自动弹出
3. H5 点击内容页
4. `Compass` 样本接收
5. 自动转图
6. `Accelerometer`
如果最小 `web-view` 测试页仍失败,再继续查:
- 业务域名
- 当前 appid
- 当前环境版本
- 真机微信缓存
---
## 6. 一句话结论
当前阶段已经确认:
**个人主体会直接影响 `web-view` 和部分传感器能力的可用性与稳定性。**
因此在企业主体审核完成前,最合理的做法是:
- 原生主流程继续开发
- H5 和高级传感器按“预留 + 待验证”处理
- 待企业主体生效后,再统一回归验证

View File

@@ -0,0 +1,293 @@
# 游戏结算层方案
## 1. 目标
为游戏结束后的结果展示建立独立结算层,不把结算逻辑散落在:
- 规则层
- HUD
- 顶部提示
- 页面临时弹窗
目标是:
- 统一承接结束态
- 展示成绩与摘要信息
- 支撑不同玩法的结算差异
- 为后续文创奖励、奖章、分享做扩展位
一句话:
**把“比赛结束后显示点什么”提升为正式的结果场景能力。**
---
## 2. 当前现状
当前项目已经有:
- `session_finished`
- `gameSessionStatus = finished`
- 基础成绩、里程、时长、心率等 telemetry
- 游戏信息面板可读取当前状态快照
但还没有正式的:
- `ResultScene`
- `SummaryModel`
- 结束后专属页面承载
---
## 3. 设计原则
### 3.1 结算不应只是提示条
结束不是一个瞬时反馈,而是一次阶段切换。
所以它需要独立层,而不是只弹一句:
- 已完成
- 已结束
### 3.2 结算要与玩法解耦
顺序赛、积分赛、后续幽灵赛、金币赛,结算内容不同。
所以应该有:
- 通用结算结构
- 玩法补充区块
### 3.3 结算要可扩
后续可能加入:
- 奖章
- 排名
- 收藏卡
- 文创解锁
- 分享图
所以一开始就要留结构。
---
## 4. 建议的新层级
建议增加:
- `ResultScene`
概念上与这些层并列:
- `MapPresentation`
- `HUD`
- `Feedback`
- `ContentExperienceLayer`
职责:
- 承接结束态
- 持有结算模型
- 控制显示与关闭
- 为玩法结果提供统一展示结构
---
## 5. 建议的数据模型
### 5.1 SummaryModel
```ts
interface ResultSummaryModel {
title: string
subtitle: string
mode: string
finished: boolean
durationMs: number
distanceMeters: number
averageSpeedKmh: number | null
calories: number | null
averageHeartRateBpm: number | null
completedCount: number
skippedCount: number
totalCount: number
score: number | null
extraRows: Array<{ label: string; value: string }>
}
```
### 5.2 ResultSceneState
```ts
interface ResultSceneState {
visible: boolean
summary: ResultSummaryModel | null
}
```
---
## 6. 第一阶段应展示什么
建议先做一版“基础结算页”,不要一上来做复杂演出。
### 通用区域
- 赛事名称
- 玩法名称
- 完成状态
- 总用时
- 总里程
- 平均速度
- 卡路里
- 平均心率
### 玩法区域
顺序赛:
- 完成控制点数量
- 跳过点数量
- 总控制点数量
积分赛:
- 总得分
- 已完成点数
- 未完成点数
### 操作区
- 返回地图
- 关闭
- 后续再加重开 / 分享
---
## 7. 配置建议
建议在配置中预留:
```json
{
"game": {
"result": {
"enabled": true,
"showTelemetry": true,
"showCollectedContent": true,
"showAwards": false,
"template": "default"
}
}
}
```
这意味着:
- 结算是否启用
- 展示哪些区块
- 用哪个模板
都可配置。
---
## 8. 与当前架构的关系
### 规则层
负责:
- 产出 `session_finished`
### Telemetry
负责:
- 提供里程、速度、心率、卡路里等数据
### MapEngine
负责:
- 在结束时汇总通用结算模型
- 把结果快照送到页面层
### 页面层
负责:
- 渲染结算页
---
## 9. 第一阶段最小实施范围
建议第一阶段只做:
1. `session_finished -> ResultScene`
2. 基础 summary 展示
3. 顺序赛 / 积分赛的简单差异化字段
4. 手动关闭 / 返回地图
先不要一上来做:
- 复杂章节动画
- 排名
- 分享图生成
- 复杂奖章系统
---
## 10. 后续扩展方向
这层建好后,可以逐步加:
- 文创奖励
- 奖章 / 成就
- 排名
- 解锁内容
- 分享卡
- 二次引导
---
## 11. 推荐实施顺序
1. 定义 `ResultSummaryModel`
2.`MapEngine` 汇总结束快照
3. 页面层增加结果面板
4. 顺序赛 / 积分赛各补一组玩法字段
5. 再考虑动画、奖励和品牌内容
---
## 12. 与文创体验层的配合
后续建议:
- 文创体验层
- 承接“游戏中途”的体验
- 结算层
- 承接“游戏结束后”的体验
二者不要混。
如果后续结算后要解锁文创卡片,可以由:
- `ResultScene`
- 显示结算
- 结算完成后
- 再触发内容奖励卡
---
## 13. 结论
当前最合适的方向不是继续在结束时零散堆文案,而是:
**正式增加一层 `ResultScene`,承接顺序赛、积分赛以及未来更多玩法的统一结算体验。**
第一阶段先做基础 summary后续再逐步接入文创奖励、奖章、排名和过场动画。

View File

@@ -0,0 +1,234 @@
# 传感器现状总结
本文档用于说明当前小程序版本已经接入并实际使用的传感器/输入源、它们在系统中的作用,以及当前阶段的稳定边界。
## 1. 当前正式在用的传感器/输入源
### 1.1 GPS 定位
当前作用:
- 当前定位点显示
- GPS 轨迹线绘制
- 到目标点距离计算
- 打点半径判定
- 地图锁定跟随
- 自动转图中的前进方向判断
- 速度、里程、卡路里等 telemetry 统计
当前涉及层:
- `LocationController`
- `TelemetryRuntime`
- `MapEngine`
- `RuleEngine`
说明:
- 这是当前地图和玩法系统最核心的输入源。
### 1.2 Compass 罗盘
当前作用:
- 指北针
- 静止或低速时的地图朝向
- 真北 / 磁北参考切换相关显示
当前涉及层:
- `CompassHeadingController`
- `MapEngine`
说明:
- 当前自动转图的稳定主来源之一。
### 1.3 Gyroscope 陀螺仪
当前作用:
- 提供设备旋转速率调试数据
- 为设备朝向可信度提供辅助参考
- 为后续更稳的自动转图平滑能力预留输入
当前涉及层:
- `GyroscopeController`
- `TelemetryRuntime`
说明:
- 当前已接入并显示,但还没有直接主导地图旋转。
### 1.4 DeviceMotion 设备方向
当前作用:
- 提供设备朝向角参考
- 参与 `deviceHeadingDeg`
- 参与 `headingConfidence`
- 用于调试观察姿态相关信息
当前涉及层:
- `DeviceMotionController`
- `TelemetryRuntime`
说明:
- 当前不直接接管自动转图,主要作为辅助与调试输入。
### 1.5 BLE 心率带
虽然不是手机内置传感器,但当前已经是正式输入源。
当前作用:
- 实时心率采集
- HUD 颜色分区
- 卡路里估算
- 橙 / 红警戒边框
- 后续心率相关玩法
当前涉及层:
- `HeartRateController`
- `HeartRateInputController`
- `TelemetryRuntime`
- HUD / Feedback
## 2. 当前正式在用的模拟输入源
### 2.1 模拟 GPS
当前作用:
- 室内测试路线与打点
- 模拟移动
- 测试规则、HUD、自动转图
说明:
- 与真实 GPS 并列,是正式的开发调试输入源。
### 2.2 模拟心率
当前作用:
- 测试心率颜色区间
- 测试卡路里累计
- 测试边框警示
- 测试第二块 HUD
说明:
- 与真实心率带并列,是正式的开发调试输入源。
## 3. 当前没有纳入正式能力的传感器
### 3.1 Accelerometer 加速度计
当前状态:
- 在当前微信小程序运行时 / 设备环境下不稳定
- 启动时出现 `startAccelerometer:fail, has enable, should stop pre operation`
- 已从当前第一阶段正式方案中移出
结论:
- 当前小程序版本不依赖加速度计
- 后续更完整的姿态 / 运动融合,建议放到原生 Flutter 端实现
## 4. 当前地图上真正直接起作用的核心输入
如果只看当前会直接影响地图行为和玩法行为的核心输入,主要是:
- `GPS`
- `Compass`
- `Heart Rate (BLE)`
其中:
- `GPS` 负责位置、轨迹、速度、距离、打点、跟随、前进方向
- `Compass` 负责当前稳定的地图朝向与指北针
- `Heart Rate` 负责 HUD 颜色、卡路里和警戒反馈
而:
- `Gyroscope`
- `DeviceMotion`
当前更多是为后续更稳的朝向融合能力做准备。
## 5. 当前阶段的稳定边界
小程序第一阶段推荐稳定边界如下:
- 保留:
- `Location`
- `Compass`
- `Gyroscope`
- `DeviceMotion`
- `BLE Heart Rate`
- `Mock GPS`
- `Mock Heart Rate`
- 放弃:
- `Accelerometer`
结论:
- 小程序端以稳定为优先
- 更完整的原始传感器融合,放在原生 Flutter 端推进
## 6. 一句话总结
当前小程序版本已经正式使用的核心传感器 / 输入源是:
- `GPS`
- `Compass`
- `Gyroscope`
- `DeviceMotion`
- `Heart Rate (BLE)`
- `Mock GPS`
- `Mock Heart Rate`
其中真正直接驱动地图行为的核心仍然是:
- `GPS`
- `Compass`
其余能力更多承担辅助、调试、反馈和后续扩展输入的角色。
---
## 7. 当前主体能力边界补充
最近排查已经确认:
- 当前最初使用的是**个人主体**小程序
这会影响部分设备能力的可用性与稳定性,尤其是:
- `Compass`
- `Accelerometer`
-`web-view` 相关的扩展体验链
因此当前这份传感器结论要加一个前提:
**它不仅受到代码实现影响,也受到小程序主体能力边界影响。**
这意味着:
- 某些 Android 上的样本异常,不一定是算法错误
- 某些 H5 / 传感器问题,不一定能在个人主体下彻底解决
当前建议:
- 原生主流程继续开发
- 传感器高级能力与 H5 接入先保留设计与代码入口
- 等企业主体切换完成后,再做专项回归
详细说明见:
- [platform-capability-notes.md](D:/dev/cmr-mini/doc/platform-capability-notes.md)

View File

@@ -0,0 +1,209 @@
# 临时玩法讨论记录
本文档用于临时记录以下讨论内容:
- 贪吃蛇式玩法是否适配当前架构
- 超级玛丽拾金币式玩法是否适配当前架构
- 这些玩法是否需要大改现有系统
当前结论仅用于阶段讨论,不作为正式设计冻结文档。
---
## 1. 结论
当前这两类玩法都适合现有架构。
- `贪吃蛇式玩法`:适合
- `区域拾金币玩法`:适合
- 二者都不需要推翻现有主架构
- 主要工作会集中在:
- 新的 `RulePlugin`
- 新的 `modeState`
- 新的 `map/hud presentation`
- 少量内容模型扩展
也就是说,这两类玩法更像是在现有底座上继续长新玩法,而不是重做底层。
---
## 2. 为什么适合当前架构
当前系统已经拆出了以下关键层:
- 地图引擎
- 规则引擎
- telemetry 信息层
- map / hud presentation
- feedback 反馈层
- 真实 / 模拟传感输入
这意味着:
- 地图只负责显示和交互能力
- 规则层只负责玩法推进
- telemetry 只负责通用过程信息
- feedback 只负责声音、震动、动效等效果消费
因此后续新增玩法,原则上主要是“新增规则和表现”,而不是“重写地图页”。
---
## 3. 贪吃蛇式玩法分析
### 3.1 玩法本质
这类玩法通常包含:
- 玩家位置持续更新
- 轨迹形成蛇身
- 尾巴按规则增长或收缩
- 撞到自己、奖励点、危险区后触发状态变化
### 3.2 适配当前架构的原因
当前架构已经具备:
- 持续 GPS 输入
- 持续 telemetry 更新
- 规则事件驱动推进
- 地图轨迹绘制能力
- 统一反馈系统
因此它天然可以承载:
- 尾巴增长
- 尾巴裁切
- 自碰撞
- 收集奖励
- 危险区域
### 3.3 真正需要新增的内容
主要是玩法私有状态,而不是底层推翻:
- `snakeBody`
- `tailLength`
- `tailWindow`
- `collisionState`
- `collectibleState`
这些都应放入该玩法自己的 `modeState`
### 3.4 对当前架构的压力点
这类玩法会推动当前系统继续增强:
- `modeState` 承载更复杂连续状态
- `MapPresentation` 支持蛇身/危险区/奖励点等更多图元
- 规则层处理持续碰撞判定
但这些属于增强,不属于重构。
---
## 4. 区域拾金币玩法分析
### 4.1 玩法本质
这类玩法通常包含:
- 玩家在某片区域内自由移动
- 经过或进入范围后收集金币
- 有时间限制、连击或区域目标
- 可附带终点或出口点
### 4.2 适配当前架构的原因
它本质上非常接近:
- 自由收集
- 多目标高亮
- 局部 HUD 提示
而这些当前在 `score-o` 里已经有相当基础。
因此它可以看作:
- `score-o` 的泛化版
- 或“自由收集类玩法”的一个子类
### 4.3 真正需要新增的内容
这类玩法一般需要:
- 新点位类型:`coin / pickup / bonus`
- 新 HUD 信息:已收集数、剩余金币、区域完成度
- 新表现:金币图标、收集动效、区域边界
### 4.4 对当前架构的压力点
这类玩法比蛇尾玩法对底座压力更小。
它主要会推动:
- 内容模型从“控制点”继续泛化
- `MapPresentation` 支持更多点位类型
- HUD 能容纳玩法专属信息
但依然不需要大改主架构。
---
## 5. 需要补强的底座点
如果未来真的开发这两类玩法,最值得继续补强的是:
- 更明确的 `modeState` 规范
- 更强的 `MapPresentation`
- 更通用的内容模型
- 更清晰的玩法事件字典
建议后续逐步支持的通用对象类型:
- `control`
- `collectible`
- `bonus`
- `hazard`
- `trigger`
- `zone`
- `exit`
建议后续逐步支持的通用事件:
- `item_collected`
- `zone_entered`
- `zone_left`
- `self_collision`
- `combo_started`
- `combo_broken`
- `area_cleared`
---
## 6. 当前判断标准
如果未来实现这些玩法时出现以下现象,说明架构边界可能需要重审:
- 必须大改 `MapEngine`
- 必须大改 `TelemetryRuntime`
- 必须让渲染器自己猜玩法规则
- 必须把玩法私有状态塞进全局 telemetry
如果没有出现这些情况,而主要只是新增:
- `RulePlugin`
- `modeState`
- `presentation`
- `feedback`
那就说明当前架构是适配的。
---
## 7. 当前阶段总判断
结论可以总结成一句话:
当前这套架构不仅适合传统定向和积分赛,也适合继续承载更游戏化的运动玩法。
像贪吃蛇式玩法和区域拾金币玩法,都更像是“新增玩法插件”,而不是“推翻现有底座”。

View File

@@ -0,0 +1,330 @@
# 多人模拟器改造待开发文档
本文档用于记录“公网模拟器支持多人开发/多人联调”的待开发方案。
当前仅作为设计与排期参考,不代表已经进入实现阶段。
---
## 1. 目标
当前外部模拟器已经支持:
- mock GPS
- mock heart rate
- 公网 WebSocket 接入
但当前模型更接近“单会话广播”。
如果多人同时开发或联调,容易出现:
- A 的 GPS 影响 B 的小程序
- C 的心率影响 D 的 HUD
- 同一公网模拟器服务缺乏隔离能力
因此需要把模拟器体系升级成:
- 多房间
- 多身份
- 按目标订阅
最终目标是:
- 多人共用同一个公网模拟服务
- 各自的数据流互不干扰
- 为未来多人玩法联调留好底座
---
## 2. 当前问题本质
当前模拟器通信模型更像:
- 一个 WebSocket 服务
- 模拟器侧发布消息
- 小程序侧直接接收
这个模型在单人开发时足够。
但在多人开发时,缺少以下维度:
- `room`
- `actorId`
- `channel`
没有这些维度时,服务端无法做消息隔离与路由控制。
---
## 3. 建议的第一阶段方案
第一阶段不追求复杂功能,只解决“多人不串流”的核心问题。
### 3.1 核心模型
为所有模拟消息增加 3 个维度:
- `room`
- `actorId`
- `channel`
含义如下:
- `room`
表示一个独立测试空间
- `actorId`
表示房间中的一个具体模拟源
- `channel`
表示消息类型,例如 `gps``heart_rate`
### 3.2 第一阶段目标
第一阶段完成后应满足:
- A 和 B 可以共用同一个公网模拟器服务
- A 的小程序只接 A 的数据
- B 的小程序只接 B 的数据
- GPS 与心率都能隔离
---
## 4. 推荐协议
### 4.1 模拟器注册
```json
{
"type": "register_simulator",
"room": "team-dev",
"actorId": "sim-a"
}
```
### 4.2 小程序订阅
```json
{
"type": "subscribe",
"room": "team-dev",
"actorId": "sim-a",
"channels": ["gps", "heart_rate"]
}
```
### 4.3 发布 GPS
```json
{
"type": "publish",
"room": "team-dev",
"actorId": "sim-a",
"channel": "gps",
"payload": {
"type": "mock_gps",
"timestamp": 1711267200000,
"lat": 31.2304,
"lon": 121.4737,
"accuracyMeters": 6,
"speedMps": 2.4,
"headingDeg": 135
}
}
```
### 4.4 发布心率
```json
{
"type": "publish",
"room": "team-dev",
"actorId": "sim-a",
"channel": "heart_rate",
"payload": {
"type": "mock_heart_rate",
"timestamp": 1711267200000,
"bpm": 148
}
}
```
---
## 5. 服务端改造建议
### 5.1 服务端职责
服务端从“直接广播”升级成“按订阅路由”。
它需要维护每个 WebSocket 连接的元数据:
```ts
type ClientSession = {
socketId: string
role: 'simulator' | 'app'
room: string | null
actorId: string | null
channels: Set<string>
}
```
### 5.2 路由规则
服务端收到 `publish` 后,只转发给满足以下条件的客户端:
- `role === 'app'`
- `room` 一致
- `actorId` 一致
- `channels` 包含当前 `channel`
这一步完成后,多人使用同一个公网服务时就不会互串。
### 5.3 第一阶段不需要的复杂能力
第一阶段不建议先做:
- 房间成员列表
- 在线人数统计
- 历史消息回放
- 房间消息缓存
- 权限控制
这些可以等基础隔离跑通后再扩。
---
## 6. 小程序侧改造建议
### 6.1 调试面板新增字段
建议在调试面板中新增:
- `Mock Room`
- `Mock Actor`
- `保存房间/身份`
当前 GPS 和心率已经都有 mock bridge后续建议最终共用同一个逻辑目标
- 同一个桥接地址
- 同一个 `room`
- 同一个 `actorId`
### 6.2 连接流程
小程序连上 mock bridge 后,自动发送:
```json
{
"type": "subscribe",
"room": "...",
"actorId": "...",
"channels": ["gps", "heart_rate"]
}
```
这样:
- GPS 模拟只接自己的 `gps`
- 心率模拟只接自己的 `heart_rate`
### 6.3 当前架构适配性
这项改造与当前架构是兼容的。
原因:
- 它主要发生在传感层和调试链
- 不需要改规则层
- 不需要改 telemetry 语义
- 不需要改地图引擎主逻辑
---
## 7. 外部模拟器改造建议
### 7.1 第一阶段 UI 最小改动
模拟器左侧面板新增两个输入项:
- `Room`
- `Actor ID`
后续所有 GPS / 心率发送都自动带上它们。
### 7.2 推荐默认使用方式
多人开发时建议:
- 大家共用同一个公网服务地址
- `room` 用项目或阶段名
- `actorId` 用开发者自己名字或实例名
示例:
- room: `team-dev`
- actorId: `zhangsan`
- actorId: `lisi`
### 7.3 后续可扩展能力
后续如果要继续增强,可以加:
- 房间成员列表
- 一键复制当前房间配置
- 旁观模式
- 同房间多个 actor 同时显示
- 共享路径模板
---
## 8. 为什么这项改造值得做
这不只是为了多人开发方便。
它还会直接为未来这些方向打基础:
- 多人玩法联调
- 团队对抗玩法
- 领地争夺玩法
- 多角色追逐玩法
也就是说:
今天为“多人模拟器”加的 `room + actorId + channel`,未来可以直接演进成多人玩法调试底座。
---
## 9. 建议实施顺序
### 第一阶段
- 服务端支持 `register_simulator / subscribe / publish`
- 消息带 `room + actorId + channel`
- 小程序支持订阅指定 `room + actorId`
- 外部模拟器增加 `room / actorId`
### 第二阶段
- 增加房间成员列表
- 增加在线状态
- 增加多 actor 可视化
### 第三阶段
- 接多人玩法联调
- 接角色维度
- 接会话回放与共享调试
---
## 10. 第一阶段验收标准
第一阶段完成后,至少应满足:
1. 两个人同时连同一个公网模拟器服务,不串 GPS
2. 两个人同时连同一个公网模拟器服务,不串心率
3. 同一个房间中,不同 `actorId` 可以隔离
4. 一个小程序实例可以只接收自己配置的目标流
---
## 11. 当前结论
这项改造建议先保留为待开发事项。
当前阶段不急着实现,但应作为后续多人开发与多人玩法联调的重要底座能力。

View File

@@ -0,0 +1,570 @@
# 传感器接入待开发方案
本文档用于整理当前项目后续可利用的传感器能力,分为:
- 微信小程序能力边界
- 原生 Flutter App 能力边界
- 两端统一的抽象建议
- 推荐落地顺序
目标不是一次性接入所有传感器而是优先接入对当前地图玩法、自动转图、运动状态识别、HUD/反馈最有价值的能力。
---
## 1. 总体原则
传感器接入必须遵守以下原则:
- 原始传感器数据只放在 `engine/sensor`
- 融合后的高级状态放在 `telemetry`
- 地图引擎只消费“对地图有意义的结果”
- 规则引擎只在玩法确实需要时消费高级状态
- 不要把原始三轴值直接喂给地图或玩法逻辑
推荐统一产出的高级状态包括:
- `movementState`
- `headingSource`
- `devicePose`
- `headingConfidence`
- `cadenceSpm`
- `motionIntensity`
---
## 2. 微信小程序可用传感器
### 2.1 当前确认可用
基于微信小程序官方 API 与项目内 typings当前可直接使用的能力包括
- `Location`
- `wx.startLocationUpdate`
- `wx.startLocationUpdateBackground`
- `wx.onLocationChange`
- `Accelerometer`
- `wx.startAccelerometer`
- `wx.onAccelerometerChange`
- `Compass`
- `wx.startCompass`
- `wx.onCompassChange`
- `DeviceMotion`
- `wx.startDeviceMotionListening`
- `wx.onDeviceMotionChange`
- `Gyroscope`
- `wx.startGyroscope`
- `wx.onGyroscopeChange`
- `WeRunData`
- `wx.getWeRunData`
### 2.2 当前确认不可直接获得的原始能力
微信小程序没有直接开放以下原始传感器接口:
- `Gravity`
- `Linear Acceleration`
- `Rotation Vector`
- `Geomagnetic Field` 原始三轴
- `Proximity`
- 原始 `Step Counter`
说明:
- `wx.getWeRunData` 不是实时步数传感器流
- 它更适合中长期统计,不适合实时地图玩法
---
## 3. 微信小程序推荐应用方案
### 3.1 第一优先级
#### A. Gyroscope
用途:
- 提升自动转图平滑度
- 降低跑步中手机晃动导致的朝向抖动
- 增强指北针和地图旋转过渡体验
推荐产出:
- `turnRate`
- `headingSmoothFactor`
- `headingStability`
#### B. DeviceMotion
用途:
- 识别手机姿态
- 判断设备是竖持、倾斜还是接近平放
- 配合 gyro 增强朝向可信度
推荐产出:
- `devicePose`
- `orientationConfidence`
- `tiltState`
#### C. Compass
用途:
- 静止或低速时,作为持机朝向基准
- 指北针展示
推荐角色:
- 继续保留
- 作为“静止朝向输入”
- 不再单独承担跑动中的全部朝向逻辑
### 3.2 第二优先级
#### D. Accelerometer
用途:
- 辅助识别是否真的在移动
- 识别急停、抖动、运动强度变化
推荐产出:
- `motionIntensity`
- `movementConfidence`
说明:
- 不建议直接用原始加速度驱动地图行为
- 应和 GPS、gyro 一起融合使用
#### E. Location
用途:
- 当前定位
- 轨迹
- 目标距离
- movement heading
- 速度估计
推荐角色:
- 继续作为地图和玩法核心输入
- 后续更多与 gyro / accelerometer 配合使用
### 3.3 当前不建议优先投入
#### F. WeRunData
用途:
- 日级步数统计
- 长周期运动数据
当前不建议投入原因:
- 不是实时传感器
- 不适合当前地图实时玩法主链
---
## 4. 微信小程序推荐先产出的高级状态
### A. movementState
建议值:
- `idle`
- `walk`
- `run`
来源:
- GPS 速度
- accelerometer
- device motion
### B. headingSource
建议值:
- `sensor`
- `blended`
- `movement`
来源:
- compass
- gyroscope
- GPS track
### C. devicePose
建议值:
- `upright`
- `tilted`
- `flat`
来源:
- device motion
- gyroscope
### D. headingConfidence
建议值:
- `low`
- `medium`
- `high`
来源:
- compass
- gyroscope
- GPS 精度
- movement heading 是否可靠
---
## 5. 原生 Flutter App 可用传感器
原生 Flutter App 的能力边界明显更强,后续如果迁移或并行开发,可直接利用系统原始传感器。
### 5.1 可考虑直接接入
- `Location / GNSS`
- `Compass / Magnetometer`
- `Gyroscope`
- `Accelerometer`
- `Linear Acceleration`
- `Gravity`
- `Rotation Vector`
- `Step Counter / Pedometer`
- `Barometer`(如设备支持)
- `Proximity`(视玩法需求)
说明:
- Flutter 本身一般通过插件获取这些能力
- 具体以 Android / iOS 可用性差异为准
### 5.2 Flutter 相对小程序的主要优势
- 能直接拿到更完整的原始传感器矩阵
- 更适合做高质量姿态融合
- 更适合做步数、步频、跑动状态识别
- 可更深度控制后台行为和采样频率
---
## 6. Flutter 推荐应用方案
### 6.1 第一优先级
#### A. Rotation Vector
用途:
- 作为地图自动转图的高质量姿态输入
- 优于单纯磁力计 + 罗盘
推荐产出:
- `deviceHeadingDeg`
- `devicePose`
- `headingConfidence`
#### B. Gyroscope
用途:
- 旋转平滑
- 快速转身检测
- 姿态短时补偿
#### C. Linear Acceleration
用途:
- 识别运动状态
- 急停、冲刺、抖动判定
推荐产出:
- `motionIntensity`
- `movementState`
#### D. Step Counter
用途:
- 实时步数
- 步频
- 跑步状态识别
- 训练/卡路里模型增强
推荐产出:
- `stepCount`
- `cadenceSpm`
- `movementState`
### 6.2 第二优先级
#### E. Gravity
用途:
- 持机姿态识别
- 平放/竖持策略切换
#### F. Magnetometer
用途:
- 作为姿态融合底层输入
建议:
- 不建议单独直接映射到业务逻辑
- 主要与 rotation vector / gyro 融合
#### G. Barometer
用途:
- 海拔变化
- 爬升检测
适合:
- 户外定向训练
- 赛后统计
---
## 7. Flutter 推荐先产出的高级状态
### A. movementState
建议值:
- `idle`
- `walk`
- `run`
- `sprint`
来源:
- GPS
- step counter
- linear acceleration
### B. cadenceSpm
用途:
- 训练分析
- 卡路里估算增强
- 玩法资源逻辑
### C. devicePose
建议值:
- `upright`
- `tilted`
- `flat`
### D. headingSource
建议值:
- `sensor`
- `blended`
- `movement`
### E. headingConfidence
建议值:
- `low`
- `medium`
- `high`
### F. elevationTrend
建议值:
- `flat`
- `ascending`
- `descending`
来源:
- barometer
- GPS altitude
---
## 8. 两端统一抽象建议
尽管两端可用传感器不同,但建议统一抽象,不让上层感知平台差异。
### 8.1 原始层
放在:
- `engine/sensor`
职责:
- 读取平台原始传感器
- 做最基础的节流、归一化、权限处理
### 8.2 融合层
放在:
- `telemetry`
职责:
- 生成统一高级状态
- 对外屏蔽平台差异
建议统一输出:
- `movementState`
- `devicePose`
- `headingSource`
- `headingConfidence`
- `cadenceSpm`
- `motionIntensity`
### 8.3 消费层
#### 地图引擎消费
- `headingSource`
- `devicePose`
- `headingConfidence`
#### 规则层消费
- `movementState`
- `cadenceSpm`
- `motionIntensity`
#### HUD / Feedback 消费
- `movementState`
- `cadenceSpm`
- 心率 / 卡路里 / 训练强度
---
## 9. 推荐接入顺序
### 微信小程序第一阶段
先接:
- `Gyroscope`
- `DeviceMotion`
目标:
- 提升自动转图质量
- 产出更稳定的姿态与朝向可信度
### 微信小程序第二阶段
再接:
- `Accelerometer`
目标:
- 提升 movement state 识别
### Flutter 第一阶段
先接:
- `Rotation Vector`
- `Gyroscope`
- `Linear Acceleration`
目标:
- 直接建立高质量朝向与运动状态底座
### Flutter 第二阶段
再接:
- `Step Counter`
- `Gravity`
目标:
- 增强运动统计与姿态判断
---
## 10. 当前最值得优先投入的方向
如果只从当前项目收益看,最值得优先做的是:
### 微信小程序
- `Gyroscope`
- `DeviceMotion`
### Flutter
- `Rotation Vector`
- `Gyroscope`
- `Linear Acceleration`
原因:
- 这些能力最直接影响地图体验
- 最贴近当前自动转图、前进方向、姿态识别需求
- 复用价值高
---
## 11. 一句话结论
### 微信小程序
可用传感器有限,但足够继续做:
- 更稳的自动转图
- 更好的朝向平滑
- 更好的运动状态识别
最值得优先接入的是:
- `Gyroscope`
- `DeviceMotion`
- `Accelerometer`
### 原生 Flutter App
可利用的原始传感器更完整,建议未来重点发挥:
- `Rotation Vector`
- `Gyroscope`
- `Linear Acceleration`
- `Step Counter`
两端都应遵守同一个原则:
**原始传感器进 `engine/sensor`,高级状态进 `telemetry`,上层只消费统一状态。**