Restore v0.1 and v0.2 archives with separate progress, publish the curved v0.3 campaign, and document the original 24-level design with reproducible evidence. Refine board presentation and movement, and keep video lab tooling within its own directory.
135 lines
11 KiB
Markdown
135 lines
11 KiB
Markdown
# 关卡、旋转、碰撞与难度
|
||
|
||
## 数据格式
|
||
|
||
平顶六边形使用轴向 `(q,r)`,逻辑 y 轴向下。中心为 `x=1.5qR`、`y=√3(r+q/2)R`。
|
||
|
||
| 方向 | 朝向 | 邻格变化 |
|
||
| --- | --- | --- |
|
||
| 0 | ↑ | (0,-1) |
|
||
| 1 | ↗ | (1,-1) |
|
||
| 2 | ↘ | (1,0) |
|
||
| 3 | ↓ | (0,1) |
|
||
| 4 | ↙ | (-1,1) |
|
||
| 5 | ↖ | (-1,0) |
|
||
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"id": "my-level",
|
||
"name": "转个方向",
|
||
"rotationEnabled": true,
|
||
"moveLimit": 1,
|
||
"cells": [{ "q": 0, "r": 0, "tracks": [3, 4, 5, 0, 1, 2] }],
|
||
"tiles": [{ "id": "t0", "q": 0, "r": 0, "dir": 1, "color": 0 }]
|
||
}
|
||
```
|
||
|
||
`tracks[i]` 为进入 i 端口后使用的出口,必须满足 `tracks[tracks[i]]===i` 且不能与自己相连。六端口共有 15 种完整配对。`color` 为 0–11;旧文件的 0–3 仍有效。编辑器使用完整 12 色库;主线渲染按初始棋子顺序取主题色组,保持单关 3–4 色,颜色不参与规则。`rotationEnabled` 可选,默认开启;旧版 JSON 仍可导入,但会按新的碰撞规则重新验证。
|
||
|
||
导入限制:1–127 格、坐标半径不超过 6、`moveLimit` 为 1–999。该字段现在表示成功消除预算,只有移除棋子扣 1;旋转和失败试路不扣预算。主线将其设为初始棋子数 `N`,只表达剩余工作量。自定义关卡仍可导入不同预算,但预算不足 `N` 时无法完成全部消除。
|
||
|
||
编辑器提供半径 2/3/4(19/37/61 格)的板型,模型支持半径 4、61 格底板。底板不具备六重旋转对称性时,禁用整体旋转,避免棋子旋转后悬空。
|
||
|
||
## 旋转是真实的规则变化
|
||
|
||
顺时针一步将所有底板格子变为 `(q,r) → (-r,q+r)`,同时将轨道端口和对应出口都加 1(模 6)。上层棋子的 `(q,r,dir)` 均不改变。六次旋转恢复原状。
|
||
|
||
每次旋转免费;只有成功离场才扣 1 次消除预算。阻挡和闭环试路均不扣预算,也不改变棋子的模型位置。旋转次数与失败尝试次数仍可单独统计。撤销保存并恢复底板、棋子、预算、朝向和这些计数。重开读取原始关卡,不能从已旋转的中间状态重开。
|
||
|
||
可旋转关卡在当前朝向没有直接出口时,不会仅因此判负。主线预算为 `N`,玩家可以反复试路和转盘,不会因为这些尝试耗尽预算。
|
||
|
||
## 连续碰撞
|
||
|
||
`path-math.js` 同时服务渲染与碰撞:每条三次贝塞尔轨道采样为 20 段,碰撞针对这些实际绘制的折线进行连续扫掠,而不是仅检查当前动画帧的位置。
|
||
|
||
每个棋子的顶面碰撞体为半径 `0.88R` 的平顶六边形,阴影和视觉侧壁不计入碰撞。两个同向六边形的 Minkowski 和是半径 `1.76R` 的六边形。将移动中心线的每段与此凸六边形做半平面裁剪,即可得到最早接触时刻。长段移动也不会穿透。
|
||
|
||
例如:移动棋子在 `(1,1)` 向上走,另一棋子在 `(0,0)`。路线经过的格子不含 `(0,0)`,但经过相邻两格之间时,移动棋子的侧面可能擦到它,因此仍被阻挡。这比仅检查路线格子占用更严格。
|
||
|
||
上述擦碰仍属于通用模型规则,工坊及任意导入数据需要正确处理。新主线另设清晰度发布门槛:沿参考解答的各决策状态检查所有剩余棋子,排除路线经过格之外的擦碰阻挡。筛选参考解答不能证明所有任意旋转、点击序列都满足同样的清晰度要求,也不代表每次任意旋转都必然产生出口。
|
||
|
||
判定发生在移动前,动画只展示结果:成功沿线离场;失败走到接触位置再退回。不允许多个棋子同时运动,避免时序歧义。生成器、求解器、提示与工坊都调用同一个 `trace()`。
|
||
|
||
## 旋转求解与生成
|
||
|
||
移走可行棋子只会减少障碍,不改变轨道,因此在任意朝向下先移除所有安全棋子不会损害后续可解性。求解器对“朝向+剩余棋子集合”做 BFS,到达一个朝向后先消除所有可离场棋子。搜索仍将每次旋转计为一条代价为 1 的边,用于寻找最少旋转方案;这是搜索目标,不是玩家的预算扣费。
|
||
|
||
求解结果区分两种数量:
|
||
|
||
```text
|
||
minimumMoves = N // 最少成功消除成本
|
||
minimumActions = N + R // 最少交互数:消除 + 旋转
|
||
```
|
||
|
||
其中 `R` 是最少旋转数。BFS 返回混合 `tap` / `rotate` 动作序列;`reachable` 表示可以清空棋盘,`solvable` 还要求预算不少于 `N`。最大扩展状态数为 20,000;若触及上限,则返回 `exhausted` 表示尚未确定,不能宣称无解或已验证可解。
|
||
|
||
基础生成器在碰撞规则下逆向放入可离场棋子,主线编排在候选基础上进行独立筛选与回放。主线制作规模为 48 关、8 章,每章 6 拍:进入、换路、展开、小挑战、释放、长局回报。顺序不按认知难度非递减排序,也不再要求每一关都必须旋转。
|
||
|
||
9 月 9 日版的全部布局由 `levels/campaign-seeds.json` 中的种子及 `options` 明确指定;`scripts/generate-levels.mjs` 据此生成关卡、共享模块和验收数据。修改种子或参数后运行 `npm run levels`,不要直接编辑派生的关卡文件。参考成功路径统计保存在 `levels/campaign-analysis.json` 的逐关 `pathUsage` 中。
|
||
|
||
`minBaseTurns` / `maxBaseTurns` 限制逆向放置时的基准朝向,`startRotation` 在此之后施加。发布门槛另按最终朝向的真实参考解答统计,不能把基准朝向的参数当成旋转后的路径保证。
|
||
|
||
大棋盘用于增加熟悉规则下的工作量:早期就可使用 37 格,后期舒缓关采用 61 格,19 格也可承载较难的局部关系。小挑战后必须有认知负担回落,长局回报不能仅靠更复杂的路线或反复空转延长时间。
|
||
|
||
当前清单单关最多 25 枚棋子,长局的工作量来自更多实际弯轨消除,不以直出棋子数量或空白格上的弯线充数。每关参考解答至少 65% 的成功离场棋子经过弯道,且至少 3 枚;直线只作少量调剂,主线不安排纯直出关。
|
||
|
||
第 1 关为 37 格、8 枚棋子,开局全部可离场,参考解答每枚恰好经过 1 处弯道,无需旋转或强制先后顺序;这不意味着转到任意朝向后仍全部可出。前 7 关通过 `lesson` 字段依次明确:箭头起步与弯轨转向、旋转换路而棋子箭头不动、交叉不换轨、阻挡先后、一两弯的舒缓消除、大盘连续解路径、完整路线观察。对应制作说明见 [前期学习与章节节奏](gameplay-direction.md)。
|
||
|
||
发布要求包括:每枚棋子在 6 个朝向下忽略占用的独立路线均无闭环;参考解答全程排除路线格外擦碰阻挡;换路和小挑战关在参考解答中最多连续旋转 1 次就打开下一次消除进展。它们是候选发布门槛,不能用来代替实际生成结果或玩家验收。完整制作方向见 [玩法方向](gameplay-direction.md),生成后逐关核对 [主线审阅报告](campaign-review.md)。
|
||
|
||
## 实际参考路径使用量
|
||
|
||
`solutionPathUsage(level, actions)` 按参考动作回放旋转和成功移除,在每次移除时调用同一 `trace()` 读取实际离场路径。它忽略没有成功离场的尝试,不统计棋盘上未使用的装饰曲线;正式通过验收的完整解答还必须满足 `pathUsage.removals === N`。
|
||
|
||
| `pathUsage` 字段 | 含义 |
|
||
| --- | --- |
|
||
| `removals` | 参考解答中的成功移除数量 |
|
||
| `curvedRemovals` / `straightRemovals` | 经过至少 1 处弯道 / 没有经过弯道的成功棋子数 |
|
||
| `curvedRatio` | `curvedRemovals / removals`;主线要求至少 0.65 |
|
||
| `singleBendRemovals` / `multiBendRemovals` | 实际经过恰好 1 处 / 多于 1 处弯道的成功棋子数 |
|
||
| `totalBends` / `maxBends` | 全部实际成功路径的总转弯数 / 单枚最大转弯数 |
|
||
| `maxConsecutiveStraight` | 参考解答中连续直出棋子的最长段落 |
|
||
| `paths` | 逐棋子的 `id`、`bends` 和路线步骤数 `cells` |
|
||
|
||
弯轨门槛同时检查 `curvedRatio >= 0.65` 与 `curvedRemovals >= 3`。它约束参考解答中真正发生的消除,不证明所有其他操作顺序具有相同比例。初始独立路线的平均转弯数也不能替代这个指标,尤其是必须先旋转才有出口的关卡。
|
||
|
||
## 认知难度(0–100)
|
||
|
||
`analyze()` 的分数只估计路线阅读与依赖判断,不直接给棋盘面积、棋子数或试错预算加分。设:
|
||
|
||
- `N`:初始棋子数。
|
||
- `T`、`Tmax`:初始朝向忽略占用时,各棋子独立路线的平均、最大转弯数。
|
||
- `W`:参考解答中的消除层数;`D = max(0, W-1)/(N-1)`,当 `N≤1` 时为 0。
|
||
- `F`:初始无碰撞可离场棋子数。
|
||
- `S`:初始状态中因路线经过格之外的擦碰而被挡的棋子数。
|
||
- `R`:求解器得到的最少旋转数。
|
||
|
||
```text
|
||
弯道追踪 = min(1, T/4) × 22
|
||
最长路线 = min(1, Tmax/8) × 12
|
||
依赖比例 = min(1, D) × 18
|
||
初始阻塞 = (1-F/N) × 28
|
||
格外擦碰 = S/N × 12
|
||
必要旋转 = min(1, R/3) × 8
|
||
总分 = 四舍五入各项未取整值之和
|
||
```
|
||
|
||
`N=0` 时比例项按 0 处理,空关卡为 0 分且不能判为有效可解关卡。不可解、搜索未确定和消除预算不足分别处理。分项显示时若单独取整,显示值之和可能与总分略有差异。
|
||
|
||
0–29 轻松、30–49 进阶、50–69 烧脑、70–100 挑战。分数是制作与比较用的结构估计,没有经过玩家通关率标定,不应直接决定主线排序。相同分数的两关可能拥有不同棋盘规模与工作量。
|
||
|
||
## 工作量与内部时长估计
|
||
|
||
`analyze().workload` 单独提供 `removals`(棋子数)、`rotations`(最少旋转数)、`minimumActions`(最少交互数)、`routeCells`(初始朝向独立路线的格步骤总数,可能包含重复访问)及 `estimatedSeconds`。
|
||
|
||
```text
|
||
estimatedSeconds = round(3 × N + 0.75 × 总转弯数 + 4 × R)
|
||
```
|
||
|
||
总转弯数是初始朝向下、忽略占用的各棋子独立路线转弯数之和。该公式仅为内部制作假设,没有真人用时数据支撑,不能称为平均通关时长或向玩家承诺的完成时间。搜索未确定或关卡不可解时,不应将其作为可发布关卡的估时。
|
||
|
||
此处总转弯数与 `pathUsage.totalBends` 口径不同:前者是初始独立路线,后者是逐步旋转后真正成功走过的参考路径。两者应分别保留,不将弯轨覆盖率、增加的路线内容或最多 25 枚棋子的规模直接换算成已验证的真人时长。
|
||
|
||
后续测试应观察主动停顿、成功消除间隔、旋转到下一次成功的间隔、失败试路原因、最后一枚棋子阶段,以及提示、撤销和重开的发生位置;明确区分观察停顿、动画占用和离开游戏的时间,再校准认知评分与工作量估计。
|