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:
cabinw
2026-09-14 20:14:33 +08:00
co-authored by Claude Opus 5
parent 8d766b6f06
commit de485c0c34
2 changed files with 14 additions and 3 deletions
+7
View File
@@ -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]),