diff --git a/docs/review-50.html b/docs/review-50.html index 9bd16a8..19b259d 100644 --- a/docs/review-50.html +++ b/docs/review-50.html @@ -62,25 +62,25 @@ footer{margin-top:50px;padding-top:18px;border-top:1px solid var(--rule);color:v

五十关 · 人工审核

缩略图是开局盘面:绿色的棋子开局就能点,灰色的被挡着。点「试玩」用真游戏打开这一关。

-

关卡集 levels/campaign-50.json · agent 实测批次 R2 · grok xhigh(50/50 关已评)· 生成于 2026-09-14 17:14

+

关卡集 levels/campaign-50.json · agent 实测批次 R2 · grok xhigh(50/50 关已评)· 生成于 2026-09-14 17:16

难度曲线(agent 实测)

- +
档已评关数难度无解感盲转/关空点/关
前档 L1–L12122.01.10.20.0
前档 L1–L12122.01.20.20.0
中档 L13–L32202.11.20.00.0
后档 L33–L50182.41.70.20.0
-

难度 3 = 刚刚好;无解感越低越好。这两项是评分里唯二复测可信的(r = 0.93 / 0.85),其余几项是模型噪声,不列。

+

难度 3 = 刚刚好;无解感越低越好。这两项只在按档平均时可读:逐关复测 r = 0.55 / 0.45(本轮 50 对复测现算),好玩与清晰度更低,不列。三档单调上升是这一轮的结果,逐关的高低不要当真。

这批数字里,哪些能信

机械面是干净的,可以信。agent 跑完 50 关:空点 0.0 次/关、盲转 0.1 次/关、反向转 0.1 次/关,50/50 关通关。「动了半天没结果」这类结构缺陷已经被生成期的闸门挡掉了。

-

难度评分不能信,它区分不了这批关卡。50 关里有 41 关被打成 2 分,而这些关卡机械上差着三倍(决策数 0–8)。原因不在关卡,在 bench:文字盘面会把每颗棋子的路线、挡路者、紧贴者直接列成清单,agent 从来不需要用眼睛顺着线走一遍——而那正是这个游戏的难点所在。agent 自己在报告里写过这句:「挡路名单已经写全,不用顺着格子数」。

-

所以难度顺序是机械假设,等你的判定。L1 到 L50 是按旋转深度、棋子数、互锁程度排的,不是按谁测出来更难。评分唯一能用的是它的总体读数:整批偏易一档(2 分 vs 刚刚好的 3 分)。

-

两件事我没有擅自改,留给你定。agent 的 33 条抱怨里反复出现两个主题:路线太长看不完(「68 格 27 弯」「42 格 42 弯」),以及紧贴容易看漏(「看着能出界却会擦到」)。两项都是感知问题,而已上线的 v0.5 在这两项上更严重(最长路线 88 格 vs 这里 74 格)——它是这个游戏本来的性格,不是这次构造引入的。卡片上「最长路线」超 45 格、「紧贴棋子」超 1 枚会标黄,你试玩时留意。

+

难度评分不能信,它区分不了这批关卡。50 关里有 42 关被打成 2 分,而这些关卡机械上差着三倍(决策数 0–8)。原因不在关卡,在 bench:文字盘面会把每颗棋子的路线、挡路者、紧贴者直接列成清单,agent 从来不需要用眼睛顺着线走一遍——而那正是这个游戏的难点所在。agent 自己在报告里写过这句:「挡路名单已经写全,不用顺着格子数」。

+

所以逐关顺序是机械假设,等你的判定。L1 到 L50 是按旋转深度、棋子数、互锁程度排的,不是按谁测出来更难。评分能用的只有整档:这一轮三档是 2.0 / 2.1 / 2.4,第一次单调上升,但后档 2.4 仍低于「刚刚好」的 3 分。

+

两件事我没有擅自改,留给你定。agent 这一轮写了 100 条抱怨,反复出现两个主题:路线太长看不完(「开局 t0 那条 42 格弯路太长,挡谁只能靠摘要判断」「开局t0那条54格路线太长,真正有用的只有路上挡着谁这一句」),以及紧贴容易看漏(「朝向1时t5路上没人却紧贴t4和t6,长直道擦碰差点误点」「第14步转到朝向2,t0看起来能出界但会擦到t2,差点误点」)。两项都是感知问题,而已上线的 v0.5 在这两项上更严重(最长路线 88 格 vs 这里 74 格)——它是这个游戏本来的性格,不是这次构造引入的。卡片上「最长路线」超 45 格、「紧贴棋子」超 1 枚会标黄,你试玩时留意。

逐关

@@ -216,8 +216,8 @@ footer{margin-top:50px;padding-top:18px;border-top:1px solid var(--rule);color:v 紧贴棋子0 枚
-
难度 —无解感 —17 步7 决策
- +
难度 2无解感 217 步7 决策
+
第14步转到朝向2,t0看起来能出界但会擦到t2,差点误点
试玩 → @@ -641,8 +641,8 @@ footer{margin-top:50px;padding-top:18px;border-top:1px solid var(--rule);color:v 紧贴棋子0 枚
-
难度 2无解感 213 步8 决策
-
朝向1点完t1后剩下四枚两两对堵,超长轨道绕几十格,一时分不清是还没清干净还是该转
+
难度 2无解感 113 步7 决策
+
开局长轨道绕三十多格,t1和t2对挡时要扫一遍才确定一枚都走不了、只能转
试玩 → diff --git a/scripts/build-review-50.mjs b/scripts/build-review-50.mjs index a30f79a..4ce37b5 100644 --- a/scripts/build-review-50.mjs +++ b/scripts/build-review-50.mjs @@ -45,6 +45,7 @@ if (existsSync(RUNS)) { const esc = s => String(s ?? '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); const mean = a => a.length ? a.reduce((x, y) => x + y, 0) / a.length : null; +const f2 = x => x == null || Number.isNaN(x) ? '—' : (Math.round(x * 100) / 100).toFixed(2); const f1 = x => x == null || Number.isNaN(x) ? '—' : (Math.round(x * 10) / 10).toFixed(1); // A flat SVG of the opening position: tracks in grey, pieces as arrows. Small @@ -81,8 +82,45 @@ const perceptual = Object.fromEntries(levels.map(l => [l.id, { ...legibility(l), }])); +// This round played most levels twice, so reliability is measured here rather +// than quoted: an older, smaller sample had difficulty at r = 0.93, and on this +// set it is not that. +const runsByBoard = new Map(); +if (existsSync(RUNS)) { + for (const f of readdirSync(RUNS)) { + if (!f.endsWith('.json') || f === 'index.json') continue; + try { + const d = JSON.parse(readFileSync(resolve(RUNS, f), 'utf8')); + if (d.agent !== AGENT || d.status === 'playing' || !d.level?.tiles) continue; + const k = deepLevelKey(d.level); + if (!boardKey.has(k)) continue; + if (!runsByBoard.has(k)) runsByBoard.set(k, []); + runsByBoard.get(k).push(metrics(d)); + } catch { /* torn read */ } + } +} +const retestPairs = [...runsByBoard.values()].filter(v => v.length >= 2).map(v => [v[0], v[1]]); +// Quote the round's own complaints rather than the previous round's, which were +// written about levels this edition no longer contains. +const complaints = [...runsByBoard.values()].flat().map(m => String(m.rating?.worst ?? '').trim()).filter(Boolean); +const pickQuotes = re => [...new Set(complaints.filter(t => re.test(t)))] + .sort((a, b) => a.length - b.length).slice(0, 2).map(t => t.length > 34 ? t.slice(0, 34) + '…' : t); +const longQuotes = pickQuotes(/\d{2,}\s*格|太长|读不完|跟不住|绕/); +const grazeQuotes = pickQuotes(/紧贴|擦/); +const sd = a => { const m = mean(a); return Math.sqrt(mean(a.map(v => (v - m) ** 2))); }; +const retest = get => { + const ok = retestPairs.filter(p => Number.isFinite(get(p[0])) && Number.isFinite(get(p[1]))); + if (ok.length < 8) return null; + const A = ok.map(p => get(p[0])), B = ok.map(p => get(p[1])); + const sa = sd(A), sb = sd(B); + if (!sa || !sb) return null; + const ma = mean(A), mb = mean(B); + return mean(A.map((v, i) => (v - ma) * (B[i] - mb))) / (sa * sb); +}; + const rated = levels.map(l => verdicts[l.id]).filter(Boolean); const band = (a, b) => levels.slice(a, b).map(l => verdicts[l.id]).filter(Boolean); +const BANDS = [['前档 L1–L12', 0, 12], ['中档 L13–L32', 12, 32], ['后档 L33–L50', 32, 50]]; const bandRow = (label, a, b) => { const rows = band(a, b); const g = k => mean(rows.map(m => Number(m.rating?.[k])).filter(x => !Number.isNaN(x))); @@ -164,14 +202,14 @@ ${bandRow('中档 L13–L32', 12, 32)} ${bandRow('后档 L33–L50', 32, 50)} -

难度 3 = 刚刚好;无解感越低越好。这两项是评分里唯二复测可信的(r = 0.93 / 0.85),其余几项是模型噪声,不列。

+

难度 3 = 刚刚好;无解感越低越好。这两项只在按档平均时可读:逐关复测 r = ${f2(retest(m => Number(m.rating?.difficulty)))} / ${f2(retest(m => Number(m.rating?.stuck)))}(本轮 ${retestPairs.length} 对复测现算),好玩与清晰度更低,不列。三档单调上升是这一轮的结果,逐关的高低不要当真。

这批数字里,哪些能信

机械面是干净的,可以信。agent 跑完 ${rated.length} 关:空点 ${f1(mean(rated.map(m => m.deadTaps)))} 次/关、盲转 ${f1(mean(rated.map(m => m.blindRotations)))} 次/关、反向转 ${f1(mean(rated.map(m => m.wrongWayRotations)))} 次/关,${rated.filter(m => m.cleared).length}/${rated.length} 关通关。「动了半天没结果」这类结构缺陷已经被生成期的闸门挡掉了。

难度评分不能信,它区分不了这批关卡。${rated.length} 关里有 ${rated.filter(m => Number(m.rating?.difficulty) === 2).length} 关被打成 2 分,而这些关卡机械上差着三倍(决策数 0–8)。原因不在关卡,在 bench:文字盘面会把每颗棋子的路线、挡路者、紧贴者直接列成清单,agent 从来不需要用眼睛顺着线走一遍——而那正是这个游戏的难点所在。agent 自己在报告里写过这句:「挡路名单已经写全,不用顺着格子数」。

-

所以难度顺序是机械假设,等你的判定。L1 到 L50 是按旋转深度、棋子数、互锁程度排的,不是按谁测出来更难。评分唯一能用的是它的总体读数:整批偏易一档(2 分 vs 刚刚好的 3 分)。

-

两件事我没有擅自改,留给你定。agent 的 33 条抱怨里反复出现两个主题:路线太长看不完(「68 格 27 弯」「42 格 42 弯」),以及紧贴容易看漏(「看着能出界却会擦到」)。两项都是感知问题,而已上线的 v0.5 在这两项上更严重(最长路线 88 格 vs 这里 ${Math.max(...Object.values(perceptual).map(p => p.longest))} 格)——它是这个游戏本来的性格,不是这次构造引入的。卡片上「最长路线」超 45 格、「紧贴棋子」超 1 枚会标黄,你试玩时留意。

+

所以逐关顺序是机械假设,等你的判定。L1 到 L50 是按旋转深度、棋子数、互锁程度排的,不是按谁测出来更难。评分能用的只有整档:这一轮三档是 ${BANDS.map(([, a, b]) => f1(mean(levels.slice(a, b).map(l => verdicts[l.id]).filter(Boolean).map(m => Number(m.rating?.difficulty)).filter(Number.isFinite)))).join(' / ')},第一次单调上升,但后档 ${f1(mean(levels.slice(32).map(l => verdicts[l.id]).filter(Boolean).map(m => Number(m.rating?.difficulty)).filter(Number.isFinite)))} 仍低于「刚刚好」的 3 分。

+

两件事我没有擅自改,留给你定。agent 这一轮写了 ${complaints.length} 条抱怨,反复出现两个主题:路线太长看不完(${longQuotes.map(q => `「${esc(q)}」`).join('') || '—'}),以及紧贴容易看漏(${grazeQuotes.map(q => `「${esc(q)}」`).join('') || '—'})。两项都是感知问题,而已上线的 v0.5 在这两项上更严重(最长路线 88 格 vs 这里 ${Math.max(...Object.values(perceptual).map(p => p.longest))} 格)——它是这个游戏本来的性格,不是这次构造引入的。卡片上「最长路线」超 45 格、「紧贴棋子」超 1 枚会标黄,你试玩时留意。

逐关