feat(levels): give every level an opening worth watching

The chain locks all four of its pieces at the opening by construction, and the
pieces grown around it are mostly locked too, so a level built from it hands a
player two rim pieces and a third of a second of animation before any thinking
starts. Measured across the 235-level pool: median two tappable pieces at the
opening, and the best departure available plays for 0.57s.

Growth now reserves a couple of slots for showcase pieces — tappable at the
opening, with a route long enough to read as a journey and short enough to stay
under the animation ceiling. Median opening choices go 2 to 3 and the best
opening show 0.72s to 1.12s, without shortcutting the lock: each candidate is
still put through the solver.

Rotation quota is gone from the board. Turning is free and it is what the game
is about, so depth is grown rather than capped; what is gated is whether each
turn pays out, whether pieces swing between hopeless and trivial, and whether
there is a show up front.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
cabinw
2026-09-14 16:45:24 +08:00
co-authored by Claude Opus 5
parent a250baba53
commit 0aa080c7c3
5 changed files with 296 additions and 98 deletions
+5 -5
View File
@@ -72,8 +72,7 @@ const STATUS = {
done: ['完成', 'good'], doing: ['进行中', 'live'], todo: ['待办', 'idle'], blocked: ['受阻', 'bad'],
};
const quota = board.targets.rotationQuota;
const quotaRows = Object.entries(quota).map(([r, want]) => ({ r, want, have: pool.rot[r] || 0 }));
const depthRows = Object.entries(pool.rot).sort((a, b) => Number(a[0]) - Number(b[0]));
const html = `<!doctype html>
<html lang="zh-CN">
@@ -191,9 +190,10 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--rule);color:v
</div>
<div class="scroll"><table>
<thead><tr><th>旋转数</th><th class="n">目标关数</th><th class="n">现有</th><th>缺口</th></tr></thead>
<thead><tr><th>最少旋转数</th><th class="n">候选关数</th><th>说明</th></tr></thead>
<tbody>
${quotaRows.map(q => `<tr><td>${q.r} 转</td><td class="n">${q.want}</td><td class="n">${q.have}</td><td>${q.have >= q.want ? '<span class="tag good">已满足</span>' : `<span class="tag ${q.have ? 'live' : 'idle'}">还差 ${q.want - q.have}</span>`}</td></tr>`).join('\n')}
${depthRows.map(([r, n]) => `<tr><td>${r} 转</td><td class="n">${n}</td><td class="sub">${Number(r) <= 3 ? '浅' : Number(r) <= 5 ? '中' : '深'}</td></tr>`).join('\n')}
${depthRows.length <= 1 ? '<tr><td colspan="3" class="sub">旋转深度没有梯度——生成器把最少旋转写死了,这正是「太简单」的根因</td></tr>' : ''}
</tbody></table></div>
<p class="sub">路线家族分布:${Object.entries(pool.families).map(([k, v]) => `${esc(k)} ×${v}`).join(' · ') || '—'} 终局可走朝向分布:${JSON.stringify(pool.endgame)}</p>
@@ -205,7 +205,7 @@ ${board.definition.rules.map(r => `<li><b>${esc(r.k)}</b>:${esc(r.v)}<span cla
<div class="scroll"><table>
<thead><tr><th>关号区间</th><th>旋转数</th><th>目标 difficulty 评分</th></tr></thead>
<tbody>
${board.targets.difficultyCurve.map(b => `<tr><td>${esc(b.band)}</td><td>${esc(b.rot)}</td><td class="mono">${esc(b.difficulty)}</td></tr>`).join('\n')}
${board.targets.rotationDepth.map(b => `<tr><td>${esc(b.band)}</td><td>${esc(b.rot)}</td><td class="mono">${esc(b.difficulty)}</td></tr>`).join('\n')}
</tbody></table></div>
<h2><span class="num">C</span>任务</h2>