Files
HexTrails/docs/level-process.html
T
cabinwandClaude Opus 5 4aa0c28cef fix(bench): rebuild each round's level set from its runs, not from a snapshot
The round comparison was about to be wrong. levels/campaign-50-r2.json was
copied from levels/campaign-50.json to preserve "the set round 2 played", but
select-50 writes that path in place and several selection experiments ran
between the round and the copy. The file kept 28 of the 50 boards round 2 had
actually played and 46 of round 3's — so the comparison was scoring one round
against another round's levels, and it read as round 3 regressing.

Runs store the whole board and its campaign index, so a round is its own record
and cannot drift. round-set.mjs rebuilds it, the report uses that, and the
snapshot file is gone rather than left lying around looking authoritative.

With the rounds correctly matched, round 3 is not a regression and not an
improvement: back band 2.17 against round 2's 2.39, on a rating that retests at
0.55, which is not enough to separate seventeen levels. The report says that
rather than picking whichever reading flatters the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:22:31 +08:00

201 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>关卡制作流程复盘</title>
<style>
:root{
--ground:#EFF1F3; --surface:#FFF; --panel:#F6F8F9; --ink:#131C24; --ink2:#465766; --ink3:#6D7C89;
--rule:#D4DAE0; --rail:#3F5E79; --good:#226D63; --bad:#AF3A26; --warn:#895B0E;
color-scheme:light;
}
@media (prefers-color-scheme:dark){:root:not([data-theme=light]){
--ground:#0E151B; --surface:#131C24; --panel:#131C24; --ink:#E3E9EE; --ink2:#ADBCC8; --ink3:#8393A1;
--rule:#27333E; --rail:#8BAFCB; --good:#4EC6B6; --bad:#E3826B; --warn:#DFAD5D;
color-scheme:dark;
}}
*{box-sizing:border-box}
body{margin:0;background:var(--ground);color:var(--ink);
font-family:"PingFang SC","Hiragino Sans GB",ui-sans-serif,system-ui,sans-serif;line-height:1.7}
.wrap{max-width:920px;margin:0 auto;padding:44px 22px 96px}
h1{font-size:31px;margin:0 0 12px;letter-spacing:-.02em}
.lede{color:var(--ink2);font-size:15.5px;max-width:68ch;margin:0 0 6px}
.meta{font-size:12.5px;color:var(--ink3);font-family:ui-monospace,monospace;margin:0 0 34px}
h2{font-size:20px;margin:44px 0 12px;padding-bottom:7px;border-bottom:1px solid var(--rule)}
p{max-width:70ch}
.tiles{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));margin:18px 0 8px}
.tile{background:var(--surface);border:1px solid var(--rule);border-radius:10px;padding:13px 15px}
.tile .k{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink3);font-weight:600}
.tile .v{font-size:25px;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;margin:3px 0 1px}
.tile .n{font-size:11.5px;color:var(--ink3)}
.tile.ok .v{color:var(--good)} .tile.miss .v{color:var(--bad)}
.rule{background:var(--surface);border:1px solid var(--rule);border-radius:12px;padding:17px 20px;margin:14px 0}
.rule h3{margin:0 0 9px;font-size:16.5px;display:flex;gap:10px;align-items:baseline}
.rule h3 .no{font-family:ui-monospace,monospace;color:var(--rail);font-weight:700}
.rule dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:5px 13px;font-size:13.5px}
.rule dt{color:var(--ink3);white-space:nowrap;font-size:11.5px;letter-spacing:.04em;padding-top:3px}
.rule dd{margin:0;color:var(--ink2)}
.rule dd.ev{color:var(--ink)}
table{width:100%;border-collapse:collapse;font-size:13.5px;margin:14px 0 8px}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--rule);vertical-align:top}
th{font-size:11px;letter-spacing:.06em;color:var(--ink3);text-transform:uppercase;font-weight:600}
td.n{text-align:right;font-family:ui-monospace,monospace;white-space:nowrap}
.no{color:var(--bad);font-weight:600}.yes{color:var(--good);font-weight:600}
ol.recipe{counter-reset:s;list-style:none;padding:0;margin:14px 0}
ol.recipe li{counter-increment:s;position:relative;padding:0 0 13px 40px;font-size:14px;color:var(--ink2);max-width:70ch}
ol.recipe li::before{content:counter(s);position:absolute;left:0;top:0;width:25px;height:25px;border-radius:50%;
background:var(--rail);color:#fff;font-family:ui-monospace,monospace;font-size:12.5px;font-weight:700;
display:grid;place-items:center}
ol.recipe b{color:var(--ink)}
code{font-family:ui-monospace,monospace;font-size:.92em;background:var(--panel);padding:1px 5px;border-radius:4px;border:1px solid var(--rule)}
footer{margin-top:54px;padding-top:18px;border-top:1px solid var(--rule);color:var(--ink3);font-size:12px}
a{color:var(--rail)}
</style>
</head>
<body>
<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>
<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">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>
<p>机械面干净,说明生成期的闸门是有效的:「动了很多次没结果」这类结构缺陷已经被挡在关卡集之外。<b>难度评分那一格是这两轮真正的发现</b>:机械上差着三倍的关卡,它一律给 2 分。更早的记录里这项的复测可信度是 0.93,那是个更小的样本——在这一批 50 对复测上实测是 0.55,所以它既不区分、也不像以为的那么稳。</p>
<h2>两轮之间改了什么,效果如何</h2>
<p>第一轮唯一可用的读数是「整批偏易」,加上抱怨里反复出现的「转了没反应」。据此只改了两件事:给<b>连续空转</b>设闸门(最多一次转动没反应,此前有 11 关能连着两次),以及把难度曲线<b>写成旋转深度的配额</b>而不是指望它从排序里自己长出来。第三轮又只改了一件:桶内改按<b>反差</b>取。每轮一个变量,才看得出是哪个变量起的作用。</p>
<table>
<thead><tr><th>同一个规则机器人,两套关卡集</th><th class="n">第一版</th><th class="n">第二版</th></tr></thead>
<tbody>
<tr><td>盲转 / 关</td><td class="n">0.24</td><td class="n yes">0.12</td></tr>
<tr><td>最长连续盲转</td><td class="n">4</td><td class="n yes">1</td></tr>
<tr><td>空点 / 关</td><td class="n">0.00</td><td class="n">0.00</td></tr>
<tr><td>决策 / 关(要做的事)</td><td class="n">10.3</td><td class="n">10.3</td></tr>
</tbody>
</table>
<p>要做的事一样多,浪费掉的动作少了一半。这是同一个玩家的对照,所以变的是关卡不是模型的心情。</p>
<table>
<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>中档 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>后档的关卡参数</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>
<h2>哪些信号复测得住</h2>
<p>下面的 r 是在<b>这一批关卡</b>上现算的:同一个 agent 把每关玩了两次,50 对。不是引用更早、更小的样本。</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>
</tbody>
</table>
<p>机械指标全部 r ≈ 1.00,但那多半是因为 agent <b>84% 的情况下重放了完全相同的动作序列</b>——这本身才是有用的那条结论:重复跑同一关买不到新信息。评分则相反:同一条轨迹,6 对给出了不同的难度分。</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 对&lt;b&gt;走的是同一条轨迹&lt;/b&gt;——同样的动作、同样的结果,模型给自己打了不同的分。但按档平均约 20 局后,曲线第一次显形了(见上表)。</dd>
<dt>为什么</dt><dd>文字盘面把每颗棋子的路线、挡路者、紧贴者列成清单,agent 不必用眼睛顺线走一遍——而那正是这游戏的难点。agent 自己写过:「挡路名单已经写全,不用顺着格子数」。剩下的评分噪声,只有靠聚合才压得下去。</dd>
<dt>现在怎么做</dt><dd>逐关排序改为机械假设(旋转深度、棋子数、互锁),由人工审核裁定;评分只用来读整档的高低。</dd>
</dl>
</div>
<div class="rule">
<h3><span class="no">2</span>先用规则机器人扫,再花 agent 时间;同一关不要跑两遍</h3>
<dl>
<dt>证据</dt><dd class="ev">第二轮意外把每关跑了两次,得到 50 对复测:其中 42 对(84%)重放了&lt;b&gt;完全相同&lt;/b&gt;的动作序列,机械指标复测 r = 1.00。规则机器人跑完 50 关只要 1 秒。</dd>
<dt>为什么</dt><dd>agent 近乎确定性,重复同一关几乎不产生新信息。为「提高可信度」而重复跑,是把预算花在已经知道的答案上。</dd>
<dt>现在怎么做</dt><dd>全量验收交给规则机器人;agent 每关只跑一次,省下的预算用来多覆盖关卡。</dd>
</dl>
</div>
<div class="rule">
<h3><span class="no">3</span>门槛写在生成期,不写在筛选期</h3>
<dl>
<dt>证据</dt><dd class="ev">把开局门槛提到「可点≥3 枚、演出≥0.8s」后筛选写出了 0 关——池子的中位数是 2 枚 / 0.57s。</dd>
<dt>为什么</dt><dd>筛选只能从池子里挑,挑不出池子里没有的东西。分布不对时,要改的是构造。</dd>
<dt>现在怎么做</dt><dd>开局问题由构造里的展示位插入解决,不是靠筛。</dd>
</dl>
</div>
<div class="rule">
<h3><span class="no">4</span>每加一个门槛,先证明它挡掉了东西</h3>
<dl>
<dt>证据</dt><dd class="ev">两次结构性猜测被实测推翻:「插入端多朝向出口」过滤器对漏斗零效果;为解释难度评分而建的 legibility 指标与评分相关 -0.02。</dd>
<dt>为什么</dt><dd>一个不改变漏斗的门槛,只会让人以为问题已经解决了。</dd>
<dt>现在怎么做</dt><dd>两者都已撤回/降级为纯测量。漏斗每一级的剩余数量都打印出来。</dd>
</dl>
</div>
<div class="rule">
<h3><span class="no">5</span>抱怨要对照基线,不要直接当缺陷</h3>
<dl>
<dt>证据</dt><dd class="ev">agent 33 条抱怨里最集中的是「路线太长读不完」。但已上线的 v0.5 最长路线 74 格,比这批新关卡的 74 格还长。</dd>
<dt>为什么</dt><dd>那是这个游戏本来的性格。把它当缺陷修掉,改的是游戏而不是关卡。</dd>
<dt>现在怎么做</dt><dd>只在审核页标出,交给人裁定。</dd>
</dl>
</div>
<h2>agent 能测什么,不能测什么</h2>
<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>直接观察,无主观成分</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>
</tbody>
</table>
<h2>要做 1000 关的话</h2>
<ol class="recipe">
<li><b>先把判据写成可执行的闸门</b>,再开始生成。这一轮的闸门链是:长成 → 首转双向 → 锁住 ≤90% → 无不可解态 → 终局朝向 ≥2 → 每转释放 ≥2 → 反差 ≥12 → 开局可点 ≥3 → 开局演出 ≥0.9s → 长巡游 ≥3。每一级剩多少必须打印出来。</li>
<li><b>用规则机器人做全量验收</b>(12 秒 / 全池),不用 agent。机械旋转指标已证明测的是关卡不是玩家。</li>
<li><b>agent 只抽样跑</b>,而且只看两样东西:机械缺陷指标,和自由文本抱怨。不要为难度标定花 agent 时间——那条路这一轮已经证明走不通。</li>
<li><b>难度曲线靠机械假设铺,靠人眼裁定。</b>用 <code>select-50.mjs</code> 按特征打分并均匀铺开,家族上限防止一套模板占满(上一版 1000 关实际只有 30 套模板)。</li>
<li><b>每一轮只动一个变量</b>,并且必须能在漏斗里看到它挡掉了什么,否则撤回。</li>
<li><b>人工审核要准备好材料</b>:开局盘面从求解器取(不是示意图)、标明哪些数字可信、每关一个试玩直达链接。</li>
</ol>
<h2>被实测否掉的做法</h2>
<table>
<thead><tr><th>试过什么</th><th>结果</th></tr></thead>
<tbody>
<tr><td>拟合 difficulty ~ 机械特征,用来重选关卡</td><td>响应几乎无方差(44/50 同分),回归的是常数</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>
<tr><td>「插入端多朝向出口」过滤器</td><td>对漏斗零效果,已删除</td></tr>
<tr><td>把锁住比例当难度主轴排序</td><td>方向反了:它奖励「开局什么都做不了」</td></tr>
</tbody>
</table>
<footer>
<p class="mono">生成:node scripts/build-process-report.mjs · 页面所有数字由脚本从 levels/campaign-50.json 与 bench/runs 直接测得</p>
<p>相关:<a href="review-50.html">五十关人工审核页</a> · <a href="level-board.html">任务看板</a> · <a href="bench-reliability.html">bench 复测可信度</a></p>
</footer>
</div>
</body>
</html>