Design Review · v3 · 2026-09-13 · main @ 10cf93a · 方案见 §07 · 难度曲线见 §08

转转六角:一台按顺序播放动画的机器 HEX TRAILS — Gameplay & UI Review

这份评审的结论只有一句:这个项目的工程质量和文档纪律,远高于它的设计质量。大量产能被投入在可以被自动化验收的维度——可解性、对比度、回放证据、关卡数量——恰恰绕开了唯一不能被自动化验收的那个问题:玩家为什么要想。

100%
「猴子玩家」通关率
完全不看轨道,随机点击
1000 / 1000 关
1.67
动作 ÷ 棋子数
理论下限是 1.00
v0.3 为 2.60 · v0.4 为 3.42
562
1000 关里不重复的
轨道底盘数量
一块底盘最多被用 21 次
34px
91 格关卡的棋子可点直径
iOS 最小触控为 44px
541 / 1000 关低于 44px
01 动摇根基 改动成本 中

核心循环里没有决策,只有执行

这个游戏在数学上不存在「坏棋」,也不存在失败。因此它无法产生解谜游戏赖以为生的那件事:想通了。

我用仓库自己的 src/core.js 写了一个「猴子玩家」——完全不读轨道,随机点一枚在场棋子,连续 3 次点不动就随机转一下底盘。跑完全部 1000 关:

猴子玩家(零智能策略)在三个关卡版本上的表现。动作 = 点击 + 旋转。
关卡版本关数通关率动作 ÷ 棋子(中位数)含义
v0.5 · 千段轨迹1000100%1.67每枚棋子只浪费 0.67 次点击
v0.4 · 长路与惊喜(手作)20100%3.42手作版反而更需要试探
v0.3 · 弯轨 48 关(手作)48100%2.60同上

注意最后一列的方向:程序生成的 v0.5 把游戏做得比手作版更空,不是更厚。1000 关的扩产过程中,「需要思考」这件事被稀释掉了。

开局就已经解完了

对 1000 关冻结布局逐关重算,使用 core.available() 与 core.solve()。
结构指标实测为什么重要
开局即可直接离场的棋子占比中位数 64%过半棋子不需要任何判断
开局 ≥50% 棋子可出的关卡847 关85% 的关卡开局就半解
开局 0 枚可出(必须先旋转)0 关一次都没有出现过
「一波清空」:所有棋子开局全可出,任意顺序285 关28.5% 的关卡完全没有谜题
最少旋转数 = 0568 关差异化机制在 57% 关卡用不到
最少旋转数的全局上限2 次1000 关里没有一关需要转 3 次

为什么会这样:三条规则叠加的结果

① 消除是单调的。src/core.js:87 的 trace() 只按占位判定阻挡,移走一枚棋子只会减少障碍、永不增加。所以「能出的就出掉」永远是最优策略,点击顺序不影响任何结果——所有可出棋子塌缩成一个等价类,有效分支因子约等于 1。

② 失败在数学上不可达。全部 1000 关 moveLimit === tiles.length,而 moves 只在成功离场时减 1、同时棋子也少 1,两者永远相等;rotationEnabled 全为 true,canRotate() 全部返回真。Game.lost 的两个分支都永不成立,src/app.js:170 的失败分支是死代码。

③ 游戏替玩家说出了答案。src/app.js:218 在每次旋转后弹 toast「转一转,N 枚棋子的出口打开了!」——而这恰好是 432 个「需要旋转」关卡里唯一需要动脑的那一刻。同时提示按钮免费、无限次,直接把完整路线画出来。

再叠上一层:通关面板那三颗星是 src/app.js:167 里写死的三个 icon('star'),无论你用几步、用不用提示,永远三颗。mistakes 在 core.js:297 老老实实累加,全项目零处读取。系统知道玩家会点错,但点错不产生任何后果,所以没必要显示——这正是问题的自白。

怎么改 · 已修订

本节初版建议「旋转变有限资源」与「relay 停靠点上主线」已撤回。制作人的约束是面向超休闲玩家、难度尽量小且逐步释放——这两条都会引入「卡住 / 弄坏局面」的感受,对这个用户群有害。撤回的理由和替代方案见 §07 修订方案。这里保留的判断是:「难度低」和「没有选择」是两个旋钮——不动第一个旋钮,甚至再往下调,也能把第二个从 0 调到 1。

不要加扣步惩罚。docs/gameplay-direction.md §2 的「免费旋转 + 免费试错 + 无出口不判负」是全项目最正确的设计决策,必须保住。§07 的全部方案都建立在这条之上。

02 动摇根基 改动成本 大

1000 关是一个数量幻觉

1000 关只有 30 个关卡名、562 块不重复的底盘,和一条完全水平的难度曲线。它不是资产,是同一关的 1000 次随机重排。

同一块底盘,相隔 563 关

我从 levels/v0.5.json 用仓库自己的 path-math.js 把底盘还原成矢量图。下面两张是第 14 关和第 577 关,移除棋子后的轨道布局:

LEVEL 014 · 第 1 章「初见弯路」
37 格 · 7 枚棋子
LEVEL 577 · 第 12 章「曲径相逢」
37 格 · 9 枚棋子
两张图的 SVG 逐字节相同

这不是孤例。对全部 1000 关按「格坐标 + 轨道配对」做精确去重:

底盘复用统计。官方审核报告的「精确对称去重后 1000 关唯一」成立,因为它把棋子算进了指纹;玩家一眼看到的却是底盘。
指标实测
不重复的轨道底盘562 / 1000
与别的关共用底盘的关卡591 关(59%)
单块底盘的最高复用次数21 次
不重复的关卡名30 个
单个关卡名的最高出现次数63 次(花瓣 · 清挡放行)

30 个名字 = 6 种形态(花瓣 / 环绕 / 螺旋 / 回折 / 编织 / 双叶跨区)× 5 种体验(长路巡游 / 清挡放行 / 旋转换路 / 分段释放 / 轻巧反差)。整个 1000 关战役,就是这张 6×5 的表格,每格填进去 33 个程序生成的实例。

20 章,没有一章有自己的身份

下表每一行是一章 50 关。注意形态种类和体验种类两列——每一章都均匀地混着全部 6 种形态和全部 5 种体验。章节名写得有情绪(初见弯路 → 花瓣初开 → 千回百转 → 一路盛放),但它们只存在于一个下拉框的标题里。

