Files
cabinwandClaude Opus 5 c0c1558464 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>
2026-09-14 17:26:22 +08:00

65 lines
3.7 KiB
JavaScript
Raw Permalink 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.
#!/usr/bin/env node
// Rebuild the level set a bench round actually played, from the round's runs.
//
// node scripts/round-set.mjs --round=R2 [--out=…]
//
// Disk snapshots of "the set round N played" are not trustworthy: select-50
// writes levels/campaign-50.json in place, so any selection experiment run
// between the round and the copy silently replaces it. One such copy kept only
// 28 of the 50 boards its round had played, which would have made the round
// comparison compare a set against somebody else's levels.
//
// Runs store the whole board and its campaign index, so the round is its own
// record. That record cannot drift.
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { deepLevelKey } from '../src/deep-lock.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 ROUND = String(args.round ?? 'R2');
export function roundSet(round, dir = resolve('bench/runs')) {
const byBoard = new Map();
for (const f of readdirSync(dir)) {
if (!f.endsWith('.json') || f === 'index.json') continue;
try {
const d = JSON.parse(readFileSync(resolve(dir, f), 'utf8'));
if (d.status === 'playing' || !d.level?.tiles) continue;
if (!new RegExp(`^${round} · `).test(String(d.agent))) continue;
const k = deepLevelKey(d.level);
if (!byBoard.has(k)) byBoard.set(k, { level: d.level, runs: [] });
byBoard.get(k).runs.push(d);
} catch { /* torn read */ }
}
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);
}
}