初始提交:三玩法 Web 原型与设计文档

- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试
- 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本
- 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试
- 产品文档、UI 设计规范、各玩法 GDD / HANDOFF
- 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
This commit is contained in:
cabinw
2026-09-16 14:30:59 +08:00
commit 8026e4628d
169 changed files with 22162 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
# 连连气球 · 连消畅玩版
2026-09-15 更新。用户反馈关卡打断连消节奏、球的视觉偏脏;本版把主循环改为持续合成与放飞,并重新设计气球和补给。v1 关卡设计保留在 [历史存档](archive-v1/GDD.md)。
## 体验目标
划出一串 → 气球聚到末端 → 新数膨胀 → 自动接力 → 新球落下再接 → 稳定后继续划。每次操作争取出现可理解的 2~3 连消,后期大数也有消化出口。没有过关弹窗、步数、倒计时、失败页。
## 规则
- 相邻距离仍为两球半径和的 1.35 倍;至少三颗。
- 顺号链必须全程严格 +1 或 −1;新增整条同号链。不能重复使用同一球,也不能混合顺号与同号。
- 合成结果为链中最大数 +1,ID 和位置继承链尾。
- 自动接力只围绕刚合成的球:优先寻找 `n−2 → n−1 → n`;否则寻找两颗同号连到它。每次升级后再检查。
- 首轮接力结束后补球,落稳再检查新球周围是否能接上;这一轮仍算同一次手势。
- 普通手势总计最多 3 次合成;彩虹期间最多 4 次。上限控制观看时长,也给玩家留下下一条手动长链。
- 基础分 = `100 × 新数字 × 链长`。第 k 次连消乘 k;彩虹再乘 2。
- 合成 10 时气球飞出,不占盘面,另加 10,000 分。放飞奖励固定,不再翻倍。
## 彩虹时刻
每次普通手势的能量增量是 `max(1, 手动链长−2) + 自动合成次数`。攒满 10 后,从下一次手势开始,连续 5 次双倍得分;用完重新积攒。没有限时催促,免费工具不消耗彩虹次数。撤销恢复能量和剩余次数。
## 掉落设计
| 环节 | 行为 | 目的 |
|---|---|---|
| 开局 | 24 颗,数字 1–4,固定可复现种子 | 直接上手,多条短链 |
| 下一袋 | 显示接下来 3 个数字,按序兑现 | 玩家能提前判断 |
| 配号 | 三颗连续数为一袋,随最高数逐步引入高号,自然掉落最高 6 | 补齐数字层次,保留造大数过程 |
| 落点 | 每次最多试 5 个物理落点,评价合法链数量、长链和最新气球的接力机会 | 让补球帮助下一连 |
| 容量 | 目标 24 颗,投影面积预算约 60%,半径缓慢增长 | 为新球保留空间 |
| 断号 | 自动微风;调整少量低号、重排,极端情况保留最高 6 颗并补低号 | 不出现失败页 |
| 大数 | 同号也能消化,10 号放飞 | 避免大数无限堵盘 |
正常掉落不改盘上气球的数字或 ID,不偷偷改已展示的预告;自动微风是额外救场,可能调整低号。掉落候选和救场均有迭代上限,不做无限随机重试。
## UI 与反馈
- 名称「连连气球 · POP & LINK」。浅天蓝与白色背景,粉彩气球,细描边、椭圆高光、绳结和短线;去掉纸色、杂点、叶子、厚重阴影。
- 顶部显示得分和最大气球;下一行显示彩虹能量与下一袋,中央尽量留给触摸盘面。
- 底栏是撤一步、找一串、吹口气、新一局。重开是玩家主动打开记录面板后选择。
- 拖动期间显示数字链、预计升级和最低得分。落下后的新连消是额外惊喜。
- 合成收拢、轻弹膨胀、递升短音、得分浮字和 2/3/4 连消字样;放飞 10 有独立提示。
- 合成约 155ms;补球约 215ms;手动微风约 390ms。动画完成后接收下一次手势,稳定盘面不受显示帧率影响。
- 支持系统减少动态效果设置;关闭动画也能立即看到最终分数与能量。
## 技术与存档
纯 TypeScript 核心 + 小型圆形物理 + 可序列化状态和时间线。Web 使用 Canvas 与 PointerEvent;Cocos 使用相同手势/动画/绘制数据,组件以 Graphics + Label 呈现,可后续换 Sprite 池。
新键 `mode:02-connect-1234:balloons-v2` 保存纪录与音效偏好,不覆盖旧 v1 关卡成绩。完整撤销保留最近 40 次操作,包括随机数状态、预告、盘面、能量与统计;纪录只增不减。
连消率通过固定种子随机策略和提示策略抽样验证,详见 [QA](QA.md)。它用于排查玩法断档,不代表真实玩家留存或主观爽感;后续可以基于实机试玩调整落点评分、能量阈值与节奏。
+78
View File
@@ -0,0 +1,78 @@
# 连连气球 · Cocos Creator 接入
当前交付是可玩的 Web 畅连版,以及同源 TypeScript 核心和 Creator 球槽组件。组件已使用本机 **Creator 3.8.8 的真实 cc.d.ts** 检查;Cocos 场景、手机原生包尚未运行验收。
## 导出与球槽
```bash
npm run cocos:check
npm run cocos:export -- /你的工程/assets/modes/02-connect-1234
```
默认生成 `cocos/export/02-connect-1234/`,每次重新生成,包含当前核心和 `adapters/Connect1234Board.ts`。自定义导出只写文件,不删除已有工程文件;从 v1 升级时建议导入新目录。JSON 常量导出为 TypeScript 数据模块,减少对宿主打包方式的依赖。
1. 创建本玩法 2D 场景,Canvas 建议 720 × 1280。
2. 创建 `Board`,添加 `UITransform`,尺寸例如 640 × 740;挂 `Connect1234Board`。
3. 设置 `seed`,默认 1234。没有关卡编号。
4. 球槽添加矩形 Mask 裁掉顶部进入的气球;背景、顶栏、能量条和工具按钮在宿主 UI 搭建。
逻辑坐标 **360 × 416,左上原点,Y 向下**。组件用 `UITransform.convertToNodeSpaceAR` 转换触摸,等比缩放并兼容任意锚点。只接受一根手指,触摸取消不提交;弹窗或切后台时禁用 Board 组件以取消当前手势。
## 方法与事件
| 方法 | 行为 |
|---|---|
| `startNew(seed?)` | 新开畅玩局,省略种子时使用当前时间 |
| `retry()` | 用相同种子重新开始 |
| `undo()` | 撤销上一操作,含补球、彩虹和微风 |
| `showHint()` | 显示可以实际滑出的提示链 |
| `shuffle()` | 免费微风重排并播放掉落 |
| `snapshot()` | 完整、可序列化状态副本 |
在宿主组件 `start` 中订阅并主动读取初始 `snapshot()`,避免错过 Board 的 `onLoad` 事件:
```ts
board.on('balloon-hud', ({ snapshot, canUndo, busy, fever }) => {
// 分数、最大数字、能量、剩余双倍次数、queue 前 3 项;busy 时禁用工具。
});
board.on('balloon-reaction', (stage, doubled) => {
// 每段合成的浮字/短音:stage.points、stage.index、stage.created。
});
board.on('balloon-settled', (result, snapshot) => {
// 更新本地纪录;result.feverStarted 提示下一次开始双倍。
});
```
`balloon-merge` 在整次操作开始发出完整 `FlowResult`,含按序的 `beats`。不发过关或失败事件,不自动弹结算页。
## 规则、显示与存档
- `FlowGame.commit(ids)` 同步计算最终稳定状态,返回 `merge → fall → merge…` 时间线。`FlowAnimation` 按毫秒重放,无 DOM 或帧率依赖。
- `FlowResult.stages` 是所有合成段;`beats` 是真实播放顺序;最终盘面读 `result.supply.balls` 或 `game.balls`。`result.balls` 是最后一次合成后、最终补球前的盘面。
- 数字预告先兑现再补后续袋;掉落物理与自动接力不由宿主帧率决定。
- `view.ts` 的椭圆、路径、文字指令映射到 Graphics + Label。Label 节点池复用,支持数字描边;发布时可改成气球 Sprite + Label 池。
- `record.ts` 与 `StoragePort` 适配 `sys.localStorage`,键为 `mode:02-connect-1234:balloons-v2`。记录最高分、最大数、连消、放飞和声音设置,不保存中途局面。
```ts
import { sys } from 'cc';
import { loadRecord, recordRound, saveRecord } from './src/record';
const storage = {
read: (key: string) => sys.localStorage.getItem(key),
write: (key: string, value: string) => sys.localStorage.setItem(key, value),
};
const record = loadRecord(storage);
// 每次 balloon-settled:recordRound(record, snapshot.stats); saveRecord(storage, record);
```
## 性能与后续验收
最多 24 球;稳定时仅绘制,提交时试算少量落点。所有补给随机搜索有上限。测量脚本输出本机提交耗时,见 [QA](QA.md);真机需重新测量,必要时将供给候选计算移入宿主分帧任务,同时保持同一随机序列。
不要直接换成帧率驱动的 Box2D 落点再复用当前数据假定;更换物理后需重新验证邻接、补给和手势路线。
```bash
npm run check
COCOS_TYPES=/路径/cc.d.ts npm run cocos:check
```
移植阶段剩余:Creator 实际场景、Mask 与安全区、音频、减少动态效果配置、前后台生命周期、Android/iOS 与目标小游戏平台触摸及性能验收。
+65
View File
@@ -0,0 +1,65 @@
# 连连气球 · v2 验证记录
日期:2026-09-15。当前版本为无关卡连消畅玩版。历史关卡验收见 [v1 存档](archive-v1/QA.md)。
## 自动检查
- TypeScript Web 和纯核心检查通过;核心检查不加载 DOM / Node 类型。
- **37 项测试通过**:14 项覆盖当前顺号/同号、连消分数、补球接力、彩虹五次、放飞、预告、撤销与随机确定性、微风救场、动画跳帧、存档和拥挤物理回归;23 项保留 v1 及共用代码回归。
- **3,200 次操作、3,232 个稳定盘面**通过:唯一 ID、合法数字、边界、圆形重叠和至少一条可连路线。
- 最大数值穿透 **1.174 逻辑像素**,阈值为 2px;气球外观比碰撞圆略窄。针对种子 6559 的拥挤盘面保留回归数据,补球稳定阶段增加了约束迭代。
- 物理先判断平方距离,仅对接触球开方,降低补球试算成本。
- 生产包构建成功,JS **29,024 字节**(未 gzip),没有开发观察接口。
### 连续游玩抽样
每局连续操作 100 次;不调用免费工具跳过难盘。无路时由核心自动微风处理,次数列在表内。
| 策略 | 局数 / 操作数 | 触发连消 | 每局放飞 | 自动微风 | 本机提交耗时中位数 / P95 |
|---|---:|---:|---:|---:|---:|
| 从合法链随机抽取 | 24 / 2,400 | 812,33.8% | 3–5 颗 | 161 | 8.5 / 16.7 ms |
| 沿提示路线 | 8 / 800 | 499,62.4% | 5–8 颗 | 46 | 8.5 / 15.3 ms |
32 局都玩满 100 次,全部至少合成过 10。提示策略每次还通过 `Gesture` 实际路线采样,验证链条确实能按球心滑出。
这是固定种子算法抽样,不是玩家行为统计,也不是无死局的数学证明。随机策略对搜索出的合法链抽样;提示策略更偏向长链和即时连消。耗时来自当前开发机,手机端需另测。
完整种子、分数、放飞和性能数据见 [validation.json](qa-v2/validation.json)。复现:`npm run validate`。
## 浏览器与正式包
使用 ego-browser Chromium,390 × 844 移动视口,CDP `Input.dispatchTouchEvent` 输入:
- 实际连续完成 **24 次触摸合成**,达到 **317,500 分**,最高数字 **10**,放飞 **1 颗**;触发彩虹双倍及多连消,过程未弹出关卡/失败面板。
- 验证不足三颗、触摸取消、划回上一颗均不提交。
- 合成后撤销并重走,完整盘面、队列、随机数、分数与能量相同。
- 微风不扣分或能量,撤销恢复原局面。
- 页面错误和未处理 Promise 拒绝为 **0**。
- 帮助、纪录、重开和 Escape 关闭面板可用。
- 系统减少动态效果时,手势可正常提交,立即显示最终状态;横幅不依赖动画才能可见。
- **正式包 http://localhost:8003** 使用真实触摸完成首轮合成,得到 **10,000 分**;刷新后纪录保留,开发观察接口不存在。验收后恢复验收前的 v2 纪录,旧 v1 存档保持原样。
结果:[browser.json](qa-v2/browser.json) · [production.json](qa-v2/production.json)。浏览器长局检查在物理平方距离优化前完成;优化后重新完成全部 3,200 次核心抽样、减少动态效果交互及正式包触摸检查。
## 布局
| 视口 | 结果 |
|---|---|
| 320 × 568 | 无横向溢出,页面高 650,可滚动到工具栏 |
| 360 × 740 | 完整布局 |
| 390 × 844 | 完整竖屏,主触摸验收尺寸 |
| 430 × 932 | 完整布局 |
| 844 × 390 | 保持竖屏面板,可纵向滚动 |
| 1280 × 900 | 居中面板与桌面气球插画 |
上述尺寸下主要按钮均至少 44 × 44 CSS 像素。数值见 [layout.json](qa-v2/layout.json)。
截图:[当前开局](qa-v2/mobile.png) · [3 连消](qa-v2/first-merge.png) · [彩虹](qa-v2/rainbow.png) · [放飞后](qa-v2/flight.png) · [帮助](qa-v2/help.png) · [桌面](qa-v2/desktop.png)。
浏览器验收脚本为 `tools/browser-flow*.mjs`。其中空间 ID 4 是本次已有 QA 空间;重新验收时应先创建自己的一个 QA 空间并替换 ID,再通过 `ego-browser nodejs < 脚本` 执行,不能接管已交还用户的空间。正式包脚本只恢复它自己产生的测试纪录。
## Cocos
`npm run cocos:check` 通过:导出当前核心与 `Connect1234Board`,使用本机 **Creator 3.8.8 的真实 cc.d.ts** 检查触摸、坐标转换、Graphics、Label 描边、事件和状态接口。导出目录不含旧关卡模块或题库。
尚未验收:Creator 场景运行、实体手机触摸与性能、Android/iOS 和小游戏平台原生包。接入步骤及宿主 UI 工作见 [HANDOFF](HANDOFF.md)。
@@ -0,0 +1,67 @@
> 历史存档:关卡版 v1,已由连消畅玩版替换。当前规则见 [GDD](../GDD.md)。
# 连号花园 · 玩法二实现规格
2026-09-15:按本轮需求,从最初的无尽分数设计演化为 **10 关挑战 + 自由生长**。本文件是 v1 规则。
## 核心规则
- 圆球物理堆积;相邻圆心距 ≤ 半径之和 × 1.35。
- 单指按住球,连接至少 3 个严格连续数字;升序、降序均合法,不能换向或重复用球。
- 滑回上一颗撤回一步;快速滑动分段采样;触摸取消、窗口失焦不会提交合成。
- 松手合成 `max + 1`,结果落在链尾;计分 `100 × 新数字 × 链长`。
- 球半径 `min(82, 22 + 6 × (n−1))`,保留变大的空间压力;高数封顶保证自由模式仍可触摸。
- 每次补到本关目标球数,面积最多占槽的 59%;物理稳定后才接收下一次操作。下落期间顶部开放,稳定收尾加入顶部约束,保证所有球可见且可触摸。
- 无计时,非法手势不扣步。挑战每次有效合成消耗一步,所有目标达成即过关(最后一步先检查胜利)。
- 没有合法链时允许撤销或使用本关唯一一次打乱;步数用尽可撤销或重试。
## 原创演化
- **固定种子与补球队列**:同一关重试能复盘,撤销连随机状态一并恢复。
- **10 关目标制**:三连、倒序、四连、双目标、造大数逐步教学;有限步数,不限思考时间。
- **容错**:免费提示、连续撤销、每关一次免费打乱。打乱保留球的数字、不耗步;无可行重排不扣次数。
- **提示**:开局及沿标准解操作时显示经验证的下一步;偏离后寻找当前合法链,不保证整局最优。
- **星级**:不打乱且在标准解步数以内为 3 星;标准步数 +2 以内为 2 星,其余通关为 1 星。提示、撤销不扣星。
- **自由生长**:保留无尽分数循环,用掉落权重抽样,无关卡目标和步数上限。
## 10 关
完整配置、种子、补球队列、通关轨迹见 [campaign.json](../levels/campaign.json)。
| 关 | 名称 | 主要训练 | 目标 | 步数 |
|---|---|---|---|---|
| 1 | 初芽 | 三连松手 | 合成 4 | 3 |
| 2 | 回转 | 降序与落点 | 倒序合成一次 | 4 |
| 3 | 伸展 | 长链 | 一次连 4 颗 | 5 |
| 4 | 双生 | 材料预留 | 合成两颗 5 | 6 |
| 5 | 丰收 | 长链得分 | 6,500 分 | 7 |
| 6 | 抽叶 | 两个目标 | 合成两颗 6,四连 | 8 |
| 7 | 繁枝 | 大球与落点 | 合成 7 | 9 |
| 8 | 花径 | 弯曲长链 | 五连,14,000 分 | 10 |
| 9 | 并蒂 | 连续筹备 | 合成两颗 7 | 12 |
| 10 | 满园 | 综合挑战 | 合成 8,22,000 分 | 14 |
标准解由有界搜索找到并回放验算,不宣称唯一解或最优解。自动验证证明可通关,实际玩家难度仍可继续调参。
## UI 与触摸
- 对外名称「连号花园」,奶油纸色、鼠尾草绿、柔和彩球;6 以上长叶,8 以上小花。
- 竖屏从上到下:品牌/声音/帮助 → 关卡入口 → 目标与步数 → 球槽 → 当前连线预览 → 撤销/提示/打乱/重来。
- 数字在球心,颜色不参与判定。按钮触摸区至少 44px。
- 适配安全区、320px 窄屏和横屏;球槽维持逻辑宽高比,短屏允许页面滚动,槽内禁止浏览器拖动。
- 连线显示将生成的新数和分数;合成收拢、下落、得分浮字、过关结果;支持关闭音效与减少动态效果。
- 选关底部面板,完成后解锁下一关;末关展示整套成绩并可转自由生长。
## 技术边界
`src/` 仅使用 ES2020 标准库;第二份 tsconfig 排除 DOM 和 Node 类型,持续证明无平台依赖。
- `game / rules / physics / random`:确定性状态机、规则、固定步长圆形物理。
- `input`:平台无关手势状态机。
- `goals / progress / levels`:目标、存档数据与关卡。
- `view`:输出基础绘制指令,由 Canvas 或 Cocos Graphics/Label 适配。
- `solver`:离线设计搜索、运行时轻量提示。
- `web/`:浏览器输入、Canvas、音效、HTML 界面与本地存储。
- `cocos/`:Creator 3.x 场景接入组件示例,迁移说明见 HANDOFF。
所有代码与玩法一、三隔离;不引入 React、DOM 规则、第三方物理引擎、服务端或远程素材。
@@ -0,0 +1,114 @@
> 历史存档:关卡版 v1,已由连消畅玩版替换。当前规则见 [GDD](../GDD.md)。
# 连号花园 · Cocos Creator 接手说明
## 当前交付边界
Web 版已完成十关、自由模式、动画、音效、提示、撤销、打乱、结算与本地进度。Cocos 交付的是 **可导出的同一套核心代码 + 球槽组件**;已经用本机 Creator 3.8.8 的真实 `cc.d.ts` 通过类型检查,尚未在 Creator 场景内完成运行验收。
后续移植需要在 Cocos 场景搭建顶栏、目标、按钮、选关和结算面板,绑定本文列出的事件。HTML/CSS 不作为游戏逻辑带入引擎。
## 1. 导出
在 `modes/02-connect-1234` 执行:
```bash
npm run cocos:export
```
输出 `cocos/export/02-connect-1234/`。将整个目录复制到 Creator 项目的 `assets/modes/02-connect-1234/`。也可显式指定输出路径:
```bash
npm run cocos:export -- /你的工程/assets/modes/02-connect-1234
```
导出时仅将 JSON 导入替换成生成的等价 TypeScript 数据模块,避免依赖 Creator 对 JSON module 的打包行为。规则、物理、手势、绘制指令、动画、目标、存档结构保持同一份源码。
不要手动修改导出目录;改 `src/`、`levels/` 或 `cocos/Connect1234Board.ts`,重新导出即可。
## 2. 创建球槽
1. 新建本玩法的 2D 场景,Canvas 设计分辨率建议 **720 × 1280**。
2. 在 Canvas 下创建 `Board` 节点,加 `UITransform`,尺寸可用 **640 × 740**。
3. 将导出包的 `adapters/Connect1234Board.ts` 挂到 `Board`。组件自动创建 Graphics 与复用的 Label 节点。
4. 在 Inspector 设置 `levelNumber` 为 1–10;运行后即可对球槽进行触摸连号。
5. 为 Board 的绘图区域配置矩形 Mask,裁掉从顶部掉入的球。背景颜色、圆角容器由场景美术完成。
核心坐标为 **360 × 416,左上角原点,Y 向下**。适配组件自动等比缩放并用 `UITransform.convertToNodeSpaceAR` 转换触摸坐标,不需要改变球的半径、物理或邻接算法。
## 3. 绑定 UI
组件公开:
| 方法 | 行为 |
|---|---|
| `loadLevel(number)` | 加载第 1–10 关 |
| `retry()` | 按固定种子重开当前关 |
| `undo()` | 撤回一次合成或打乱 |
| `showHint()` | 显示提示连线 |
| `shuffle()` | 每关一次重排,数字保留、不扣步 |
| `snapshot()` | 获取完整状态副本 |
在 Board 节点监听:
```ts
board.on('garden-hud', ({ level, snapshot, goals, canUndo }) => {
// 刷新标题、分数、步数、目标、工具按钮。
});
board.on('garden-merge', (result) => {
// 播放音效、振动、得分浮字。球槽自身已播放合成和下落动画。
});
board.on('garden-result', (status, snapshot) => {
// won: 成功;lost: 步数用完;stuck: 无合法链。
// 核心不会自动弹面板或跳关,由壳/UI 决定。
});
```
玩法切场景时由组件生命周期解绑触摸;只接受一根活动手指,TOUCH_CANCEL 会取消手势。宿主弹模态面板或进后台时应禁用 Board 组件,触发取消。
组件示例接入的是挑战模式。自由模式的逻辑已经在 `new Game(level, true)` 内实现,按 Web 的入口方式补一个场景按钮即可;其随机开局应确保至少有一条合法链。
## 4. 存档和音效
`src/progress.ts` 仅接受 `StoragePort` 接口。Cocos 实现:
```ts
import { sys } from 'cc';
const storage = {
read: (key: string) => sys.localStorage.getItem(key),
write: (key: string, value: string) => sys.localStorage.setItem(key, value),
};
```
存档键 `mode:02-connect-1234:v1`,保存各关最高分/星级及最高数字。不存中途盘面。音效使用 Cocos `AudioSource`,订阅 `garden-merge`;Web 的音效是本地合成的短音,不需要下载音频素材。
## 5. 物理与性能
- 继续使用核心固定步长圆形分离物理,可保证 Web 与 Cocos 的固定关卡解一致。
- `Game.commit` 先算稳定状态;`MergeAnimation` 负责重放收拢和物理过程,宿主只传入毫秒时间。
- 核心不会在每个显示帧重算关卡。最多 18 个球,稳定盘面冻住,操作时不会追着移动的球连线。
- Graphics 适配器是接入示例;发布时可将彩球换为 Sprite + Label 预制体池,保留相同球 ID 和坐标。无需变更规则。
- 如果未来换 Box2D,请保留逻辑判定盘面与显示物理解耦,或重新验证全部关卡。直接替换积分器可能改变邻接和落点。
## 6. 检查
```bash
npm run check
npm run cocos:check
```
`cocos:check` 自动寻找 macOS `/Applications/Cocos/Creator/` 已安装版本,也可提供声明文件路径:
```bash
COCOS_TYPES=/路径/cc.d.ts npm run cocos:check
```
Creator 内还需验收:实际场景字体、触摸坐标、Mask、UI 安全区、音频、Android/iOS 原生包,以及微信等目标平台的生命周期。
## 参考接口
- [Cocos 3.8 节点触摸事件](https://docs.cocos.com/creator/3.8/manual/en/engine/event/event-node.html)
- [UITransform 坐标转换](https://docs.cocos.com/creator/3.8/api/en/class/UITransform)
- [Graphics 图形接口](https://docs.cocos.com/creator/3.8/api/en/class/GraphicsComponent)
实现及类型检查以本机 Creator 3.8.8 的声明文件为准。
@@ -0,0 +1,60 @@
> 历史存档:关卡版 v1,已由连消畅玩版替换。当前规则见 [GDD](../GDD.md)。
# 验证记录
日期:2026-09-15。当前实现的验证范围如下;未将浏览器触摸模拟称为实体手机测试。
## 自动检查
`npm run check` 全部通过:
- 两套 TypeScript 检查通过,其中 `src/` 不含 DOM / Node 类型。
- **23 项测试**通过:规则、升降序、拒绝非法链、落点、最后一步成功、确定性重试、撤销、快速划线、取消、打乱、死局、失败、存档容错、十关触摸路线、帧率无关动画。
- **10 / 10 关**使用固定种子逐步回放通过;验证同时经过 `Gesture`,避免“规则上合法、实际划线无法选中”的假解。
- **1,225 个稳定盘面**通过边界与圆形重叠检查,含 **1,046 次随机合成**;最大数值穿透 **0.772 逻辑像素**(验收阈值 2px)。随机自由局自然结束;这批样本中最长连续局为 18 次合成。
- 所有挑战关的开局与补球数字都在 1–4,最终通过正常合成得到 8。
- 生产构建成功,JS 约 **33 KB**(未 gzip),不包含开发观察端口。
- 独立 `release/` 正式包在端口 8003 实际完成第一关触摸合成(1,200 分,余 2 步),确认无开发观察端口,并已清理测试成绩、恢复第一关开局。见 [production.json](../qa/production.json)。
详细关卡逐步数字、球 ID、分数、步数与压力检查统计见 [validation.json](../qa/validation.json)。搜索给出的是一条可行路线,非唯一解、非最优解证明。
## 浏览器触摸验收
Ego Chromium,390 × 844 移动视口,使用 CDP `Input.dispatchTouchEvent` 产生浏览器输入:
- 从 **第 1 关实际连续游玩到第 10 关**,没有直接修改分数、盘面或过关状态。
- 验证两颗松手不扣步、TOUCH_CANCEL 不提交、滑回上一颗撤回。
- 确认十关依次解锁、最终结果为 10 / 10、存档记录 30 / 30 星。
- 页面错误和未处理 Promise 拒绝记录为 **0**。
- 后续刷新验证成绩保留;实际点按钮验证撤销和重走完全一致、打乱不扣步且保留数字、撤销恢复打乱库存。
- 自由模式实际完成合成且步数保持无限;帮助面板可用 Escape 关闭。
- 抽取动画为跨平台模块后,再次通过浏览器验证合成、撤销、重走和打乱。
结果见 [browser.json](../qa/browser.json)、[layout.json](../qa/layout.json)。`tools/browser-*.mjs` 是本次验收脚本,使用 `GARDEN_SPACE` 指定当前已有的 Ego QA 空间、`GARDEN_QA_DIR` 指定输出目录;应使用独立测试存档,先跑通关脚本再跑布局脚本。
## 尺寸检查
| 视口 | 结果 |
|---|---|
| 320 × 568 | 无横向溢出;页面高 650px,可纵向滚动触及底栏 |
| 360 × 740 | 完整布局,主要按钮 ≥ 44px |
| 390 × 844 | 完整布局,触摸通关主测试尺寸 |
| 430 × 932 | 完整布局,主要按钮 ≥ 44px |
| 844 × 390 | 横屏仍可操作,可纵向滚动 |
| 1280 × 900 | 居中竖屏游戏面板与桌面背景 |
截图:[开局](../qa/mobile-initial.png) · [后段关卡](../qa/mobile-level8.png) · [帮助](../qa/mobile-help.png) · [选关](../qa/mobile-level-select.png) · [全通关](../qa/mobile-all-clear.png) · [桌面](../qa/desktop.png)。
## Cocos 迁移检查
`npm run cocos:check` 通过:
- 生成独立的 Creator 导入目录。
- 使用本机 **Cocos Creator 3.8.8 的真实 `cc.d.ts`** 检查核心、数据与适配组件,非手写模拟类型。
- 球槽坐标转换、Graphics、Label、触摸 API 均通过类型检查。
**仍需后续移植阶段完成**:Creator 场景实际运行、Cocos UI 面板搭建、原生 Android/iOS 与目标小游戏平台验收。当前不宣称这些工作已完成。
## 难度判断的范围
难度梯度由教学目标、解的步数、容错余量、长链和多轮合成要求制定,已验证全部关卡可通关。尚未做多人试玩统计,平均通关耗时、提示使用率、玩家卡关分布需要后续试玩采样。
@@ -0,0 +1,205 @@
{
"viewport": [
390,
844
],
"input": "CDP touch events",
"invalidCancelBacktrack": true,
"undoDeterministic": true,
"breezeUndo": true,
"moves": [
{
"turn": 1,
"score": 10000,
"combo": 3,
"peak": 6,
"flown": 0,
"feverLeft": 0
},
{
"turn": 2,
"score": 19000,
"combo": 3,
"peak": 6,
"flown": 0,
"feverLeft": 5
},
{
"turn": 3,
"score": 31000,
"combo": 2,
"peak": 7,
"flown": 0,
"feverLeft": 4
},
{
"turn": 4,
"score": 43000,
"combo": 2,
"peak": 7,
"flown": 0,
"feverLeft": 3
},
{
"turn": 5,
"score": 62200,
"combo": 3,
"peak": 7,
"flown": 0,
"feverLeft": 2
},
{
"turn": 6,
"score": 77800,
"combo": 2,
"peak": 9,
"flown": 0,
"feverLeft": 1
},
{
"turn": 7,
"score": 84200,
"combo": 2,
"peak": 9,
"flown": 0,
"feverLeft": 0
},
{
"turn": 8,
"score": 93800,
"combo": 3,
"peak": 9,
"flown": 0,
"feverLeft": 0
},
{
"turn": 9,
"score": 100700,
"combo": 2,
"peak": 9,
"flown": 0,
"feverLeft": 0
},
{
"turn": 10,
"score": 108300,
"combo": 2,
"peak": 9,
"flown": 0,
"feverLeft": 0
},
{
"turn": 11,
"score": 120200,
"combo": 3,
"peak": 9,
"flown": 0,
"feverLeft": 5
},
{
"turn": 12,
"score": 142200,
"combo": 1,
"peak": 10,
"flown": 1,
"feverLeft": 4
},
{
"turn": 13,
"score": 184200,
"combo": 4,
"peak": 10,
"flown": 1,
"feverLeft": 3
},
{
"turn": 14,
"score": 198000,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 2
},
{
"turn": 15,
"score": 217200,
"combo": 3,
"peak": 10,
"flown": 1,
"feverLeft": 1
},
{
"turn": 16,
"score": 221200,
"combo": 1,
"peak": 10,
"flown": 1,
"feverLeft": 0
},
{
"turn": 17,
"score": 236200,
"combo": 3,
"peak": 10,
"flown": 1,
"feverLeft": 0
},
{
"turn": 18,
"score": 242200,
"combo": 3,
"peak": 10,
"flown": 1,
"feverLeft": 0
},
{
"turn": 19,
"score": 249100,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 0
},
{
"turn": 20,
"score": 253300,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 5
},
{
"turn": 21,
"score": 267100,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 4
},
{
"turn": 22,
"score": 275500,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 3
},
{
"turn": 23,
"score": 305500,
"combo": 3,
"peak": 10,
"flown": 1,
"feverLeft": 2
},
{
"turn": 24,
"score": 317500,
"combo": 2,
"peak": 10,
"flown": 1,
"feverLeft": 1
}
],
"errors": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

@@ -0,0 +1,312 @@
{
"layouts": [
{
"width": 320,
"height": 568,
"scrollWidth": 320,
"scrollHeight": 650,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 50
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 50
},
{
"id": "records",
"w": 60,
"h": 61,
"bottom": 119.921875
},
{
"id": "undo",
"w": 68.75,
"h": 59.5,
"bottom": 620
},
{
"id": "hint",
"w": 68.75,
"h": 59.5,
"bottom": 620
},
{
"id": "shuffle",
"w": 68.75,
"h": 59.5,
"bottom": 620
},
{
"id": "restart",
"w": 68.75,
"h": 59.5,
"bottom": 620
}
],
"stageHeight": 326.65625
},
{
"width": 360,
"height": 740,
"scrollWidth": 360,
"scrollHeight": 740,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 50
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 50
},
{
"id": "records",
"w": 60,
"h": 61,
"bottom": 119.921875
},
{
"id": "undo",
"w": 73.5,
"h": 59.5,
"bottom": 710
},
{
"id": "hint",
"w": 73.5,
"h": 59.5,
"bottom": 710
},
{
"id": "shuffle",
"w": 73.5,
"h": 59.5,
"bottom": 710
},
{
"id": "restart",
"w": 73.5,
"h": 59.5,
"bottom": 710
}
],
"stageHeight": 416.65625
},
{
"width": 390,
"height": 844,
"scrollWidth": 390,
"scrollHeight": 844,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 57
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 57
},
{
"id": "records",
"w": 60,
"h": 75,
"bottom": 147.9453125
},
{
"id": "undo",
"w": 81,
"h": 61,
"bottom": 810
},
{
"id": "hint",
"w": 81,
"h": 61,
"bottom": 810
},
{
"id": "shuffle",
"w": 81,
"h": 61,
"bottom": 810
},
{
"id": "restart",
"w": 81,
"h": 61,
"bottom": 810
}
],
"stageHeight": 470.1015625
},
{
"width": 430,
"height": 932,
"scrollWidth": 430,
"scrollHeight": 932,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 57
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 57
},
{
"id": "records",
"w": 60,
"h": 75,
"bottom": 147.9453125
},
{
"id": "undo",
"w": 91,
"h": 61,
"bottom": 898
},
{
"id": "hint",
"w": 91,
"h": 61,
"bottom": 898
},
{
"id": "shuffle",
"w": 91,
"h": 61,
"bottom": 898
},
{
"id": "restart",
"w": 91,
"h": 61,
"bottom": 898
}
],
"stageHeight": 558.1015625
},
{
"width": 844,
"height": 390,
"scrollWidth": 844,
"scrollHeight": 650,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 51
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 51
},
{
"id": "records",
"w": 60,
"h": 61,
"bottom": 120.921875
},
{
"id": "undo",
"w": 88,
"h": 59.5,
"bottom": 619
},
{
"id": "hint",
"w": 88,
"h": 59.5,
"bottom": 619
},
{
"id": "shuffle",
"w": 88,
"h": 59.5,
"bottom": 619
},
{
"id": "restart",
"w": 88,
"h": 59.5,
"bottom": 619
}
],
"stageHeight": 324.65625
},
{
"width": 1280,
"height": 900,
"scrollWidth": 1280,
"scrollHeight": 900,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44,
"bottom": 83
},
{
"id": "help",
"w": 44,
"h": 44,
"bottom": 83
},
{
"id": "records",
"w": 60,
"h": 75,
"bottom": 176.5
},
{
"id": "undo",
"w": 88,
"h": 61,
"bottom": 844
},
{
"id": "hint",
"w": 88,
"h": 61,
"bottom": 844
},
{
"id": "shuffle",
"w": 88,
"h": 61,
"bottom": 844
},
{
"id": "restart",
"w": 88,
"h": 61,
"bottom": 844
}
],
"stageHeight": 470
}
],
"reducedMotion": true,
"keyboardModal": true
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