逐章重算。「开局可出比例」是衡量「这一关需要多少判断」最直接的指标——它在 20 章里完全没有移动。
章章节名 平均格数 平均棋子(↑ 唯一在增长的量) 开局可出比例(→ 完全水平) 必旋 %形态体验
1初见弯路34 7.2 71% 40%6 / 65 / 5
2朝内的出口37 7.5 70% 44%6 / 65 / 5
3绕行花园44 8.2 72% 40%6 / 65 / 5
4轻轻让路48 9.0 69% 42%6 / 65 / 5
5环里的风景48 8.9 66% 46%6 / 65 / 5
6花瓣初开58 8.8 70% 40%6 / 65 / 5
7交织之间58 9.8 70% 44%6 / 65 / 5
8两岸来信60 9.8 70% 42%6 / 65 / 5
9回旋的发现65 10.1 69% 46%6 / 65 / 5
10层层放行68 10.9 71% 46%6 / 65 / 5
11走进花心63 10.2 69% 48%6 / 65 / 5
12曲径相逢65 10.7 70% 42%6 / 65 / 5
13另一侧的出口61 10.9 71% 40%6 / 65 / 5
14编织新路61 11.3 68% 46%6 / 65 / 5
15旋开的花园64 11.5 70% 42%6 / 65 / 5
16从容穿行63 11.8 70% 42%6 / 65 / 5
17旧路新发现70 11.9 69% 42%6 / 65 / 5
18花与回声69 11.8 71% 40%6 / 65 / 5
19千回百转66 12.1 70% 46%6 / 65 / 5
20一路盛放58 11.6 67% 46%6 / 65 / 5

从第 1 关到第 1000 关,玩家面对的认知负担没有变化,只是要多点四下。第 20 章的平均格数甚至降回到 58,比第 10 章的 68 还小。

顺便说一句核心卖点的真实密度

「长路巡游:一次点击,享受一整段弯绕离场」是设计准则里的第一条爽点。我模拟了完整通关,用 exit-animation.js 的 exitTiming() 统计玩家实际看到的每一段动画:

n = 10,208 次成功离场,来自 1000 次完整模拟通关。
观看内容实测判断
一关中最长那一段离场动画中位数 4.24 秒卖点成立
一关中全部离场动画总时长中位数 11.0 秒可接受
单次离场动画时长中位数 0.57 秒—
时长 < 0.5 秒(一闪而过)的点击占比43%密度不足
时长 ≥ 2 秒(真正的巡游)的点击占比15.2%—

结论要说公道:长路的高光每关确实会出现一次(95% 的关卡至少有一段 ≥2 秒),这一条设计目标是兑现了的。问题是其余 43% 的点击换来的是不到半秒的一闪而过——爽点存在,但被大量无内容的点击稀释了。

怎么改

停止关卡生产,把 1000 关砍到 200–300 关。排序键从「棋子数」换成三元组 (必须旋转次数, 解法波次, 首波可点比例),并在章内强制单调:第 1 章开局可出比例上限 0.80,第 6 章 0.55,第 12 章 0.35。「0 次旋转即可通关」的全局比例从 56.8% 压到每章 ≤10%——v0.1 的 24 关做到过 0%。同时把 levels/v0.5-analysis.json 移出发行包,src/levels-v05.js(单文件 9.6 MB,首屏静态 import)改成按章 dynamic import,levels/v0.5/chapter-01..20.json 已经切好了只是没接上。

03 显著影响体验 改动成本 小 · 约 15 行

核心动词「观察」,在 UI 上根本不存在

你不肯让玩家免费地「自己看」,却愿意免费无限地「直接告诉他答案」。这是整份评审里性价比最高的一处修改。

这个游戏的核心动词是看懂这枚棋子会走到哪。但正式模式里没有任何「看」的操作——没有长按、没有悬停、没有选中高亮。playTile 的第一行就是 path=null,然后直接执行。唯一能看到一条路线的方式,是把它执行掉。

而能力早就有了:

三处代码,同一个能力,三种可用性。
位置做的事谁能用
app.js:201编辑器「查看路径」工具:path=trace(draft,tile)只有开发者
app.js:244提示按钮:path=trace(...) 画出完整路线,无次数限制、无消耗玩家,但只能看求解器替他选中的那一枚
app.js:179playTile 开头 path=null玩家想看的那一枚 —— 看不了

渲染通道 renderer.js:79 已经在那儿了,提示按钮上甚至标着「免费」。当规则前提是「旋转免费、失败免费」、唯一有成本的动作是成功离场时,不给免费预览是自相矛盾的:你把所有摩擦都去掉了,却在最需要思考的环节强迫玩家用执行代替思考。结果是玩家养成「乱点试试」的习惯,这又回过头来印证了第 01 条。

怎么改

canvas 的 pointerdown 挂一个 250 ms 计时器,触发时执行 path=trace(game.level,tile,game.tiles); draw(),pointerup 清空且不调用 game.tap()。可离场用 THEME.primary、被挡用 THEME.warning 画到接触点。同时把提示按钮的文案从「提示」改成「找一枚能走的」——让「我自己看」和「帮我找」在语义上彻底分开。另外把 TILE_RADIUS 从 .985 降到 .90,让棋子底下露出一圈轨道端口:规划「移开 A 之后 B 往哪走」所必需的那段轨道,现在恰好被 A 自己完全盖住。

04 动摇根基 改动成本 中

棋盘在目标设备上物理不可读

54% 的关卡,棋子可点直径低于 iOS 最小触控尺寸。而且瓶颈是宽度不是高度——「把画布加高」这条路是堵死的。

触控尺寸

用 geometry.js 的 layout() 在 390 px 宽手机上逐档实算。下图中每个六角都按真实相对尺寸绘制,虚线圆是 44 px 最小触控参考:

