feat(levels): select for 反差, the thing round 2 says separates hard from easy
Round 2 rated 16 runs a 3 and 84 a 2, which is too little spread to rank levels
by — but enough to ask what the 3s had in common. Contrast: 32.6 against 20.5,
a 59% gap and by far the largest of any feature. Rotation depth came second at
+1.2 turns. Board size and longest route ran the *other* way: the levels rated
harder were slightly smaller with shorter routes, which is what the design brief
has said all along — 难是难在用户无法一下子判断, not in how much board there is.
So within a depth, picking evenly across the ranked slice was diluting the one
thing the brief calls the point of the game. The depth quota already supplies
the difficulty range; inside a depth there is nothing to spread for, so it takes
the highest-contrast levels instead.
Taken alone that collapses the route grammars — two families filled 36 of the
50 — so the family cap comes down to 0.26. The pair beats the shipped round on
both axes rather than trading one for the other: contrast 14.8 / 22.0 / 27.9 →
15.2 / 24.9 / 28.6, families {18,12,8,2,10} → {13,13,5,9,10}. Same rule bot,
mechanics unchanged: worst blind streak still 1, dead taps still 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+24571
-25224
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,14 @@ const FAMILY_CAP = Number(args.familyCap ?? 0.36);
|
|||||||
// no matter how the score is weighted. A quota per depth states the curve
|
// no matter how the score is weighted. A quota per depth states the curve
|
||||||
// instead of hoping it falls out of the ranking.
|
// instead of hoping it falls out of the ranking.
|
||||||
const CURVE = args.curve ? JSON.parse(args.curve) : null;
|
const CURVE = args.curve ? JSON.parse(args.curve) : null;
|
||||||
|
// Within one depth, what to take. Spreading evenly across the ranked slice was
|
||||||
|
// the default, and it quietly dilutes the thing the design brief calls the
|
||||||
|
// point of the game: levels the agent rated hardest carried 59% more 反差 than
|
||||||
|
// the rest (32.6 against 20.5), far and away the biggest difference between
|
||||||
|
// them — bigger than rotation depth, while board size and route length ran the
|
||||||
|
// other way. The depth quota already supplies the range, so inside a depth
|
||||||
|
// there is nothing to spread for.
|
||||||
|
const PICK = String(args.pickBy ?? 'contrast');
|
||||||
|
|
||||||
// Starting weights, replaced by the fit after a round with agents.
|
// Starting weights, replaced by the fit after a round with agents.
|
||||||
const DEFAULT_WEIGHTS = { rotations: 1.0, pieces: 0.10, lockedShare: 1.6, monkeyAPT: 0.14, cells: 0.004 };
|
const DEFAULT_WEIGHTS = { rotations: 1.0, pieces: 0.10, lockedShare: 1.6, monkeyAPT: 0.14, cells: 0.004 };
|
||||||
@@ -70,6 +78,17 @@ const takeable = (rows, i) => {
|
|||||||
// Even spread across a ranked list, taking `want` of it. Used on the whole pool
|
// Even spread across a ranked list, taking `want` of it. Used on the whole pool
|
||||||
// when there is no curve, and on one depth's slice when there is.
|
// when there is no curve, and on one depth's slice when there is.
|
||||||
const spread = (rows, want) => {
|
const spread = (rows, want) => {
|
||||||
|
if (PICK === 'contrast') {
|
||||||
|
const out = [];
|
||||||
|
for (const row of [...rows].sort((a, b) => b.contrast - a.contrast)) {
|
||||||
|
if (out.length >= want) break;
|
||||||
|
if (!takeable([row], 0)) continue;
|
||||||
|
row.taken = true;
|
||||||
|
used[row.family] = (used[row.family] ?? 0) + 1;
|
||||||
|
out.push(row);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
const out = [];
|
const out = [];
|
||||||
for (let slot = 0; slot < want; slot++) {
|
for (let slot = 0; slot < want; slot++) {
|
||||||
const ideal = Math.round(slot * (rows.length - 1) / Math.max(1, want - 1));
|
const ideal = Math.round(slot * (rows.length - 1) / Math.max(1, want - 1));
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user