整理中文文档结构与索引

This commit is contained in:
2026-03-30 18:49:22 +08:00
parent 3b9117427e
commit 24bc60bc7f
49 changed files with 462 additions and 136 deletions

View File

@@ -0,0 +1,417 @@
# 配置驱动应用的后台管理方案建议
本文用于整理当前这类“配置驱动型地图游戏应用”的后台管理建议,面向:
- 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,356 @@
# 积分赛配置文档(基础版)
本文档用于给服务端和后台配置设计提供一份可直接落地的积分赛基础模板。
目标是先把积分赛入口结构定稳,后续程序功能再逐步细化。
---
## 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,588 @@
# 游戏配置文件设计方案(阶段讨论稿)
本文档用于整理当前阶段推荐的配置文件设计方案,供后端、客户端和后台管理设计参考。
目标是让配置真正成为游戏的驱动入口,同时兼顾后续多玩法、多资源、多活动复用。
---
## 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 组合。**

View File

@@ -0,0 +1,314 @@
# 顺序赛配置文档(基础版)
本文档用于给服务端和后台配置设计提供一份可直接落地的顺序赛基础模板。
目标是先把入口配置结构定稳,后续程序功能再逐步细化。
---
## 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,416 @@
# 默认配置模板文档(当前实现版)
本文档提供一份 **当前客户端可直接使用的默认配置模板**
目标是:
- 给服务端/后台一个稳定的起步模板
- 保证即使只填最少字段,也能正常跑起来
- 随开发持续补充和维护
说明:
- 本模板优先保证“可运行”
- 高级字段可以逐步补
- 文创内容和点击内容也已经纳入模板
---
## 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)
这样可以保证:
- 客户端实现
- 服务端配置
- 后台录入
- 联调样例
始终保持一致。