@@ -0,0 +1 @@
{"balls":[{"id":73,"n":8,"r":46.400000000000006,"x":155.1468869644993,"y":361.6,"vx":0,"vy":0},{"id":34,"n":4,"r":33.6,"x":232.1383484022295,"y":374.4,"vx":0,"vy":0},{"id":106,"n":8,"r":46.400000000000006,"x":306.6,"y":346.91101173225013,"vx":0,"vy":0},{"id":119,"n":1,"r":24,"x":31,"y":384,"vx":0,"vy":0},{"id":79,"n":7,"r":43.2,"x":242.29979747458898,"y":285.3798913004701,"vx":0,"vy":0},{"id":127,"n":2,"r":27.2,"x":87.42066087373445,"y":338.81330205261503,"vx":0,"vy":0},{"id":134,"n":2,"r":27.2,"x":325.8,"y":275.0430952965191,"vx":0,"vy":0},{"id":94,"n":2,"r":27.2,"x":34.2,"y":338.04324185934405,"vx":0,"vy":0},{"id":141,"n":5,"r":36.8,"x":316.2,"y":211.18521655052808,"vx":0,"vy":0},{"id":148,"n":8,"r":46.400000000000006,"x":59.06976562543874,"y":270.57975200986914,"vx":0,"vy":0},{"id":152,"n":4,"r":33.6,"x":274.2729088066474,"y":153.89093156184353,"vx":0,"vy":0},{"id":151,"n":9,"r":49.6,"x":154.30680989880818,"y":254.69261229076352,"vx":0,"vy":0},{"id":157,"n":5,"r":36.8,"x":316.2,"y":96.84270530601155,"vx":0,"vy":0},{"id":158,"n":6,"r":40,"x":47,"y":185.95289916855464,"vx":0,"vy":0},{"id":155,"n":4,"r":33.6,"x":203.52445014195436,"y":139.33193488508178,"vx":0,"vy":0},{"id":160,"n":5,"r":36.8,"x":43.8,"y":110.39302971436348,"vx":0,"vy":0},{"id":161,"n":6,"r":40,"x":125.67701639860698,"y":169.1546351531075,"vx":0,"vy":0},{"id":149,"n":5,"r":36.8,"x":226.66064639633422,"y":206.41680839495766,"vx":0,"vy":0},{"id":162,"n":4,"r":33.6,"x":40.6,"y":41.6,"vx":0,"vy":0},{"id":163,"n":1,"r":24,"x":105.05076572430656,"y":107.92440190412071,"vx":0,"vy":0},{"id":164,"n":2,"r":27.2,"x":156.26870064135628,"y":100.18081469709189,"vx":0,"vy":0}],"stats":{"score":265500,"turns":37,"reactions":66,"maxNumber":10,"bestCombo":3,"lastCombo":1,"popped":208,"flown":1},"rng":1760922726,"nextId":165,"queue":[1,2,3],"feverCharge":0,"feverLeft":3,"breezes":3}
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

