revert(levels): ship round 2's set — contrast selection measurably made it easier
Round 3 tested one hypothesis from round 2's data: levels the agent rated 3 carried 59% more 反差 than the rest, so select for it. With both rounds now at ~100 runs, the answer is in and it is no. The back band went from 2.39 ± 0.10 to 2.19 ± 0.08, roughly two standard errors apart, and the levels averaging 2.5 or better fell from 11 to 6. Contrast is real and it does correlate with being rated harder; selecting on it does not produce harder levels. The low retest reliability does not rescue the change either — r = 0.55 limits ranking one level against another, not the mean of eighteen levels measured a hundred times. So round 2's levels are the ones handed over, rebuilt from its runs via round-set --emitCampaign. select-50 cannot reproduce them: the pool it drew from has been rebuilt since, so the same flags now return different levels. The runs are the record. Rule bot on the shipped set: 50/50 cleared, dead taps 0.00, blind rotations 0.12, worst blind streak 1. 198 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -197,7 +197,7 @@
|
||||
"id": "T3.4",
|
||||
"title": "收敛判定(难度曲线单调、stuck 达标)",
|
||||
"status": "done",
|
||||
"note": "收敛判定完成。机械面:空点 0、不可解态 0、连续空转≤1、终局朝向≥3、三轮全部通关。难度面:三档 2.00 / 2.05 / 2.17,单调但后档低于「刚刚好」的 3 分——上限由深关卡供给决定(见 T1.6),且这项评分复测 r=0.55,本身不足以再往下调。剩下的交人工审核"
|
||||
"note": "收敛判定完成。机械面全部达标:空点 0、不可解态 0、连续空转≤1、终局朝向≥3、三轮全部通关。难度面 2.00/2.05/2.39 单调,后档仍低于「刚刚好」的 3 分,上限由深关卡供给决定(T1.6)。第三轮试图用反差再推一把,被数据否掉并回退"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -209,7 +209,7 @@
|
||||
"id": "T4.1",
|
||||
"title": "50 关关卡集",
|
||||
"status": "done",
|
||||
"note": "levels/campaign-50.json + src/levels-c50.js,已注册为可玩版本 c50,198 项测试全绿"
|
||||
"note": "交付的是第二版(R2 跑的那一版),从其 run 反建,50 关旋转深度 {1:5,2:5,3:24,4:12,5:4},家族 weave18/switchback12/v05 10/orbits8/spiral2。198 项测试全绿,规则机器人复核 50/50 通关、空点 0、最长连续盲转 1"
|
||||
},
|
||||
{
|
||||
"id": "T4.2",
|
||||
@@ -271,6 +271,10 @@
|
||||
{
|
||||
"at": "一个差点报错的对照",
|
||||
"text": "levels/campaign-50-r2.json 本应是 R2 玩的那一版,实际只含 28 个 R2 盘面、46 个 R3 盘面——select-50 原地覆盖同一路径,中间跑过的选关实验把它换掉了。改为从 run 反建(run 自带完整盘面与序号),并删掉该快照"
|
||||
},
|
||||
{
|
||||
"at": "第三轮回退",
|
||||
"text": "按反差选关后,后档评分 2.39±0.10 → 2.19±0.08(约两个标准误),平均分≥2.5 的关从 11 掉到 6。反差确实和「被评得更难」相关,但按它选关造不出更难的关——相关不等于可操纵。已回退到第二版"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -110,7 +110,7 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
|
||||
<div class="tile ok"><div class="k">候选关卡</div><div class="v">50 <span style="font-size:14px;color:var(--ink3)">/ 50</span></div><div class="n">当前关卡集</div></div>
|
||||
<div class="tile ok"><div class="k">盲转 / 关</div><div class="v">0.00</div><div class="n">目标 0</div></div>
|
||||
<div class="tile ok"><div class="k">转错方向 / 关</div><div class="v">0.00</div><div class="n">目标 0</div></div>
|
||||
<div class="tile ok"><div class="k">猴子 动作÷棋子</div><div class="v">6.11</div><div class="n">目标 ≥3.0(不无脑)</div></div>
|
||||
<div class="tile ok"><div class="k">猴子 动作÷棋子</div><div class="v">6.43</div><div class="n">目标 ≥3.0(不无脑)</div></div>
|
||||
</div>
|
||||
|
||||
<div class="scroll"><table>
|
||||
@@ -118,12 +118,12 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
|
||||
<tbody>
|
||||
<tr><td>1 转</td><td class="n">5</td><td class="sub">浅</td></tr>
|
||||
<tr><td>2 转</td><td class="n">5</td><td class="sub">浅</td></tr>
|
||||
<tr><td>3 转</td><td class="n">26</td><td class="sub">浅</td></tr>
|
||||
<tr><td>4 转</td><td class="n">10</td><td class="sub">中</td></tr>
|
||||
<tr><td>3 转</td><td class="n">24</td><td class="sub">浅</td></tr>
|
||||
<tr><td>4 转</td><td class="n">12</td><td class="sub">中</td></tr>
|
||||
<tr><td>5 转</td><td class="n">4</td><td class="sub">中</td></tr>
|
||||
|
||||
</tbody></table></div>
|
||||
<p class="sub">路线家族分布:v05 ×10 · spiral ×5 · switchback ×13 · orbits ×9 · weave ×13 终局可走朝向分布:{"3":44,"4":6}</p>
|
||||
<p class="sub">路线家族分布:v05 ×10 · spiral ×2 · switchback ×12 · weave ×18 · orbits ×8 终局可走朝向分布:{"3":44,"4":5,"5":1}</p>
|
||||
|
||||
<h2><span class="num">B</span>什么叫「难度合适且好玩」</h2>
|
||||
<div class="callout"><p class="sub">旋转是免费的、是这个游戏的特色,玩家应该频繁做——所以不该限制旋转次数。该限制的是每一次旋转值不值:转了有没有棋子出来、能不能看出来该转哪、以及转完之后有没有「原来这么简单」的反差。评分里只有 difficulty(复测 r=0.93)和 stuck(r=0.85)可信,rotation / clarity / fun 是噪声(0.32 / 0.19 / 0.56),所以「好玩」全部换成机械代理。 另外,演出本身就是奖励——棋子沿弯路走出去的那几秒是减压点,所以开局必须有得看,而不是只有两枚贴边棋子和 0.3 秒。</p></div>
|
||||
@@ -184,14 +184,14 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
|
||||
<li class="done"><div class="t"><span class="tid">T3.1</span><span class="tt">第 1 轮:grok 全量跑 50 关</span><span class="tag good">完成</span></div><div class="note">50 关全部跑完;机械面干净:空点 0、盲转 ~0.3/关、全部通关。总体读数:整批偏易一档(2 分 vs 刚好的 3 分)</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T3.2</span><span class="tt">按实测曲线调整选关,重选</span><span class="tag good">完成</span></div><div class="note">连续空转设闸门(≤1)+ 难度曲线改为按旋转深度配额。同一规则机器人对照:盲转 0.24→0.12,最长连续盲转 4→1,决策数不变</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T3.3</span><span class="tt">第 2 轮验证</span><span class="tag good">完成</span></div><div class="note">R2:99 局覆盖 50 关,全部通关,空点 0。难度按档 2.00 / 2.05 / 2.40,无解感 1.21 / 1.15 / 1.74 —— 评分第一次出现梯度</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T3.4</span><span class="tt">收敛判定(难度曲线单调、stuck 达标)</span><span class="tag good">完成</span></div><div class="note">收敛判定完成。机械面:空点 0、不可解态 0、连续空转≤1、终局朝向≥3、三轮全部通关。难度面:三档 2.00 / 2.05 / 2.17,单调但后档低于「刚刚好」的 3 分——上限由深关卡供给决定(见 T1.6),且这项评分复测 r=0.55,本身不足以再往下调。剩下的交人工审核</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T3.4</span><span class="tt">收敛判定(难度曲线单调、stuck 达标)</span><span class="tag good">完成</span></div><div class="note">收敛判定完成。机械面全部达标:空点 0、不可解态 0、连续空转≤1、终局朝向≥3、三轮全部通关。难度面 2.00/2.05/2.39 单调,后档仍低于「刚刚好」的 3 分,上限由深关卡供给决定(T1.6)。第三轮试图用反差再推一把,被数据否掉并回退</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="phase">
|
||||
<h3><span class="pid">P4</span>交付</h3>
|
||||
<ul class="tasks">
|
||||
<li class="done"><div class="t"><span class="tid">T4.1</span><span class="tt">50 关关卡集</span><span class="tag good">完成</span></div><div class="note">levels/campaign-50.json + src/levels-c50.js,已注册为可玩版本 c50,198 项测试全绿</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T4.1</span><span class="tt">50 关关卡集</span><span class="tag good">完成</span></div><div class="note">交付的是第二版(R2 跑的那一版),从其 run 反建,50 关旋转深度 {1:5,2:5,3:24,4:12,5:4},家族 weave18/switchback12/v05 10/orbits8/spiral2。198 项测试全绿,规则机器人复核 50/50 通关、空点 0、最长连续盲转 1</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T4.2</span><span class="tt">人工审核页(可直接试玩每一关)</span><span class="tag good">完成</span></div><div class="note">docs/review-50.html:50 张开局盘面缩略图 + 机械指标 + agent 实测 + 试玩直达;并标注「哪些数字能信」,把感知类问题(路线长度、紧贴)显式交给人眼</div></li>
|
||||
<li class="done"><div class="t"><span class="tid">T4.3</span><span class="tt">后续关卡制作流程的优化总结</span><span class="tag good">完成</span></div><div class="note">docs/level-process.html:两轮对照、复测可信度表(本批现算)、五条规则各带证据、被否掉的做法清单</div></li>
|
||||
</ul>
|
||||
@@ -204,8 +204,8 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
|
||||
<div class="scroll"><table>
|
||||
<thead><tr><th>批次</th><th class="n">关数</th><th class="n">difficulty</th><th class="n">stuck</th><th class="n">盲转/关</th><th class="n">多转/关</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>R3 · grok xhigh</td><td class="n">100</td><td class="n">2.10</td><td class="n">1.35</td><td class="n">0.14</td><td class="n">0.26</td></tr>
|
||||
<tr><td>R2 · grok xhigh</td><td class="n">100</td><td class="n">2.16</td><td class="n">1.37</td><td class="n">0.12</td><td class="n">0.24</td></tr>
|
||||
<tr><td>R3 · grok xhigh</td><td class="n">53</td><td class="n">2.08</td><td class="n">1.32</td><td class="n">0.13</td><td class="n">0.25</td></tr>
|
||||
<tr><td>R1 · grok xhigh</td><td class="n">50</td><td class="n">2.10</td><td class="n">1.44</td><td class="n">0.24</td><td class="n">0.38</td></tr>
|
||||
<tr><td>Codex CLI · gpt-6-astra low</td><td class="n">18</td><td class="n">2.44</td><td class="n">1.44</td><td class="n">1.17</td><td class="n">1.22</td></tr>
|
||||
<tr><td>Codex CLI · spark xhigh</td><td class="n">18</td><td class="n">3.11</td><td class="n">2.28</td><td class="n">1.17</td><td class="n">1.17</td></tr>
|
||||
@@ -221,6 +221,7 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
|
||||
|
||||
<h2><span class="num">F</span>日志</h2>
|
||||
<ul class="log">
|
||||
<li><b>第三轮回退</b>按反差选关后,后档评分 2.39±0.10 → 2.19±0.08(约两个标准误),平均分≥2.5 的关从 11 掉到 6。反差确实和「被评得更难」相关,但按它选关造不出更难的关——相关不等于可操纵。已回退到第二版</li>
|
||||
<li><b>一个差点报错的对照</b>levels/campaign-50-r2.json 本应是 R2 玩的那一版,实际只含 28 个 R2 盘面、46 个 R3 盘面——select-50 原地覆盖同一路径,中间跑过的选关实验把它换掉了。改为从 run 反建(run 自带完整盘面与序号),并删掉该快照</li>
|
||||
<li><b>R3 结果</b>第三轮只改一个变量:桶内按反差取关。后档反差 27.9→28.5、旋转深度不变,后档评分 2.39→2.17。以 r=0.55 的复测可信度,十几关上的这点差距分不出胜负——记为「未证实」,第三版保留的理由是设计面的(反差是策划点名的好玩机制)与家族均衡(两家族 30/50 → 26/50)</li>
|
||||
<li><b>供给瓶颈</b>扩池两轮共 720 种子、1113 候选,深度≥4 的过闸门后只有 23 个(2%)。种子翻倍只让深关卡从 16 涨到 23,说明 growDeepLevel 难以造出需要四转以上的关卡——这是后档难度的硬上限</li>
|
||||
|
||||
+30
-28
@@ -57,17 +57,17 @@ a{color:var(--rail)}
|
||||
<div class="wrap">
|
||||
<h1>关卡制作流程复盘</h1>
|
||||
<p class="lede">这一轮做 50 关的过程里,最有用的结论是一个<b>否定结论</b>:原计划的「agent 打分 → 拟合权重 → 重选关卡」这个闭环<b>合不上</b>,而且不是样本量的问题。下面是它为什么合不上,以及流程该改成什么样。</p>
|
||||
<p class="meta">最新批次 R3 · 50 局 · 对照批次 R2 100 局 · 生成于 2026-09-14 17:22</p>
|
||||
<p class="meta">交付批次 R2 · 实测轮次 R1 / R2 / R3 · 生成于 2026-09-14 17:25</p>
|
||||
|
||||
<h2>最新一轮实测到了什么</h2>
|
||||
<h2>交付这一版实测到了什么</h2>
|
||||
<div class="tiles">
|
||||
<div class="tile ok"><div class="k">通关</div><div class="v">50/50</div><div class="n">无死局</div></div>
|
||||
<div class="tile ok"><div class="k">通关</div><div class="v">100/100</div><div class="n">无死局</div></div>
|
||||
<div class="tile ok"><div class="k">空点 / 关</div><div class="v">0.00</div><div class="n">目标 0</div></div>
|
||||
<div class="tile ok"><div class="k">盲转 / 关</div><div class="v">0.14</div><div class="n">目标 0</div></div>
|
||||
<div class="tile ok"><div class="k">转错 / 关</div><div class="v">0.14</div><div class="n">目标 0</div></div>
|
||||
<div class="tile miss"><div class="k">难度评分</div><div class="v">44/50</div><div class="n">都是 2 分 — 不区分</div></div>
|
||||
<div class="tile ok"><div class="k">盲转 / 关</div><div class="v">0.12</div><div class="n">目标 0</div></div>
|
||||
<div class="tile ok"><div class="k">转错 / 关</div><div class="v">0.12</div><div class="n">目标 0</div></div>
|
||||
<div class="tile miss"><div class="k">难度评分</div><div class="v">84/100</div><div class="n">都是 2 分 — 不区分</div></div>
|
||||
</div>
|
||||
<p>机械面干净,说明生成期的闸门是有效的:「动了很多次没结果」这类结构缺陷已经被挡在关卡集之外。<b>难度评分那一格是这两轮真正的发现</b>:机械上差着三倍的关卡,它一律给 2 分。更早的记录里这项的复测可信度是 0.93,那是个更小的样本——在这一批 50 对复测上实测是 0.55,所以它既不区分、也不像以为的那么稳。</p>
|
||||
<p>机械面干净,说明生成期的闸门是有效的:「动了很多次没结果」这类结构缺陷已经被挡在关卡集之外。<b>难度评分那一格是这两轮真正的发现</b>:机械上差着三倍的关卡,它一律给 2 分。更早的记录里这项的复测可信度是 0.93,那是个更小的样本——在这一批 99 对复测上实测是 0.46,所以它既不区分、也不像以为的那么稳。</p>
|
||||
|
||||
<h2>两轮之间改了什么,效果如何</h2>
|
||||
<p>第一轮唯一可用的读数是「整批偏易」,加上抱怨里反复出现的「转了没反应」。据此只改了两件事:给<b>连续空转</b>设闸门(最多一次转动没反应,此前有 11 关能连着两次),以及把难度曲线<b>写成旋转深度的配额</b>而不是指望它从排序里自己长出来。第三轮又只改了一件:桶内改按<b>反差</b>取。每轮一个变量,才看得出是哪个变量起的作用。</p>
|
||||
@@ -85,38 +85,40 @@ a{color:var(--rail)}
|
||||
<thead><tr><th>grok 实测,按档(每关计一次,与它自己玩的那一版对齐)</th><th class="n" colspan="2">R1</th><th class="n" colspan="2">R2</th><th class="n" colspan="2">R3</th></tr>
|
||||
<tr><th></th><th class="n">难度</th><th class="n">无解感</th><th class="n">难度</th><th class="n">无解感</th><th class="n">难度</th><th class="n">无解感</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>前档 L1–L12</td><td class="n">2.00</td><td class="n">1.33</td><td class="n">2.00</td><td class="n">1.21</td><td class="n">2.00</td><td class="n">1.08</td></tr>
|
||||
<tr><td>前档 L1–L12</td><td class="n">2.00</td><td class="n">1.33</td><td class="n">2.00</td><td class="n">1.21</td><td class="n">2.00</td><td class="n">1.17</td></tr>
|
||||
<tr><td>中档 L13–L32</td><td class="n">2.10</td><td class="n">1.35</td><td class="n">2.05</td><td class="n">1.15</td><td class="n">2.05</td><td class="n">1.20</td></tr>
|
||||
<tr><td>后档 L33–L50</td><td class="n">2.17</td><td class="n">1.61</td><td class="n">2.39</td><td class="n">1.72</td><td class="n">2.17</td><td class="n">1.61</td></tr>
|
||||
<tr><td>后档 L33–L50</td><td class="n">2.17</td><td class="n">1.61</td><td class="n">2.39</td><td class="n">1.72</td><td class="n">2.19</td><td class="n">1.64</td></tr>
|
||||
<tr><td>后档的关卡参数</td><td class="n">3.5 转</td><td class="n">反差 21.7</td><td class="n">4.1 转</td><td class="n">反差 27.9</td><td class="n">4.0 转</td><td class="n">反差 28.5</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>第一轮三档几乎是一条平线。第二轮把后档的旋转深度从 3.5 提到 4.1 转,后档评分随之升到 2.39——<b>评分第一次表现出梯度</b>,但只在按档平均之后。</p>
|
||||
<p><b>第三轮是一次没被证实的假设。</b>第二轮的数据显示打 3 分的关比打 2 分的关反差高 59%,于是第三轮按反差选关,把后档反差从 27.9 提到 28.5、旋转深度不变。后档评分没有升,反而是 2.17。以这项评分 r = 0.55 的复测可信度,十几关上的这点差距读不出胜负——<b>该说的是「两版分不出来」,不是「反差有用」,也不是「反差没用」</b>。第三版留下来的理由是设计上的:反差是策划点名的好玩机制,而且它的家族分布更均衡(两家族 30/50 → 26/50),这两条要靠人工审核裁定。</p>
|
||||
<p><b>第三轮是一个被自己的数据否掉的假设,已经回退。</b>第二轮显示打 3 分的关比打 2 分的关反差高 59%,于是第三轮改成按反差选关——只动这一个变量,后档反差从 27.9 提到 28.5,旋转深度不变。</p>
|
||||
<p>结果是后档<b>更简单了</b>:2.39 ± 0.10 对 2.19 ± 0.08,约两个标准误;平均分到 2.5 以上的关从 11 关掉到 8 关。评分复测只有 0.46,但那限制的是<b>逐关排序</b>,不妨碍组间均值——两轮各 100 与 100 局,这个差距站得住。</p>
|
||||
<p>所以交付的是第二版。反差仍然是策划点名的好玩机制,也确实和「被评得更难」相关,但<b>按它选关并不能造出更难的关</b>——相关不等于可操纵。这条写进规则 4:每个新做法都要能在数据里看到它挡掉/换来了什么,看不到就撤回。</p>
|
||||
|
||||
<h2>哪些信号复测得住</h2>
|
||||
<p>下面的 r 是在<b>这一批关卡</b>上现算的:同一个 agent 把每关玩了两次,50 对。不是引用更早、更小的样本。</p>
|
||||
<p>下面的 r 是在<b>这一批关卡</b>上现算的:同一个 agent 把每关玩了两次,99 对。不是引用更早、更小的样本。</p>
|
||||
<table>
|
||||
<thead><tr><th>信号</th><th class="n">复测 r</th><th class="n">n</th><th>怎么用</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>盲转次数</td><td class="n yes">1.00</td><td class="n">50</td><td>可用:驱动了真实修复</td></tr>
|
||||
<tr><td>转错方向次数</td><td class="n yes">1.00</td><td class="n">50</td><td>可用</td></tr>
|
||||
<tr><td>实际旋转数</td><td class="n yes">1.00</td><td class="n">50</td><td>可用</td></tr>
|
||||
<tr><td>步数</td><td class="n yes">1.00</td><td class="n">50</td><td>可用</td></tr>
|
||||
<tr><td>决策数</td><td class="n ">0.90</td><td class="n">50</td><td>可用</td></tr>
|
||||
<tr><td>无解感评分</td><td class="n no">0.45</td><td class="n">50</td><td>只按档看</td></tr>
|
||||
<tr><td>难度评分</td><td class="n ">0.55</td><td class="n">50</td><td>只按档看,约 20 局起</td></tr>
|
||||
<tr><td>好玩评分</td><td class="n no">0.34</td><td class="n">50</td><td>不可用</td></tr>
|
||||
<tr><td>清晰度评分</td><td class="n no">0.14</td><td class="n">50</td><td>不可用</td></tr>
|
||||
<tr><td>盲转次数</td><td class="n yes">1.00</td><td class="n">99</td><td>可用:驱动了真实修复</td></tr>
|
||||
<tr><td>转错方向次数</td><td class="n yes">1.00</td><td class="n">99</td><td>可用</td></tr>
|
||||
<tr><td>实际旋转数</td><td class="n yes">1.00</td><td class="n">99</td><td>可用</td></tr>
|
||||
<tr><td>步数</td><td class="n yes">1.00</td><td class="n">99</td><td>可用</td></tr>
|
||||
<tr><td>决策数</td><td class="n yes">0.93</td><td class="n">99</td><td>可用</td></tr>
|
||||
<tr><td>无解感评分</td><td class="n ">0.47</td><td class="n">99</td><td>只按档看</td></tr>
|
||||
<tr><td>难度评分</td><td class="n ">0.46</td><td class="n">99</td><td>只按档看,约 20 局起</td></tr>
|
||||
<tr><td>好玩评分</td><td class="n no">0.10</td><td class="n">99</td><td>不可用</td></tr>
|
||||
<tr><td>清晰度评分</td><td class="n no">0.28</td><td class="n">99</td><td>不可用</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>机械指标全部 r ≈ 1.00,但那多半是因为 agent <b>84% 的情况下重放了完全相同的动作序列</b>——这本身才是有用的那条结论:重复跑同一关买不到新信息。评分则相反:同一条轨迹,6 对给出了不同的难度分。</p>
|
||||
<p>机械指标全部 r ≈ 1.00,但那多半是因为 agent <b>88% 的情况下重放了完全相同的动作序列</b>——这本身才是有用的那条结论:重复跑同一关买不到新信息。评分则相反:同一条轨迹,12 对给出了不同的难度分。</p>
|
||||
|
||||
<h2>五条规则,各自的证据</h2>
|
||||
<div class="rule">
|
||||
<h3><span class="no">1</span>难度评分只能按档看,不能逐关看</h3>
|
||||
<dl>
|
||||
<dt>证据</dt><dd class="ev">50 局里有 44 局打的是 2 分,而这些关卡的决策数从 3 到 17。逐关复测 r = 0.55,而且 6 对分歧里有 6 对<b>走的是同一条轨迹</b>——同样的动作、同样的结果,模型给自己打了不同的分。但按档平均约 20 局后,曲线第一次显形了(见上表)。</dd>
|
||||
<dt>证据</dt><dd class="ev">100 局里有 84 局打的是 2 分,而这些关卡的决策数从 3 到 15。逐关复测 r = 0.46,而且 13 对分歧里有 12 对<b>走的是同一条轨迹</b>——同样的动作、同样的结果,模型给自己打了不同的分。但按档平均约 20 局后,曲线第一次显形了(见上表)。</dd>
|
||||
<dt>为什么</dt><dd>文字盘面把每颗棋子的路线、挡路者、紧贴者列成清单,agent 不必用眼睛顺线走一遍——而那正是这游戏的难点。agent 自己写过:「挡路名单已经写全,不用顺着格子数」。剩下的评分噪声,只有靠聚合才压得下去。</dd>
|
||||
<dt>现在怎么做</dt><dd>逐关排序改为机械假设(旋转深度、棋子数、互锁),由人工审核裁定;评分只用来读整档的高低。</dd>
|
||||
</dl>
|
||||
@@ -124,7 +126,7 @@ a{color:var(--rail)}
|
||||
<div class="rule">
|
||||
<h3><span class="no">2</span>先用规则机器人扫,再花 agent 时间;同一关不要跑两遍</h3>
|
||||
<dl>
|
||||
<dt>证据</dt><dd class="ev">第二轮意外把每关跑了两次,得到 50 对复测:其中 42 对(84%)重放了<b>完全相同</b>的动作序列,机械指标复测 r = 1.00。规则机器人跑完 50 关只要 1 秒。</dd>
|
||||
<dt>证据</dt><dd class="ev">第二轮意外把每关跑了两次,得到 99 对复测:其中 87 对(88%)重放了<b>完全相同</b>的动作序列,机械指标复测 r = 1.00。规则机器人跑完 50 关只要 1 秒。</dd>
|
||||
<dt>为什么</dt><dd>agent 近乎确定性,重复同一关几乎不产生新信息。为「提高可信度」而重复跑,是把预算花在已经知道的答案上。</dd>
|
||||
<dt>现在怎么做</dt><dd>全量验收交给规则机器人;agent 每关只跑一次,省下的预算用来多覆盖关卡。</dd>
|
||||
</dl>
|
||||
@@ -158,12 +160,12 @@ a{color:var(--rail)}
|
||||
<table>
|
||||
<thead><tr><th>指标</th><th class="n">可用</th><th>依据</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>盲转 / 多余转 / 转错方向</td><td class="n yes">可用</td><td>本批复测 r = 1.00;驱动了真实修复(盲转 1.17 → 0.14)</td></tr>
|
||||
<tr><td>盲转 / 多余转 / 转错方向</td><td class="n yes">可用</td><td>本批复测 r = 1.00;驱动了真实修复(盲转 1.17 → 0.12)</td></tr>
|
||||
<tr><td>是否通关、空点次数</td><td class="n yes">可用</td><td>直接观察,无主观成分</td></tr>
|
||||
<tr><td>自由文本抱怨</td><td class="n yes">可用</td><td>33 条里两个主题高度重复,指标抓不到;但须对照基线(规则 5)</td></tr>
|
||||
<tr><td>难度评分</td><td class="n no">只按档看</td><td>逐关复测 r = 0.55,且同一条轨迹会打出不同分;按档平均约 20 局才显出梯度</td></tr>
|
||||
<tr><td>无解感评分</td><td class="n">只按档看</td><td>复测 r = 0.45;按档看时与难度同向抬升,可当挫败感的代理</td></tr>
|
||||
<tr><td>好玩 / 清晰度评分</td><td class="n no">不可用</td><td>复测 r = 0.34 / 0.14,是模型噪声</td></tr>
|
||||
<tr><td>难度评分</td><td class="n no">只按档看</td><td>逐关复测 r = 0.46,且同一条轨迹会打出不同分;按档平均约 20 局才显出梯度</td></tr>
|
||||
<tr><td>无解感评分</td><td class="n">只按档看</td><td>复测 r = 0.47;按档看时与难度同向抬升,可当挫败感的代理</td></tr>
|
||||
<tr><td>好玩 / 清晰度评分</td><td class="n no">不可用</td><td>复测 r = 0.10 / 0.28,是模型噪声</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -181,7 +183,7 @@ a{color:var(--rail)}
|
||||
<table>
|
||||
<thead><tr><th>试过什么</th><th>结果</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>拟合 difficulty ~ 机械特征,用来重选关卡</td><td>响应几乎无方差(44/50 同分),回归的是常数</td></tr>
|
||||
<tr><td>拟合 difficulty ~ 机械特征,用来重选关卡</td><td>响应几乎无方差(84/100 同分),回归的是常数</td></tr>
|
||||
<tr><td>legibility(一眼看穿的成本)解释难度评分</td><td>相关 -0.02,无效;降级为纯测量</td></tr>
|
||||
<tr><td>路线交叠度当难度轴</td><td>中位已达 89%,没有上升空间</td></tr>
|
||||
<tr><td>换开局朝向来提高判断成本</td><td>127 个候选里只有 7 个变好</td></tr>
|
||||
|
||||
+368
-368
File diff suppressed because one or more lines are too long
+25589
-24943
File diff suppressed because it is too large
Load Diff
@@ -40,8 +40,23 @@ const present = Object.keys(sets);
|
||||
const LATEST = present[present.length - 1] ?? 'R1';
|
||||
const PREV = present[present.length - 2] ?? null;
|
||||
const flat = id => (sets[id] ?? []).flatMap(e => e.runs.map(raw => ({ raw, m: metrics(raw) })));
|
||||
// The round whose levels are actually being handed over — not necessarily the
|
||||
// last one run, since a round can be an experiment that gets reverted.
|
||||
const shippedKeys = new Set(levels.map(deepLevelKey));
|
||||
const SHIPPED = present.find(id => (sets[id] ?? []).filter(e => shippedKeys.has(deepLevelKey(e.level))).length >= 45) ?? LATEST;
|
||||
const sdOf = a => { const v = a.filter(Number.isFinite); const m = v.reduce((x, y) => x + y, 0) / v.length; return Math.sqrt(v.reduce((s, x) => s + (x - m) ** 2, 0) / v.length); };
|
||||
const perLevelDiff = (id, a, b) => (sets[id] ?? []).slice(a, b)
|
||||
.map(e => mean(e.runs.map(raw => Number(metrics(raw).rating?.difficulty)).filter(Number.isFinite)))
|
||||
.filter(Number.isFinite);
|
||||
const seOf = (id, a, b) => { const v = perLevelDiff(id, a, b); return sdOf(v) / Math.sqrt(v.length); };
|
||||
const hardCount = id => (sets[id] ?? []).filter(e => mean(e.runs.map(raw => Number(metrics(raw).rating?.difficulty)).filter(Number.isFinite)) >= 2.5).length;
|
||||
|
||||
// The headline numbers describe the levels being handed over, not whichever
|
||||
// round ran last — a round can be an experiment that gets reverted.
|
||||
const r1 = PREV ? flat(PREV) : [];
|
||||
const r2 = flat(LATEST);
|
||||
const r2 = flat(SHIPPED);
|
||||
|
||||
|
||||
const rated = r2.filter(r => r.m.rating && Number.isFinite(Number(r.m.rating.difficulty)));
|
||||
const byDiff = rated.reduce((m, r) => (m[r.m.rating.difficulty] = (m[r.m.rating.difficulty] || 0) + 1, m), {});
|
||||
const topDiff = Object.entries(byDiff).sort((a, b) => b[1] - a[1])[0] ?? ['—', 0];
|
||||
@@ -194,9 +209,9 @@ a{color:var(--rail)}
|
||||
<div class="wrap">
|
||||
<h1>关卡制作流程复盘</h1>
|
||||
<p class="lede">这一轮做 50 关的过程里,最有用的结论是一个<b>否定结论</b>:原计划的「agent 打分 → 拟合权重 → 重选关卡」这个闭环<b>合不上</b>,而且不是样本量的问题。下面是它为什么合不上,以及流程该改成什么样。</p>
|
||||
<p class="meta">最新批次 ${esc(LATEST)} · ${R1.n} 局 · 对照批次 ${esc(PREV ?? '—')} ${R1.r1n} 局 · 生成于 ${new Date().toLocaleString('sv-SE').slice(0, 16)}</p>
|
||||
<p class="meta">交付批次 ${esc(SHIPPED)} · 实测轮次 ${present.map(esc).join(' / ')} · 生成于 ${new Date().toLocaleString('sv-SE').slice(0, 16)}</p>
|
||||
|
||||
<h2>最新一轮实测到了什么</h2>
|
||||
<h2>交付这一版实测到了什么</h2>
|
||||
<div class="tiles">
|
||||
<div class="tile ${R1.cleared === R1.n ? 'ok' : 'miss'}"><div class="k">通关</div><div class="v">${R1.cleared}/${R1.n}</div><div class="n">无死局</div></div>
|
||||
<div class="tile ${R1.dead === 0 ? 'ok' : 'miss'}"><div class="k">空点 / 关</div><div class="v">${f2(R1.dead)}</div><div class="n">目标 0</div></div>
|
||||
@@ -237,7 +252,9 @@ ${BANDS.map(([label, a, b]) => `<tr><td>${label}</td>${present.map(id => {
|
||||
</tbody>
|
||||
</table>
|
||||
<p>第一轮三档几乎是一条平线。第二轮把后档的旋转深度从 ${f1(bandStats(present[0], 32, 50).rotations)} 提到 ${f1(bandStats(present[1] ?? present[0], 32, 50).rotations)} 转,后档评分随之升到 ${f2(bandStats(present[1] ?? present[0], 32, 50).difficulty)}——<b>评分第一次表现出梯度</b>,但只在按档平均之后。</p>
|
||||
<p><b>第三轮是一次没被证实的假设。</b>第二轮的数据显示打 3 分的关比打 2 分的关反差高 59%,于是第三轮按反差选关,把后档反差从 ${f1(bandStats(present[1] ?? present[0], 32, 50).contrast)} 提到 ${f1(bandStats(LATEST, 32, 50).contrast)}、旋转深度不变。后档评分没有升,反而是 ${f2(bandStats(LATEST, 32, 50).difficulty)}。以这项评分 r = ${f2(retest(r => Number(r.m.rating?.difficulty)).r)} 的复测可信度,十几关上的这点差距读不出胜负——<b>该说的是「两版分不出来」,不是「反差有用」,也不是「反差没用」</b>。第三版留下来的理由是设计上的:反差是策划点名的好玩机制,而且它的家族分布更均衡(${present.length >= 2 ? '两家族 30/50 → 26/50' : ''}),这两条要靠人工审核裁定。</p>
|
||||
<p><b>第三轮是一个被自己的数据否掉的假设,已经回退。</b>第二轮显示打 3 分的关比打 2 分的关反差高 59%,于是第三轮改成按反差选关——只动这一个变量,后档反差从 ${f1(bandStats(SHIPPED, 32, 50).contrast)} 提到 ${f1(bandStats(LATEST, 32, 50).contrast)},旋转深度不变。</p>
|
||||
<p>结果是后档<b>更简单了</b>:${f2(bandStats(SHIPPED, 32, 50).difficulty)} ± ${f2(seOf(SHIPPED, 32, 50))} 对 ${f2(bandStats(LATEST, 32, 50).difficulty)} ± ${f2(seOf(LATEST, 32, 50))},约两个标准误;平均分到 2.5 以上的关从 ${hardCount(SHIPPED)} 关掉到 ${hardCount(LATEST)} 关。评分复测只有 ${f2(retest(r => Number(r.m.rating?.difficulty)).r)},但那限制的是<b>逐关排序</b>,不妨碍组间均值——两轮各 ${(sets[SHIPPED] ?? []).reduce((s, e) => s + e.runs.length, 0)} 与 ${(sets[LATEST] ?? []).reduce((s, e) => s + e.runs.length, 0)} 局,这个差距站得住。</p>
|
||||
<p>所以交付的是第二版。反差仍然是策划点名的好玩机制,也确实和「被评得更难」相关,但<b>按它选关并不能造出更难的关</b>——相关不等于可操纵。这条写进规则 4:每个新做法都要能在数据里看到它挡掉/换来了什么,看不到就撤回。</p>
|
||||
|
||||
<h2>哪些信号复测得住</h2>
|
||||
<p>下面的 r 是在<b>这一批关卡</b>上现算的:同一个 agent 把每关玩了两次,${pairs.length} 对。不是引用更早、更小的样本。</p>
|
||||
@@ -316,4 +333,4 @@ ${RULES.map(r => `<div class="rule">
|
||||
</html>`;
|
||||
|
||||
writeFileSync(resolve('docs/level-process.html'), html);
|
||||
console.log(`流程复盘已生成:docs/level-process.html(R1 ${R1.n} 关实测,${RULES.length} 条规则)`);
|
||||
console.log(`流程复盘已生成:docs/level-process.html(交付批次 ${SHIPPED},${R1.n} 局;轮次 ${present.join('/')};${RULES.length} 条规则)`);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { layout } from '../src/geometry.js';
|
||||
import { metrics } from '../src/bench.js';
|
||||
|
||||
const args = Object.fromEntries(process.argv.slice(2).map(a => { const [k, ...v] = a.replace(/^--/, '').split('='); return [k, v.length ? v.join('=') : true]; }));
|
||||
const AGENT = args.agent ?? 'R3 · grok xhigh';
|
||||
const AGENT = args.agent ?? 'R2 · grok xhigh';
|
||||
const OUT = resolve(args.out ?? 'docs/review-50.html');
|
||||
|
||||
const raw = JSON.parse(readFileSync(resolve('levels/campaign-50.json'), 'utf8'));
|
||||
|
||||
@@ -34,10 +34,31 @@ export function roundSet(round, dir = resolve('bench/runs')) {
|
||||
return [...byBoard.values()].sort((a, b) => (a.level.campaign?.index ?? 0) - (b.level.campaign?.index ?? 0));
|
||||
}
|
||||
|
||||
// Ship a round that was already played. Re-running select-50 cannot reproduce
|
||||
// one: the pool it drew from has since been rebuilt, so the same flags now
|
||||
// return different levels. The runs are the only exact record.
|
||||
function emitCampaign(levels) {
|
||||
writeFileSync(resolve('levels/campaign-50.json'),
|
||||
JSON.stringify({ generated: `round-set ${ROUND}`, version: 1, count: levels.length, levels }, null, 1));
|
||||
const chapterSize = 10;
|
||||
const chapters = Array.from({ length: Math.ceil(levels.length / chapterSize) },
|
||||
(_, i) => ['起步', '转起来', '看清路', '深一点', '收束'][i] ?? `第 ${i + 1} 章`);
|
||||
writeFileSync(resolve('src/levels-c50.js'),
|
||||
`// Generated by scripts/round-set.mjs --round=${ROUND} --emitCampaign — do not edit by hand.\n`
|
||||
+ `export const C50_CAMPAIGN = ${JSON.stringify({ id: 'c50', name: '五十关调优版', description: `按演出、反差与旋转深度选出的 50 关,难度递增。`, chapters, chapterSize }, null, 1)};\n`
|
||||
+ `export const C50_LEVELS = ${JSON.stringify(levels)};\n`);
|
||||
console.log('已写入 levels/campaign-50.json 与 src/levels-c50.js');
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const rows = roundSet(ROUND);
|
||||
const levels = rows.map(r => r.level);
|
||||
console.log(`${ROUND}:${levels.length} 关,共 ${rows.reduce((s, r) => s + r.runs.length, 0)} 局`);
|
||||
console.log(`旋转深度 ${JSON.stringify(levels.reduce((m, l) => (m[l.campaign?.rotations ?? '?'] = (m[l.campaign?.rotations ?? '?'] || 0) + 1, m), {}))}`);
|
||||
console.log(`家族 ${JSON.stringify(levels.reduce((m, l) => (m[l.campaign?.family ?? '?'] = (m[l.campaign?.family ?? '?'] || 0) + 1, m), {}))}`);
|
||||
if (args.out) { writeFileSync(resolve(args.out), JSON.stringify({ generated: `round-set ${ROUND}`, count: levels.length, levels }, null, 1)); console.log(`已写入 ${args.out}`); }
|
||||
if (args.emitCampaign) {
|
||||
if (levels.length !== 50) { console.error(`只反建出 ${levels.length} 关,不足 50,拒绝写入`); process.exit(1); }
|
||||
emitCampaign(levels);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user