fix(levels): let a packed board pay its opening as a cascade, not one long trip
The opening gate asks for a departure of at least 0.57s, which is how a sparse board pays: one piece threading a long route while you watch. A packed board cannot pay that way — the pieces free at the opening are the ones at the rim, and their routes are short by construction. So the gate rejected 29 of 29 crowded candidates for lacking a payoff they deliver in a different currency: the cascade of pieces leaving one after another, which is what the brief singles out as 多个棋子连环出去,非常赏心悦目. openingRunMs totals every departure available before a turn is needed, and the gate takes either that or the single long show. Crowded boards come through at 46% density, 0.92 crossings per cell and 14% straight track. Both thresholds scale with the motion dial the way the first one already did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,8 @@ const MIN_CONTRAST = Number(args.minContrast ?? 12);
|
||||
// levels that were fine. See openingShowFloor in deep-lock.
|
||||
const MIN_OPENING_SHOW = openingShowFloor(Number(args.minOpeningShow ?? 800));
|
||||
const MIN_OPENING_CHOICES = Number(args.minOpeningChoices ?? 3);
|
||||
// The cascade alternative to a single long show, in the same calibrated units.
|
||||
const MIN_OPENING_RUN = openingShowFloor(Number(args.minOpeningRun ?? 2600));
|
||||
const MIN_CRUISES = Number(args.minCruises ?? 3);
|
||||
// Rotation count is deliberately not capped: turning is free and it is the
|
||||
// point of the game. What has to hold is that every turn pays out (minRelease),
|
||||
@@ -123,7 +125,9 @@ const playable = grownEnough.filter(r => r.audit.unsolvable === 0 && r.stats.fir
|
||||
// The show is the payout. An opening that offers two rim pieces and a 0.3s
|
||||
// hop gives a casual player nothing to enjoy before the thinking starts.
|
||||
&& r.show.openingChoices >= MIN_OPENING_CHOICES
|
||||
&& r.show.openingBestMs >= MIN_OPENING_SHOW
|
||||
// One long departure, or a cascade of short ones — a packed board pays the
|
||||
// other way round and the single-best test cannot see it.
|
||||
&& (r.show.openingBestMs >= MIN_OPENING_SHOW || r.show.openingRunMs >= MIN_OPENING_RUN)
|
||||
&& r.show.cruises >= MIN_CRUISES
|
||||
&& r.shape.crossPerCell <= MAX_CROSS);
|
||||
// Locked-share used to sort first and highest, which is backwards for a casual
|
||||
@@ -142,9 +146,9 @@ const g5 = g4.filter(r => r.stats.minRelease >= MIN_RELEASE);
|
||||
console.log(`\n【门槛】长成 ${grownEnough.length} → 首转双向 ${g1.length} → 锁住≤90% ${g2.length} → 无不可解态 ${g3.length} → 终局紧局≤${(MAX_TIGHT_SHARE*100).toFixed(1)}% ${g4.length} → 每转释放≥${MIN_RELEASE} ${g5.length} → 反差≥${MIN_CONTRAST} ${playable.length}`);
|
||||
const g6 = g5.filter(r => r.contrast.contrast >= MIN_CONTRAST);
|
||||
const g7 = g6.filter(r => r.show.openingChoices >= MIN_OPENING_CHOICES);
|
||||
const g8 = g7.filter(r => r.show.openingBestMs >= MIN_OPENING_SHOW);
|
||||
const g8 = g7.filter(r => r.show.openingBestMs >= MIN_OPENING_SHOW || r.show.openingRunMs >= MIN_OPENING_RUN);
|
||||
const g9 = g8.filter(r => r.show.cruises >= MIN_CRUISES);
|
||||
console.log(` → 开局可点≥${MIN_OPENING_CHOICES} ${g7.length} → 开局演出≥${(MIN_OPENING_SHOW / 1000).toFixed(2)}s ${g8.length} → 长巡游≥${MIN_CRUISES} ${g9.length} → 交叉/格≤${MAX_CROSS} ${playable.length}`);
|
||||
console.log(` → 开局可点≥${MIN_OPENING_CHOICES} ${g7.length} → 开局演出≥${(MIN_OPENING_SHOW/1000).toFixed(2)}s 或连锁≥${(MIN_OPENING_RUN/1000).toFixed(1)}s ${g8.length} → 长巡游≥${MIN_CRUISES} ${g9.length} → 交叉/格≤${MAX_CROSS} ${playable.length}`);
|
||||
console.log(`【候选池】旋转深度 ${JSON.stringify(playable.reduce((m, r) => (m[r.stats.rotations] = (m[r.stats.rotations] || 0) + 1, m), {}))}`);
|
||||
console.log(` 交叉/格 中位 ${med(playable.map(r => r.shape.crossPerCell)).toFixed(2)} · 直线占比 中位 ${(med(playable.map(r => r.shape.straightShare))*100).toFixed(0)}% · 密度 中位 ${(med(playable.map(r => r.level.tiles.length/r.level.cells.length))*100).toFixed(0)}%`);
|
||||
console.log(` 反差 中位 ${med(playable.map(r => r.contrast.contrast)).toFixed(1)} · 家族 ${JSON.stringify(playable.reduce((m, r) => (m[r.family] = (m[r.family] || 0) + 1, m), {}))}`);
|
||||
|
||||
@@ -304,6 +304,13 @@ export function showStats(level) {
|
||||
exits: shows.length,
|
||||
openingChoices: openingShows.length,
|
||||
openingBestMs: openingShows.length ? Math.round(Math.max(...openingShows)) : 0,
|
||||
// What the opening actually pays out, counting every piece that can leave
|
||||
// before a turn is needed. A sparse board pays with one long journey; a
|
||||
// packed board pays with a cascade of short ones, which is the thing the
|
||||
// brief calls 多个棋子连环出去,非常赏心悦目. Judging both by the length of
|
||||
// their single best departure fails every packed board — 29 of 29 in a
|
||||
// sweep — for lacking a payoff they deliver a different way.
|
||||
openingRunMs: Math.round(openingShows.reduce((sum, ms) => sum + ms, 0)),
|
||||
totalMs: Math.round(shows.reduce((a, b) => a + b, 0)),
|
||||
medianMs: Math.round(sorted[Math.floor(sorted.length / 2)]),
|
||||
longestMs: Math.round(sorted[sorted.length - 1]),
|
||||
|
||||
Reference in New Issue
Block a user