@@ -0,0 +1,10 @@
{
"turns": 1,
"score": "10,000",
"errors": [],
"debugExposed": false,
"persistence": true,
"originalRecordRestored": true,
"legacyRecordPreserved": true,
"url": "http://localhost:8003/"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,300 @@
{
"generatedAt": "2026-09-15T11:34:17.717Z",
"note": "固定种子算法抽样;随机策略从合法链均匀抽取,提示策略通过真实手势控制器。连消率不是玩家数据。",
"states": 3232,
"maxOverlap": 1.173909676008897,
"strategies": [
{
"strategy": "random",
"sessions": 24,
"gestures": 2400,
"comboGestures": 812,
"comboRate": 0.3383333333333333,
"reactions": 3572,
"flown": 94,
"breezes": 161,
"minPaths": 2,
"commitMs": {
"median": 8.465915999997378,
"p95": 16.694249999998647,
"max": 27.764500000000226
},
"runs": [
{
"seed": 937,
"score": 606600,
"peak": 10,
"flown": 3,
"comboGestures": 26,
"breezes": 5
},
{
"seed": 1874,
"score": 524300,
"peak": 10,
"flown": 3,
"comboGestures": 31,
"breezes": 7
},
{
"seed": 2811,
"score": 584800,
"peak": 10,
"flown": 4,
"comboGestures": 32,
"breezes": 9
},
{
"seed": 3748,
"score": 510100,
"peak": 10,
"flown": 3,
"comboGestures": 30,
"breezes": 5
},
{
"seed": 4685,
"score": 535500,
"peak": 10,
"flown": 4,
"comboGestures": 32,
"breezes": 5
},
{
"seed": 5622,
"score": 687600,
"peak": 10,
"flown": 4,
"comboGestures": 35,
"breezes": 8
},
{
"seed": 6559,
"score": 548700,
"peak": 10,
"flown": 5,
"comboGestures": 28,
"breezes": 9
},
{
"seed": 7496,
"score": 765100,
"peak": 10,
"flown": 4,
"comboGestures": 49,
"breezes": 8
},
{
"seed": 8433,
"score": 605900,
"peak": 10,
"flown": 3,
"comboGestures": 40,
"breezes": 5
},
{
"seed": 9370,
"score": 626500,
"peak": 10,
"flown": 5,
"comboGestures": 34,
"breezes": 2
},
{
"seed": 10307,
"score": 597300,
"peak": 10,
"flown": 3,
"comboGestures": 34,
"breezes": 7
},
{
"seed": 11244,
"score": 606400,
"peak": 10,
"flown": 4,
"comboGestures": 35,
"breezes": 11
},
{
"seed": 12181,
"score": 716600,
"peak": 10,
"flown": 5,
"comboGestures": 41,
"breezes": 5
},
{
"seed": 13118,
"score": 685200,
"peak": 10,
"flown": 4,
"comboGestures": 46,
"breezes": 9
},
{
"seed": 14055,
"score": 634000,
"peak": 10,
"flown": 4,
"comboGestures": 35,
"breezes": 3
},
{
"seed": 14992,
"score": 624700,
"peak": 10,
"flown": 4,
"comboGestures": 37,
"breezes": 7
},
{
"seed": 15929,
"score": 644100,
"peak": 10,
"flown": 5,
"comboGestures": 32,
"breezes": 6
},
{
"seed": 16866,
"score": 571300,
"peak": 10,
"flown": 4,
"comboGestures": 32,
"breezes": 4
},
{
"seed": 17803,
"score": 601300,
"peak": 10,
"flown": 5,
"comboGestures": 36,
"breezes": 9
},
{
"seed": 18740,
"score": 518400,
"peak": 10,
"flown": 4,
"comboGestures": 33,
"breezes": 8
},
{
"seed": 19677,
"score": 532500,
"peak": 10,
"flown": 3,
"comboGestures": 26,
"breezes": 4
},
{
"seed": 20614,
"score": 540900,
"peak": 10,
"flown": 4,
"comboGestures": 29,
"breezes": 4
},
{
"seed": 21551,
"score": 569800,
"peak": 10,
"flown": 3,
"comboGestures": 28,
"breezes": 10
},
{
"seed": 22488,
"score": 550700,
"peak": 10,
"flown": 4,
"comboGestures": 31,
"breezes": 11
}
]
},
{
"strategy": "guided",
"sessions": 8,
"gestures": 800,
"comboGestures": 499,
"comboRate": 0.62375,
"reactions": 1570,
"flown": 49,
"breezes": 46,
"minPaths": 2,
"commitMs": {
"median": 8.500040999999328,
"p95": 15.332957999999053,
"max": 30.179541999998037
},
"runs": [
{
"seed": 937,
"score": 968000,
"peak": 10,
"flown": 6,
"comboGestures": 62,
"breezes": 6
},
{
"seed": 1874,
"score": 837000,
"peak": 10,
"flown": 5,
"comboGestures": 54,
"breezes": 6
},
{
"seed": 2811,
"score": 899000,
"peak": 10,
"flown": 6,
"comboGestures": 56,
"breezes": 7
},
{
"seed": 3748,
"score": 1238900,
"peak": 10,
"flown": 7,
"comboGestures": 71,
"breezes": 3
},
{
"seed": 4685,
"score": 1138000,
"peak": 10,
"flown": 7,
"comboGestures": 63,
"breezes": 4
},
{
"seed": 5622,
"score": 952800,
"peak": 10,
"flown": 5,
"comboGestures": 62,
"breezes": 9
},
{
"seed": 6559,
"score": 909300,
"peak": 10,
"flown": 5,
"comboGestures": 61,
"breezes": 6
},
{
"seed": 7496,
"score": 1297900,
"peak": 10,
"flown": 8,
"comboGestures": 70,
"breezes": 5
}
]
}
]
}
@@ -0,0 +1,60 @@
{
"checks": [
"两颗松手不扣步",
"触摸取消不提交",
"回滑撤回"
],
"levels": [
{
"level": 1,
"moves": 1,
"score": 1200
},
{
"level": 2,
"moves": 1,
"score": 1200
},
{
"level": 3,
"moves": 1,
"score": 2000
},
{
"level": 4,
"moves": 2,
"score": 3500
},
{
"level": 5,
"moves": 4,
"score": 7700
},
{
"level": 6,
"moves": 4,
"score": 8800
},
{
"level": 7,
"moves": 4,
"score": 8700
},
{
"level": 8,
"moves": 6,
"score": 14200
},
{
"level": 9,
"moves": 8,
"score": 20500
},
{
"level": 10,
"moves": 10,
"score": 23400
}
],
"errors": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

+241
View File
@@ -0,0 +1,241 @@
{
"checks": [
"刷新保留十关成绩",
"撤销精确恢复盘面及补球",
"重走结果确定一致",
"打乱保留数字和步数",
"撤销恢复打乱库存",
"自由模式无步数限制",
"帮助可用 Escape 关闭"
],
"layouts": [
{
"width": 320,
"height": 568,
"overflow": false,
"bodyHeight": 650,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 69.5,
"h": 56
},
{
"id": "hint",
"w": 69.5,
"h": 56
},
{
"id": "shuffle",
"w": 69.5,
"h": 56
},
{
"id": "restart",
"w": 69.5,
"h": 56
}
]
},
{
"width": 360,
"height": 740,
"overflow": false,
"bodyHeight": 740,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 74.25,
"h": 56
},
{
"id": "hint",
"w": 74.25,
"h": 56
},
{
"id": "shuffle",
"w": 74.25,
"h": 56
},
{
"id": "restart",
"w": 74.25,
"h": 56
}
]
},
{
"width": 390,
"height": 844,
"overflow": false,
"bodyHeight": 844,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 81.75,
"h": 62
},
{
"id": "hint",
"w": 81.75,
"h": 62
},
{
"id": "shuffle",
"w": 81.75,
"h": 62
},
{
"id": "restart",
"w": 81.75,
"h": 62
}
]
},
{
"width": 430,
"height": 932,
"overflow": false,
"bodyHeight": 932,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 91.75,
"h": 62
},
{
"id": "hint",
"w": 91.75,
"h": 62
},
{
"id": "shuffle",
"w": 91.75,
"h": 62
},
{
"id": "restart",
"w": 91.75,
"h": 62
}
]
},
{
"width": 844,
"height": 390,
"overflow": false,
"bodyHeight": 690,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 88.75,
"h": 56
},
{
"id": "hint",
"w": 88.75,
"h": 56
},
{
"id": "shuffle",
"w": 88.75,
"h": 56
},
{
"id": "restart",
"w": 88.75,
"h": 56
}
]
},
{
"width": 1280,
"height": 900,
"overflow": false,
"bodyHeight": 900,
"buttons": [
{
"id": "sound",
"w": 44,
"h": 44
},
{
"id": "help",
"w": 44,
"h": 44
},
{
"id": "undo",
"w": 88.75,
"h": 62
},
{
"id": "hint",
"w": 88.75,
"h": 62
},
{
"id": "shuffle",
"w": 88.75,
"h": 62
},
{
"id": "restart",
"w": 88.75,
"h": 62
}
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,10 @@
{
"url": "http://localhost:8003",
"debugPortAbsent": true,
"touch": {
"status": "won",
"score": "1,200 分",
"moves": "2"
},
"resetToCleanLevel1": true
}
@@ -0,0 +1,883 @@
{
"generatedAt": "2026-09-15T09:43:46.893Z",
"levels": [
{
"id": 1,
"title": "初芽",
"seed": 1234,
"budget": 3,
"solutionMoves": 1,
"spareMoves": 2,
"score": 1200,
"openings": 18,
"goals": [
{
"text": "合成 1 颗「4」",
"current": 1,
"target": 1,
"complete": true
}
],
"moves": [
{
"ids": [
3,
11,
10
],
"numbers": [
3,
2,
1
],
"created": 4,
"score": 1200
}
]
},
{
"id": 2,
"title": "回转",
"seed": 2212,
"budget": 4,
"solutionMoves": 1,
"spareMoves": 3,
"score": 1200,
"openings": 14,
"goals": [
{
"text": "完成 1 次倒序合成",
"current": 1,
"target": 1,
"complete": true
}
],
"moves": [
{
"ids": [
3,
8,
1
],
"numbers": [
3,
2,
1
],
"created": 4,
"score": 1200
}
]
},
{
"id": 3,
"title": "伸展",
"seed": 3421,
"budget": 5,
"solutionMoves": 1,
"spareMoves": 4,
"score": 2000,
"openings": 28,
"goals": [
{
"text": "一次连起 4 颗",
"current": 4,
"target": 4,
"complete": true
}
],
"moves": [
{
"ids": [
1,
2,
7,
8
],
"numbers": [
1,
2,
3,
4
],
"created": 5,
"score": 2000
}
]
},
{
"id": 4,
"title": "双生",
"seed": 4517,
"budget": 6,
"solutionMoves": 2,
"spareMoves": 4,
"score": 3500,
"openings": 38,
"goals": [
{
"text": "合成 2 颗「5」",
"current": 2,
"target": 2,
"complete": true
}
],
"moves": [
{
"ids": [
4,
9,
5,
8
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 2000
},
{
"ids": [
2,
3,
7
],
"numbers": [
2,
3,
4
],
"created": 5,
"score": 3500
}
]
},
{
"id": 5,
"title": "丰收",
"seed": 5603,
"budget": 7,
"solutionMoves": 4,
"spareMoves": 3,
"score": 7700,
"openings": 4,
"goals": [
{
"text": "获得 6,500 分",
"current": 7700,
"target": 6500,
"complete": true
}
],
"moves": [
{
"ids": [
1,
2,
10
],
"numbers": [
1,
2,
3
],
"created": 4,
"score": 1200
},
{
"ids": [
11,
7,
18,
15
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 3200
},
{
"ids": [
12,
14,
19,
20,
15
],
"numbers": [
1,
2,
3,
4,
5
],
"created": 6,
"score": 6200
},
{
"ids": [
8,
13,
9
],
"numbers": [
4,
3,
2
],
"created": 5,
"score": 7700
}
]
},
{
"id": 6,
"title": "抽叶",
"seed": 6723,
"budget": 8,
"solutionMoves": 4,
"spareMoves": 4,
"score": 8800,
"openings": 40,
"goals": [
{
"text": "合成 2 颗「6」",
"current": 2,
"target": 2,
"complete": true
},
{
"text": "一次连起 4 颗",
"current": 4,
"target": 4,
"complete": true
}
],
"moves": [
{
"ids": [
1,
9,
6,
4
],
"numbers": [
1,
2,
3,
4
],
"created": 5,
"score": 2000
},
{
"ids": [
2,
3,
7,
4
],
"numbers": [
2,
3,
4,
5
],
"created": 6,
"score": 4400
},
{
"ids": [
15,
17,
22,
20
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 6400
},
{
"ids": [
18,
21,
11,
20
],
"numbers": [
2,
3,
4,
5
],
"created": 6,
"score": 8800
}
]
},
{
"id": 7,
"title": "繁枝",
"seed": 7841,
"budget": 9,
"solutionMoves": 4,
"spareMoves": 5,
"score": 8700,
"openings": 12,
"goals": [
{
"text": "合成 1 颗「7」",
"current": 1,
"target": 1,
"complete": true
}
],
"moves": [
{
"ids": [
4,
3,
2
],
"numbers": [
4,
3,
2
],
"created": 5,
"score": 1500
},
{
"ids": [
17,
9,
11,
2
],
"numbers": [
2,
3,
4,
5
],
"created": 6,
"score": 3900
},
{
"ids": [
7,
15,
19,
8
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 5900
},
{
"ids": [
12,
21,
8,
2
],
"numbers": [
3,
4,
5,
6
],
"created": 7,
"score": 8700
}
]
},
{
"id": 8,
"title": "花径",
"seed": 8917,
"budget": 10,
"solutionMoves": 6,
"spareMoves": 4,
"score": 14200,
"openings": 18,
"goals": [
{
"text": "一次连起 5 颗",
"current": 5,
"target": 5,
"complete": true
},
{
"text": "获得 14,000 分",
"current": 14200,
"target": 14000,
"complete": true
}
],
"moves": [
{
"ids": [
4,
3,
5,
12
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 2000
},
{
"ids": [
16,
17,
13,
11,
12
],
"numbers": [
1,
2,
3,
4,
5
],
"created": 6,
"score": 5000
},
{
"ids": [
15,
10,
23,
25
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 7000
},
{
"ids": [
20,
21,
7,
25
],
"numbers": [
2,
3,
4,
5
],
"created": 6,
"score": 9400
},
{
"ids": [
19,
9,
18,
22
],
"numbers": [
1,
2,
3,
4
],
"created": 5,
"score": 11400
},
{
"ids": [
12,
22,
32,
31
],
"numbers": [
6,
5,
4,
3
],
"created": 7,
"score": 14200
}
]
},
{
"id": 9,
"title": "并蒂",
"seed": 9123,
"budget": 12,
"solutionMoves": 8,
"spareMoves": 4,
"score": 20500,
"openings": 16,
"goals": [
{
"text": "合成 2 颗「7」",
"current": 2,
"target": 2,
"complete": true
}
],
"moves": [
{
"ids": [
1,
2,
3
],
"numbers": [
2,
3,
4
],
"created": 5,
"score": 1500
},
{
"ids": [
3,
10,
6,
8,
17
],
"numbers": [
5,
4,
3,
2,
1
],
"created": 6,
"score": 4500
},
{
"ids": [
4,
12,
13,
7
],
"numbers": [
1,
2,
3,
4
],
"created": 5,
"score": 6500
},
{
"ids": [
22,
15,
9,
14,
7,
17
],
"numbers": [
1,
2,
3,
4,
5,
6
],
"created": 7,
"score": 10700
},
{
"ids": [
21,
16,
20,
27
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 12700
},
{
"ids": [
11,
25,
19,
24,
27
],
"numbers": [
1,
2,
3,
4,
5
],
"created": 6,
"score": 15700
},
{
"ids": [
33,
29,
23,
28
],
"numbers": [
1,
2,
3,
4
],
"created": 5,
"score": 17700
},
{
"ids": [
30,
39,
28,
27
],
"numbers": [
3,
4,
5,
6
],
"created": 7,
"score": 20500
}
]
},
{
"id": 10,
"title": "满园",
"seed": 10429,
"budget": 14,
"solutionMoves": 10,
"spareMoves": 4,
"score": 23400,
"openings": 16,
"goals": [
{
"text": "合成 1 颗「8」",
"current": 1,
"target": 1,
"complete": true
},
{
"text": "获得 22,000 分",
"current": 23400,
"target": 22000,
"complete": true
}
],
"moves": [
{
"ids": [
1,
6,
3
],
"numbers": [
2,
3,
4
],
"created": 5,
"score": 1500
},
{
"ids": [
3,
7,
9,
5,
4
],
"numbers": [
5,
4,
3,
2,
1
],
"created": 6,
"score": 4500
},
{
"ids": [
10,
20,
19,
23
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 6500
},
{
"ids": [
8,
13,
18,
23,
4
],
"numbers": [
2,
3,
4,
5,
6
],
"created": 7,
"score": 10000
},
{
"ids": [
14,
30,
31,
29
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 12000
},
{
"ids": [
22,
16,
25,
29
],
"numbers": [
2,
3,
4,
5
],
"created": 6,
"score": 14400
},
{
"ids": [
27,
24,
33
],
"numbers": [
2,
3,
4
],
"created": 5,
"score": 15900
},
{
"ids": [
4,
29,
33,
37,
32
],
"numbers": [
7,
6,
5,
4,
3
],
"created": 8,
"score": 19900
},
{
"ids": [
28,
2,
15,
35
],
"numbers": [
4,
3,
2,
1
],
"created": 5,
"score": 21900
},
{
"ids": [
39,
38,
40
],
"numbers": [
2,
3,
4
],
"created": 5,
"score": 23400
}
]
}
],
"physics": {
"checkedStates": 1225,
"randomMerges": 1046,
"longestRun": 18,
"maxOverlap": 0.7719946571965153
}
}