19 格六角宽 82px72px可点直径37 格六角宽 59px52px可点直径61 格六角宽 47px41px可点直径91 格六角宽 38px34px可点直径
虚线圆 = 44 px 最小触控参考。实测值:layout() 取 min((width−48)/跨度X, (height−50)/跨度Y, cellLimit);对 91 格棋盘,宽度项 19.18 < 高度项 23.20,宽度胜出。我把画布高度从 492 px 调到 700 px 重算,宽度项仍然是瓶颈——布局回收救不了它。
可点直径 = 六角宽 × COLLIDER_RADIUS (0.88)。
底盘六角宽可点直径关卡数判断
19 格 (r2)82 px72 px7充裕
37 格 (r3)59 px52 px452合格
61 格 (r4)47 px41 px268低于 44 px
91 格 (r5)38 px34 px273远低于 44 px

这是第 500 关的真实棋盘

91 格、14 枚棋子、近 200 段等权重的深蓝曲线。请试着用眼睛从任意一枚棋子追到它的出口:

LEVEL 500 · 第 10 章「层层放行」· 91 格 · 14 枚棋子
在手机上,这整块图的宽度是 326 px。

棋盘越大不是「更有内容」,而是「更看不清」。而看不清 + 试错免费 = 乱点——这和第 01 条是同一个洞的两面。

renderer.js:69 其实内置了 trackEmphasis 降权机制,labs/flower 和 labs/campaign-review 都在用,唯独主游戏的 draw() 没传这个参数,所以每一根轨道都以 primary 全权重渲染。

我注意到 docs/v0.5-browser-review.md 记录了「主路线加粗」方案被撤回。撤回本身是对的——加粗 routeDesign.featured 会直接剧透答案。但正确的做法不是回到全部等权重,而是按「与任何棋子当前可达集不相交」来降权:只压掉纯装饰的线,不暗示解法。

竖屏空间分配

27% · 224px顶栏 · LEVEL 500 · 待解棋子 · toast · 130px 纯空白43% · 363px棋盘(实际绘制 326 × 363)30% · 257px左转 / 右转 · 撤销 / 重来 / 提示390 × 844
390 × 844 实测(第 500 关)。棋盘实际绘制区域 326 × 363 px。顶部 130 px 是纯空白。

在一个只有棋盘值得看的游戏里,棋盘只拿到 43%。还有一处纯浪费:design/components.css:64 用 display:none!important 把缩放工具条藏了——board-viewport.js 的双指缩放和拖动代码完整运行着,但屏幕上没有任何东西告诉玩家这个能力存在。

怎么改

① 主线棋盘上限降到 61 格,91 格那 273 关移出主线;② 非活跃轨道降到 35% 不透明度;③ 恢复缩放工具条的可见性,或至少在首次进入 61 格以上关卡时提示一次「双指可放大」;④ 回收顶部那 130 px 死区。

05 显著影响体验 改动成本 小 · 约 1 行

5 套设计 × 5 主题 = 1 套设计 × 25 个色板

没有一套设计触碰到真正的视觉问题。25 组色板全部是在同一个有缺陷的画面上调色。

我逐张看了 design/index.html 的五套方案。01 柔和原色 / 02 高彩游乐场 / 03 积木轨道屋 / 05 轻线纸境——棋盘造型、棋子形状、箭头画法、轨道渲染方式完全一致,变的只有色相。只有 04「动物弯弯镇」真的换了棋子形象(也是五套里唯一有角色感的),但代价是方向箭头被缩小、可读性下降。

真正的视觉问题——轨道不可读、棋盘没有可辨识结构、没有世界观——五套里一套都没碰。

主题的使用方式也白做了

themeScore 绑在单关难度 tier 上(campaign-browser.js:35,查表 [12,32,48,62,78]),而 tier 在相邻关之间跳来跳去:

全量重算 1000 关的主题落点。
指标实测后果
999 次关卡切换中背景变色次数696 次(69.7%)过一关屏幕闪一次,不承载任何信息
最长连续同主题6 关建立不起「第 7 章是紫色的」这种空间记忆
apricot / lagoon / lavender / rose527 / 319 / 145 / 9一个主题占 52.7%,一个只出现 9 次
midnight「每周挑战」0 关themeForScore 只返回索引 0–3,发行路径上永不可达

也就是说:投入了 5 套设计 × 5 主题的产能,玩家在发行路径上实质只会看到 2 种颜色,还是以随机顺序闪给他看。

还有一处被绿灯掩盖的问题

design/contrast-report.json 33 项全 pass,但它校验的是 token 语义配对,不是 token 在场景里的实际组合。实测白色旋转箭头压在场景背景上的对比度:

W3C 非文字对比度最低要求 3:1(项目自己在 Design.md §2 引用过)。
设计方案白色旋转箭头 vs 场景背景判断
paper · 轻线纸境1.15 – 1.20 : 1基本隐形
calm · 柔和原色1.35 – 1.60 : 1不合格
animals · 动物弯弯镇1.81 – 3.32 : 1多数不合格
toy · 积木轨道屋1.62 – 3.44 : 1多数不合格

而旋转恰恰是这个游戏最鼓励玩家做的动作——app.js:76 的 rotationHint 还会主动引导玩家去按它。这就是「文档写得很完备」和「设计做得好」之间的差距:33 项全绿的绿灯,掩盖的是屏幕上最看不见的东西恰好是最该被看见的操作。

怎么改

主题绑章节,一行改动:app.js:87 的 applyTheme 输入从 production.themeScore 换成 Math.floor(current/50),20 章映射到 5 个主题、每 4 章换一次。背景色切换从 696 次降到 4 次,每次都对应玩家能感知到的章节边界,midnight 也真正进入发行路径(第 17–20 章)。难度信息不要用背景色承载。

旋转按钮加一个实底托盘:用 --color-utility (#52697E) 做 48 px 圆角方形背景,白图标放上面——onUtility/utility 这一对在 CONTRAST_PAIRS 里已经是过审的 3:1 配对,直接复用不引入新色。同时在 CONTRAST_PAIRS 里补一条「控件 / 场景背景」让它进构建门禁。

06 显著影响体验 改动成本 中

选关界面是开发者调试面板

「解锁」是休闲解谜唯一免费的货币,现在被一个 1–1000 的输入框白送掉了。

点开那个入口(按钮的 aria-label 写的是「选择关卡」——命名本身就承认它不是地图),看到的依次是:

src/app.js:304 showMapDialog 的完整内容。
元素服务于谁
版本 <select>,选项文字如「v0.2 · 直线较多版 · 48 关」开发者 — 这是 changelog
章节 <select>(20 项)开发者
1–1000 数字输入框 + 「前往」开发者 — 「我要验证第 847 关」
上/下章翻页器 + 50 个方块按钮玩家
两段「浏览器是否允许保存」的免责说明法务
指向 labs/campaign-review/ 内部制作审核页的链接开发者 — 不该发给玩家

三个具体伤害

①「直线较多版」是在让玩家评估你的开发决策,同时告诉他「我们改过 5 次方向」。② 五套独立存档把玩家投入切成五份——打完 v0.4 的 20 关,对总进度零贡献。③ v0.1–v0.4 那 140 关是有设计文档背书的手工内容(v0.4 的 curvedRatio 0.784 对 v0.5 的 0.394),质量密度远高于 1000 关,却被埋在下拉框第二层——新玩家默认 v0.5,永远看不到。

另外 .done(已通关)这个 class 在 style.css 和 design/*.css 里一条规则都没有——通关与未通关的按钮在关卡网格里几乎没有视觉差别。

怎么改

版本 <select>、1–1000 输入框、审核页链接、存储免责说明四项全部从玩家弹窗移除;版本切换保留 ?campaign= URL 参数即可(campaign-browser.js:2-8 已支持),审核链接移到 labs/ 自己的入口。把 v0.1–v0.4 的 140 关择优混编进主线前 240 关、统一存档——一条 1140 关的线,远胜五条互不相干的线。给 .level-btn.done 写实际样式(填充色 + 星标),并按章加锁。

07 修订方案 · 零难度增加 经五路对抗核查

不动难度旋钮,把「选择」从 0 调到 1

制作人的约束:超休闲、难度尽量小、逐步释放。修订后的方案没有一条引入失败态、卡住感或资源限制;其中四条比现状更容易。它们改的是「有没有的选」,不是「要想多久」。

修订过程

初版六条方案交给五路独立视角攻击——超休闲留存制作人、仓库维护者、关卡数据分析、UX 一致性、以及最初写这份尖锐评审的人。结果:六条没有一条被判「保留原样」,每条都被指出具体漏洞;五路一致同意撤回旋转限额与 relay。下面是核对到代码和数据之后的最终版,凡是批评者的数字,我都用 core.js 自己重算过。

先修我自己漏掉的:自动提示不是两处,是三处

初版只删了 app.js:185(离场后高亮下一枚)和 app.js:218(旋转后高亮)。五路全部指出我漏了 app.js:103–110:只要场上没有任何棋子能离场,旋转按钮立刻被点亮、指令条直接写出该转哪边。我重算了这一处的影响:

432 个需旋转关,按「不旋转、贪心清到卡住」时剩余棋子数分组。这就是玩家第一次旋转时面对的状态。
首次旋转时剩余棋子关卡数占比
2 枚22352%
3 枚17340%
≥4 枚368%

92% 的旋转发生在「剩 2–3 枚、无路可走」的状态——而 103–110 恰好在那一刻把方向点亮。也就是说,「转哪边」这个全游戏唯一的真决策点,在 432 关里全部由机器代答。任何关卡曲线里的「需旋转 %」在这条提示下都是装饰。

最终方案

1

三处自动提示全部改为闲置门控,三套「看路」机制并成一套

删 app.js:185 / 218 的 hint=opened[0].id;103–110 的旋转方向高亮同样进闲置计时。阈值不是初版的 3 秒——从最后一个动画结束起算(不是从点击),T = clamp(4 s + 0.6 s × 剩余棋子, 5 s, 10 s),无出口需旋转时用 T/2;先出文字、再过 T 才画环。toast 保留但去掉数量(「有出口打开了」)——N = 1 时等于点名。闲置提示不置 helpUsed。提示按钮改为「立即触发闲置提示」,删掉 244 行直接画完整路线——长按预览、闲置提示、免费按钮三套重叠机制减为一套,代码反而少。教学关保留即时高亮。

约 30 行
当天
2

按下看路,松手发车——不是 250 ms 长按

被挡点击只花 800 ms 而且免费,「直接点、看它弹回来」比 250 ms 长按更快,所以长按永远不会被用(这一档没有长按交互)。改成 Parking Jam 手感:pointerdown 立即画该棋子路线(可出 primary / 被挡 warning 画到接触点,core.js:84 已给 collisionPoint),pointerup 在同格 = 发车、滑出 = 取消。手势仲裁:单指才启动,移动 > 8 px 取消,预览中松手不 tap 不 drag,拦 contextmenu(Android)。预览路线要画在棋子之上(renderer.js:77 现在在棋子之下)。
初版的「非可达轨道降权」作废:动态可达集 = 每次离场后棋盘自动亮出「谁能走远了」,是比 185 行更强的剧透;静态六朝向并集我实算覆盖 93–99% 的轨道段,降不了什么。替代:全盘轨道统一软化到 55–60%(用 renderer.js:69/74 已有的 secondary 通道,不用 alpha,保住 3:1 校验),只有按下的路线满色。
再加两条 always-on 的观察教学:离场棋子拖一条 200–300 ms 淡出尾迹;被挡时用 warning 色把路线画到接触点停 600 ms。每一次点击都在免费示范「它是这样走的 / 这样断的」。

约 60 行
1–2 天
3

连击:档位由「这一下打开了几枚」驱动

初版说连击「让看一眼再点有理由」——不成立:开局 64% 可出,285 关永不断,配上免费预览更不会断。连击是反馈,不是选择。但有一处能让顺序第一次有可感知的后果:app.js:184 已经算出每次点击解锁了几枚(opened.length),这是整个引擎里唯一随顺序变化的量。档位由它驱动:一下打开 N 枚就跳 N 档,opened = 0 只维持不升。「先移走闸门棋子」在 715 关里第一次被感知,零惩罚。
实现要点:计数在 game.tap 返回处(route.status==='exit'),不等动画;归零延到撞击音同帧(app.js:122 progress ≥ 0.42),否则玩家先看到连击消失再看到撞击,因果倒置;断裂静默,无红无音;旋转、预览、闲置提示都不归零;undo 用平行栈恢复;教学关不计;音高封顶 +5 半音;标签画在离场点,不占 board-toast;时间窗 2.5 s,短于闲置阈值,防「等提示续连」。

约 35 行
1 天
4

两枚只做加法的印章,不进关卡格

这一档没有按关评级——Hexa Sort、Screw Jam、Parking Jam 的通关面板都是无条件庆祝。所以印章只做加法:「完美」= 本局 mistakes === 0,达成时多放一次印章动效 + 彩带;没拿到时面板和现在完全一样(三颗硬编码的星改成无条件彩带,别再叫「星」)。不进关卡格、不进任何持久列表——地图上「3 金 40 灰」就是超休闲眼里的失败。
「一转到位」= game.rotations === par,par 取 app.js:67 已缓存的参考解旋转数;只在达成且 par > 0 时出现,缺席时什么都不显示,不显示数字。这是旋转限额的「只影响印章、不影响通关」软版本——432 个旋转关第一次有了「这一转是不是多余」的判断,零新状态。
三条明确规则:撤销洗白(core.js:278 snapshot 含 mistakes)——接受并写明;helpUsed 不影响印章;285 个全可出关不发印章、显示「畅通」、不计聚合。存档 normalizeProgress 白名单加 perfect,改 6 处测试断言。上线顺序硬依赖:第 2 条先于本条——没有零成本看路手段之前,不能让撞墙有任何记账。

约 40 行 + 存档
2 天
5

重排发布为 v0.6,用分带配额,不用全局排序

已被 §08 取代。制作人随后定下「旋转是主解法,零旋转关 ≤10%」——池子核对后,「首转时剩余 ≥50% 且一转释放 ≥2 枚」的关现有只有 12 关,重排做不出旋转为主的战役,必须重新生成约一半。下文保留作为数据层的约束记录(存档索引、验证脚本按 index 断言等仍然成立)。

「不重做任何关」在数据层不成立。v0.5.json 顺序 = chapter-XX.json = levels-v05.js,验证脚本和测试按 index 断言,每关内嵌章名,存档 completed 是索引数组——原地重排会把已通关标记静默映射到别的关。做法:campaign-versions.js 加 v0.6 并设为默认,v0.5 与其存档原样保留。
初版需求分公式错了:(1−可出比例) 与波次共线(r = 0.84),对猴子动作数做回归后三项权重是 0.08 / 0.48 / 0.43——我给了最弱项 0.6。改用分带配额:R 全可出 · G1 零旋转 ≤2 波 · G2 零旋转 ≥3 波 · T1 一转且首转剩 ≤3 枚 · T2 两转,每章配额 R 递减、T 递增。
「温柔旋转关只有 12」是我判据错——旋转的温柔度是「首转时剩几枚」不是「开局可出比例」:T1 且剩 ≤2 枚有 223 关(≤37 格 117 关),第 1 章第 2 关就能放,接住教学第 3 课。
初版前 4 章 200 关零决策 = 超休闲 D1–D3 的全部时长,流失原因会是无聊而不是难;这一档对零决策的容忍度是 5–10 关。第 1 章缩到 20 关;全可出关从第 3 章起每章 ≤15%,当喘气关。同底盘间隔 ≥100 关(562 块底盘、最大一组 21 关)。6 形态 × 5 体验改为按章解锁式亮相——这才是这一档的「坡度」。

约 80 行脚本 + 测试
1 周
6

91 格:第 2 条落地后真机重测再定,且不是一刀切

初版用 44 px 门槛砍 91 格自相矛盾——61 格 41 px 同样不过线而第 3 章就在用。改用「底盘半径 ≤ 4」。不整体删:91 格里 98 关全可出 / 一挡放行是全库最强的观赏内容(参考解最大弯数中位 16,37 格只有 10),保留作第 7 章起的「长局回报」槽,每章 ≤6 关、不连续;175 关需要决策的 91 格移到主线外的可选「大棋盘」章,不进进度分母。

数据层
随 v0.6

分带配额后的曲线

对抗核查中由关卡数据一路实算(移出需决策的 91 格、14 章 × 52 关;其底层「首转剩余棋子」分布与我的重算逐位一致)。对比初版全局排序:前 200 关需旋转 0%、全可出 100%。

分带配额曲线。「猴子 APT」= 随机玩家每枚棋子的动作数,现状全库中位 1.67。
章开局可出比例需旋转 %猴子 APT第 1 章体验分布
191%12%1.20巡游 29 · 清挡 17 · 反差 3 · 换路 3
386%21%1.32
573%38%1.81分段释放从本章起
767%44%2.1391 格长局回报从本章起
1067%48%2.21
1261%52%2.53
1449%100%2.44

第 14 章的 0.49 仍然比 v0.3 / v0.4 手作版宽松(它们的猴子 APT 是 2.60 / 3.42)。整条曲线的最高点,都没有超过项目自己两年前手作关的难度。

批评者补上的、我漏掉的(全部零难度)

+

教学改覆盖层:三个弹窗才进第 1 关,这一档是 5 秒进真关

三课教学每课通关后 app.js:164 都开一个结果弹窗——玩家要关 3 个弹窗才见到第 1 关。Screw Jam / Hexa Sort 的做法是手指覆盖层引导、无弹窗。把三课并进正式第 1–3 关做覆盖层。超休闲留存一路认为这可能是 D1 最大的单一杠杆。

表现层
2 天
+

用朝向,不用顺序:让旋转变成关于「奖励」的选择

实算:3,294 枚棋子(928 关)换一个朝向能走更长的路;285 个全可出关里有 263 关旋转后有更长的巡游。可选目标「本关里程 N / M」或「让 X 号跑完最长路」(读 trace().steps.length,core.js:87 已给),让 568 个零旋转关的旋转按钮第一次有用途,且永远不影响通关。这是核查中找到的最强的「零难度的选择」——旋转不再是关于「能不能出」,而是关于「看多长」。

原型 2 天
验证后定
+

主题绑章节 · 关名按章生成 · 手作关作章末加餐

主题绑章节(app.js:87,2 行)在初版评审里有、修订时丢了,补回。30 个关名配 1000 关,改为家族 × 体验 × 章名生成(production-plan.js 已有表,10 行)。v0.3 / v0.4 那 68 关手作关(猴子 APT 2.60 / 3.42)作各章末尾的加餐,零风险现成内容。

约 15 行
半天
-

「旋转作为可选加速器」的内容现在几乎不存在——下一版生成器的事

568 个零旋转关里,开局转 ±60° 能多开 ≥1 枚的只有 38 关,≥2 枚的 3 关。「转一下更快」这种不必需但有回报的旋转,靠重排造不出来,要在下一批生成加 rotGain ≥ 2 的候选带。记在这里,不进当期。

生成器
下一版

撤回的两条,以及它们的软版本

旋转限额:撤回维持。五路一致。这一档所有「有限资源」都靠混合休闲经济(金币买道具)兜底,本项目没有经济系统,限额只剩惩罚;而且 3 次额度恰好只会咬在 140 关两转关上——那已经是全库最难的关。软版本就是第 4 条的「一转到位」印章。
relay 停靠点:不上主线。它是阻塞态(labs/variants/README.md「P 被占用时不能进入」),与定位正面冲突。但作为第 8–10 章之后的章节专属滴灌机制(每章只出 1 次、19 格 3–5 枚样本)是这一档 D7 的常规手段,可以留作后续。

诚实的结算

最初写那份尖锐评审的视角给修订方案打了分:「没有选择」这个根本问题,六条加起来解决了 3 / 10。因为没有一条改动状态空间——移走棋子仍然只减障碍,285 关仍然任意顺序清空。剩下的分数要靠三条真正触及「后果」的东西:朝向里程目标(把旋转变成关于奖励的选择)、opened.length 驱动的连击档位(顺序第一次有可感知后果)、「一转到位」印章(旋转次数第一次有判断)。这三条合起来,是「不动难度旋钮」这个约束下的天花板。再往上就要动规则,那是验证过这一轮之后的事。

08 难度曲线 · v0.6 依据项目调研 + 原文核读

曲线必须上升,而且只有两个旋钮能让它上升

制作人定下三条:零旋转关 ≤10% 只作补偿;难度是双刃剑;曲线一定增长、增长要合理——因为老玩家熟练后同一关越玩越简单。这三条在项目自己的调研里都有出处,而且项目自己的 1000 关计划表本来就是一条上升曲线。v0.5 没有执行它。

原则对应的原文

原文由三路核读:《一箭又一箭》三篇转录交叉核对(同一场演讲,算一个来源)、Refraction 论文 PDF 本地抽文、Lily's Garden arXiv HTML。exa 台账里没有专门讨论 flow / DDA / 技能习得的来源。
制作人原则原文依据原文给的数字不能迁移的部分
老玩家越玩越简单 → 曲线要涨《一箭又一箭》:「其实我们的难度并没有变,只是他们更熟练了」「到了一定的关卡,就需要增加困难关卡的频次」;3 档扩到 5 档,多次 A/B无阈值、无频率该游戏有失败态与时限,「困难」口径不同
增长要合理 = 边际新意恒定Refraction CHI 2015:已掌握的概念代价趋近 0,编排目标是让下一关的新负担保持在目标值附近;概念耗尽后系统会连续选相近关2,377 名玩家实验;无频率数字其生成器保证所有解共享解法图,本项目有多种释放顺序
太低无聊、太高流失Lily's Garden arXiv 2401.17436 §1、§3:前 ~100 关教学与容易关,之后存活老玩家的体感稳定在均值 3.2 次尝试、难关 ≥7 次3.2 / 7 / 1 次次数全部不可用(本项目无失败态);前 100 关的长度(本项目规则少得多)
新机关低频《一箭又一箭》:新机制「平均一天只遇到 1–2 次」;日均 30–40 关,核心用户 200–300 关1–2 次/日;30–40 关/日旋转不是新机关,是主解法——它的新组合频率应远高于此

项目自己的计划表 vs. 实际产出

docs/1000-level-production-plan.md §3 的预算表是上升的:挑战关(D4+D5)从 1–50 关的 0% 升到 801–1000 关的 22.5%,D1 从 50% 降到 12.5%,D5 从第 501 关起。研究报告 §7 已把同文件里「后半程保持难度平台」修正为「体感校准目标,不能固定结构难度而忽略熟练度」——这与制作人的原则③一致。实际 v0.5:

v0.5 按项目自己的 D1–D5 档位统计。「预算」按 §3 表格分段折算到 50 关。
范围预算 D4+D5实际 D4+D5预算 D1实际 D1判断
1–500%6%50%42%第 32 关已出现 D4
51–1007%20%20%38%第 2 章 = 第 20 章
201–50013%16%12%30%D1 是预算的 2.5 倍
801–100022.5%15%12.5%30%末段比预算更容易

而且 D 档本身退化成了旋转次数的换名:campaign-evidence.js:224 每次必需旋转记 15 分,结果零旋转关从不进 D4、两转关几乎都是 D4+。档位数不是问题,档位的定义才是。

规则层的一个事实:只有两个旋钮能拉曲线

消除单调(移走棋子只减障碍)+ 旋转免费可逆,意味着「先清完能清的、再转」永远不吃亏——「什么时候转」在这套规则下永远构不成难度。能拉的只剩两个量,而 v0.5 把它们全程按平了:

对 432 个需旋转关,在「贪心清空后卡住」的状态下重算。「有效朝向」= 5 个非零朝向中能开出棋子的个数。
范围卡住时有效朝向(均值 / 5)最近有效朝向距离 = 1被旋转锁住的棋子(均值)只有一侧能开路含擦碰阻挡关91 格关
1–503.65100%2.3045%28%0%
51–2003.90100%2.6725%35%1%
201–5003.89100%2.5822%39%14%
501–8003.84100%2.7429%39%15%
801–10003.75100%2.6328%41%17%

三件事一起看:① 卡住时平均 3.8 个朝向都能开路、最近的永远只差一格、锁住的永远只是最后 2–3 枚——搜索广度和锁住占比全程为零坡度;② 真正在涨的是擦碰阻挡(28% → 41%)和 91 格大盘(0% → 17%)——这是阅读负担,不是推理,对超休闲玩家是最危险的轴;③ 一个反直觉的发现:第 1 章反而是全程最挑方向的一段(45% 单向关,之后 22–29%)——新手最先碰到的旋转最挑剔,老手碰到的反而随便转,和技能习得顺序正好相反。

还有一处结构性陷阱:production-plan.js:41 把第 3 章起每个章末(第 150、200、…、1000 关)强制成「分段释放 · 91 格 · 14 枚 · 转 2 次」。我逐关核对:18 个章末全部如此。结果是可预测的「第 50 关一定难」加上 16 对相邻双峰(149–150、249–250……),直接违反计划自己写的「不能让玩家猜到每第七关一定难」和「第 1000 关可以是庆祝关」。按日均 30–40 关,这个峰大约每 1.5 天落一次,多半落在当日会话末尾——最坏的流失位置。

v0.6 曲线:分段目标

以计划表的形状为最低坡度,按「旋转是主解法」重定义各档,把「后半程平台」改为持续缓升。数字全部是待 A/B 的起点,不是验证过的最优——原文没有给任何频率或阈值。

三个可算的轴:锁住占比 = 首次旋转时剩余棋子比例;有效朝向 = 卡住时能开路的朝向数;卡住点数 ≈ 必需旋转次数。可读性轴单独封顶,不进难度。
范围零旋转关平均旋转锁住占比卡住时有效朝向单向关D4+D5本段新引入的旋转关系
1–5010%0.925–30%≥2,两侧都行<15%0%第 2 关就转;一转收尾;一转放一组
51–20010%1.030–35%≥215–25%≈7%开局先转;往返双转;同向双转
201–50010%1.1–1.235–40%1–225–40%≈13%三波释放;单向双转;一转放一组升级
501–8008%1.340%1;出现需转 2 格40–50%≈18%首个三转(D5);早转且单向
801–10008%1.4–1.540–45%1≈50%22–25%不再引入新关系,只做跨章重组

「增长要合理」的量化(推断,依据标注):困难关间隔从前 100 关的每 12–15 关一个,到 601–1000 关的每 5 关一个;D5 之后一律立刻接喘气关,D4 之后在 300 关前立刻、之后允许隔一关,间隔无规律。新旋转关系前 10 章每章引入 1 个、首次出现放在 D3 教学位、10 关内复习 2–3 次;11–20 章不再引入新关系而做跨章重组——Refraction 的教训是概念耗尽后系统必然退化成相近关反复,v0.5 后段 489 关旋转完全无用正是这个样子。可读性两项从难度里剔除并封顶:擦碰阻挡关 ≤40%,91 格按 §07 处理。

10% 喘气关怎么放

先解决一道算术:现行 scorer 一次必需旋转 = 15 分 → 最低 D2,所以「零旋转 ≤10%」意味着 D1 ≤100 关,低于计划表的 150。D1 必须按「决策数 ≤1」定义,不按旋转次数。喘气有两种合法形态:(a) 零旋转开放巡游——占 10% 配额;(b) 一转即全清——卡住后任一方向转一次、转完全部可出、主角走长路。这是招牌爽点,旋转是兑现不是负担,应计入喘气但不占配额。而 v0.5 里「旋转是开局第一步、转完全盘打开」的关一个都没有。

100 个零旋转关的绑定顺序:每个 D5 之后 +1;每个「新关系首次出现」的 D4 之后 +1;第 1 章教学位约 20 关;章末庆祝位(替换掉强制的分段释放峰)。第 100 关以后不允许两个零旋转关相邻——v0.5 有 27 串四连、20 串三连。

池子现实:不是重排,是重做一半

按可算判据对 1000 关重算。D5 定义:≥3 次旋转,或 2 次旋转且锁住 >50% 且单向。
档可算定义现有池v0.6 需要缺口
D0 喘气零旋转,开局 ≥75% 可出,≤1 波325≈90富余
D1 一转收尾1 转,锁住 ≤2 枚,两侧都行185≈280≈100(且 223 关同一互锁套路,去重后更大)
D2 一转换路1 转,锁住 ≤50%,放 ≥3 枚或单向102≈280≈180
D3 早转 / 双转收尾1 转且锁住 >50%,或 2 转收尾64≈190≈125
D4 双转组合2 转且(锁住 >35% 或 单向 或 ≥3 波)81≈135≈55
D5 深旋转≥3 转0≈2525 — 构造原型已完成:src/deep-lock.js 四子链式锁,6 形态 × r3/r4 × 8 种子产出 55 关(锁住占比中位 78%,每转释放 ≥2 枚 38/55),可在 labs/d5/ 试玩

最关键的一个数:「首次旋转时剩余 ≥50% 且一转释放 ≥2 枚」的关,现有 1000 关里只有 12 关。旋转为主的战役靠重排做不出来。最少新生成约 480 关;D1 池去重后约 510 关。现有 140 个两转关清一色是「首转放 1 枚、二转放 2 枚」的同一节奏,v0.1 的 24 关里有 4 关需要 3 转——规则允许,生成器没做。

生成器要改的地方

1

编排:beats 按档位重写为 8 种体验,5–8 拍锯齿乐句

breath / finish / group / choice / early / return / stages / deep。每句喘气 ≤1 拍且紧跟该句最高档之后;前 4 章不用 deep;11 章起启用;峰值位置按章打乱。introduction 前 10 关改为第 2 关就转、零旋转 2 关。删掉 :35 的 stages→turn 改写和 :41 的章末强制 stages。

production-plan.js
1 天
2

生成:三个新构造参数

turn/contrast 加 lockedShare(D1 0.2 / D2 0.35 / D3 0.6)——前 N 枚按朝向 1 摆放并要求在朝向 0 被挡;stage-seed 加 stagesCount=3 与「首转释放 ≥2」;contrast 允许 home=2(两转才接上长路)。这是 D5 池从 0 到 25 的唯一路径。

production-generator.js / stage-seed.js
3–4 天
3

筛选:按档位拒绝,用 core.js 可算的量

inspectCandidate 新增 band-rotations / band-dependency / band-direction / breath-too-deep / stage-rhythm-repeat 五个拒绝码;质量分加 −3·|锁住占比 − 目标|;旋转次数只作诊断不再进分;maxDryRotations 对 D5 放宽到 3。同底盘间隔 ≥100 关。

production-generator.js / build 脚本
1–2 天
4

档位:从「旋转次数 × 15 分」改为三元组

(锁住占比, 卡住时有效朝向数, 卡住点数)。擦碰阻挡与 91 格记为可读性负担,单独封顶,不进难度分。计划 §2 本来就要求四维分开记录。

campaign-evidence.js
半天

上线后怎么判断坡度对不对

本游戏无失败态,重试次数这个仪表不存在。替代指标(先验区间,须用前 100 关真人数据标定):连续空点(点了没动的最长连续次数:1–100 关 p90 ≤2,某关 p50 ≥3 = 可读性缺陷不是难);首次离场耗时(跨章 p50 上升 = 曲线在涨,连续 300 关不变 = 平);卡住停留(无可出 → 下一次旋转的秒数,50 关后 p50 <3 s;→0 且无效旋转也 →0 = 旋转已反射化);旋转效率(有效/总,55–80% 健康,≥90% 连续 200 关 = 无决策,<40% = 瞎转);逐关会话终止率(对滚动基线,>2× 的关标出——唯一能直接看到章末双峰伤害的指标);玩家用时 / 结构估算按档分组(D4 与 D2 比值相同 = 档位没在玩家身上兑现)。

原文没回答、要试玩才能定的

熟练度在本项目里多快累积(同一批玩家第 2 章和第 15 章各玩一次同结构关,测首次有效动作前时长的下降);「太难」在无失败态下的表现(退出位置而非重试);零旋转关放在困难关之后是否被感知为奖励;「中途旋转」比「收尾旋转」难多少;11–20 章只做重组会不会陷入 Refraction 的概念耗尽。这五条决定坡度涨多快,原文只说「到一定关数」。

◆ 保住不要动

做对了的(少而实)

这个项目的技术底子是好的。上面每一条建议的代价都在十行到百行量级,而不是重构——这本身就是这套架构的功劳。

计数语义

旋转免费、失败试路免费、无出口不判负、撤销全量恢复棋盘与朝向。这消除了休闲解谜里最大的焦虑源。core.js:281–298

扫掠碰撞与绘制同源

画出来的线和判定的线是同一份数据,用 Minkowski 六边形做精确扫掠裁剪,出界射线还额外延长 24 单位防穿模。同规模项目几乎都在这里翻车。path-math.js

离场动画按真实弧长计时

exitTiming() 用 routePoints 累加实际路径长度算时长,不是固定补间。靠近出口的棋子不会被强行拖成同样长的等待。exit-animation.js:5–12

连点队列存 id 不存坐标

全文 7 行。队列只存 id,取出时对最新棋盘重新查找——旋转 240 ms 期间连点会按新朝向重判。这个细节多数项目做错。tap-queue.js

教学三课共用同一块棋盘

19 格盘不变,只把棋子从 1 → 2 → 2 加上去,第 3 课才打开旋转。玩家从教学到正式第 1 关没有任何视觉断层。方法论完全正确。tutorial-levels.js

旋转底盘这个机制本身

底盘转 60° 而棋子位置与箭头不动,把「改变路线」和「不改变起点」干净地分离开。Railbound、Cosmic Express 都是局部操作,没有直接对应物。这是全项目唯一值得押注的原创资产——而它现在有 568 关根本用不到。

▸按优先级

如果是我,会这样排

本清单已按 §07 修订。排序原则:先把机器代答的那三处收回来,再给玩家看路的手段,然后才谈奖励与内容——顺序反了,印章会惩罚一个还没有零成本看路手段的玩家。

1

三处自动提示改闲置门控,三套「看路」机制并成一套

app.js:185 / 218 / 103–110。阈值从最后一个动画结束起算、随剩余棋子伸缩;toast 去掉数量;提示按钮改为触发闲置提示。当天可做,是全部方案的前提。

约 30 行
当天
2

按下看路、松手发车 + 尾迹 + 碰撞路线 + 全盘软化

0 ms 预览,不是 250 ms 长按;预览画在棋子之上;离场留尾迹、被挡画到接触点。「非可达轨道降权」作废,改为全盘统一软化、只有按下的路线满色。

约 60 行
1–2 天
3

连击(opened.length 驱动档位)+ 两枚只做加法的印章

连击是反馈不是选择,但档位由「这一下打开了几枚」驱动,顺序第一次有可感知后果。印章不进关卡格;「一转到位」是旋转限额的软版本。依赖第 2 条先上线。

约 75 行 + 存档
3 天
4

主题绑章节 · 关名按章生成 · 教学改覆盖层 · 清理选关弹窗

前两项约 15 行。教学覆盖层是 D1 的最大单一杠杆。选关弹窗删版本下拉、1–1000 输入框、审核页链接、免责说明;给已通关按钮实际样式。

表现层
3 天
5

v0.6:分带配额重排、同底盘间隔、机制按章亮相、手作关加餐

不原地重排(存档是索引数组)。第 1 章 20 关、第 2 关就有旋转;全可出关当喘气关每章 ≤15%;91 格只留 98 关观赏关作长局回报槽。曲线见 §07。

约 80 行脚本
1 周
6

里程目标原型:让旋转成为关于「看多长」的选择

3,294 枚棋子换朝向能走更长;263 个全可出关旋转后有更长巡游。做 20 关原型验证「玩家会不会为了看更长的路而主动转」,验证过再进主线。

原型 2 天
验证后定
7

暂缓:25 套主题、Cocos / 微信移植、关卡量产、relay

直到第 1–3 条上线并看到真人数据。relay 可留作第 8 章后的章节专属机制。下一批生成器要加 rotGain ≥ 2 候选带。

—

方法与口径

做了什么

这份评审不能证明什么

这里没有任何真人玩家数据。「猴子玩家」证明的是「不看轨道也能通关」这个结构性事实,不是「玩家一定会这么玩」——真人会先认真看,看不懂才退化成乱点,而退化发生在第几关,只有试玩能回答。同理,「一关观看 11 秒」是动画时长的加总,不是玩家的注意力时长。levels/v0.5-reviews.json 里 1000/1000 关标注 playerCalibration: "not-tested",这个标注是诚实的,本页也继承它。

关于底盘复用:官方审核报告称「精确整体对称去重后 1000 关唯一」,这是成立的——它的指纹把棋子算了进去。我统计的 562 是移除棋子后的轨道底盘数。两个数字都对,衡量的是不同的东西;我认为后者更接近玩家一眼看到的画面。

关于「长路巡游」:初次统计开局态时我得到「路径中位数 1 格、动画 0.32 秒」,据此差点得出「卖点完全没兑现」的结论。完整通关模拟推翻了它——路径会随棋盘清空而变长,每关最长一段的中位数是 4.24 秒。本页采用后者。