feat(routes): let leftover track pair into a bend instead of straight across
Playing on a phone, the boards read as a grid of long straight lines crossing everywhere — "一看到这个画面,我脑袋就大". The cause is one line in the filler. Each family lays its signature routes over part of the board and leaves the rest to completePorts, which paired every leftover port with the one straight across. On a switchback board that made 76% of all track pairs diameters and left 43 of 61 cells containing nothing but diameters. Two diameters in one cell always cross, so the straightness and the crossings were never two problems. Preferring a bend halves both, in every family: switchback 76% → 40% straight and 2.38 → 0.92 crossings per cell, crossing 86% → 10%, weave 37% → 5%. It was also quietly suppressing 反差, the swing between a piece looking hopeless and becoming trivial, which the design brief calls the point of the game. Straight routes barely change between orientations. With bends, petals goes from 4.2 to 14.0 and crossing from 4.8 to 28.8 — both had been failing the ≥12 gate and were absent from the level pool entirely. Petals is the family the user singles out as the good-looking one. Generation yield rises with it: four seeds now grow 24 playable levels against 3, at greater rotation depth. createRouteBoard keeps 'straight' as its default so nothing already built moves; the sweep opts in. labs/route-gallery draws both, with the real renderer, so the choice is made by looking rather than by reading a number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+13
-4
@@ -228,14 +228,23 @@ function crossing(design, radius, variant) {
|
||||
design.ray(gate, 0); design.ray(gate, 1);
|
||||
}
|
||||
|
||||
function completePorts(design, family, variant) {
|
||||
// Every family lays its signature routes over part of the board and leaves the
|
||||
// rest to be filled in. The filler used to pair a leftover port with the one
|
||||
// straight across, which is why a board reads as a grid of long straight lines
|
||||
// crossing everywhere: on a switchback board 76% of all track pairs were
|
||||
// diameters and 43 of 61 cells were nothing but diameters. Two diameters in one
|
||||
// cell always cross, so straight filler buys crossings as well as straightness.
|
||||
// `fill` picks what a leftover port prefers instead; 'curve' asks for a bend.
|
||||
function completePorts(design, family, variant, fill = 'straight') {
|
||||
for (const cell of design.cells) {
|
||||
const free = cell.tracks.flatMap((value, port) => value === null ? [port] : []);
|
||||
// Periodic completion keeps secondary rails ordered instead of random noise.
|
||||
const phase = mod(cell.q - cell.r + variant, 3);
|
||||
while (free.length) {
|
||||
const a = free.shift();
|
||||
const preferred = family === 'weave' && phase === 0 ? mod(a + 1) : opposite(a);
|
||||
const bend = mod(a + 1 + phase % 2); // 60° or 120°, varied by phase
|
||||
const preferred = fill === 'curve' ? bend
|
||||
: family === 'weave' && phase === 0 ? mod(a + 1) : opposite(a);
|
||||
const b = free.splice(free.includes(preferred) ? free.indexOf(preferred) : mod(phase, free.length), 1)[0];
|
||||
cell.tracks[a] = b; cell.tracks[b] = a;
|
||||
}
|
||||
@@ -290,7 +299,7 @@ function describeRoutes(design) {
|
||||
return { featured, heroStarts: selected.map(({ pairs, ...candidate }) => candidate) };
|
||||
}
|
||||
|
||||
export function createRouteBoard(seed, { family = 'petals', radius = 3, variant = 0 } = {}) {
|
||||
export function createRouteBoard(seed, { family = 'petals', radius = 3, variant = 0, fill = 'straight' } = {}) {
|
||||
const spec = ROUTE_FAMILIES.find(candidate => candidate.id === family);
|
||||
if (!spec) throw new RangeError(`Unknown route family: ${family}`);
|
||||
if (!Number.isInteger(radius) || radius < 2 || radius > 5) throw new RangeError('Route radius must be 2–5');
|
||||
@@ -303,7 +312,7 @@ export function createRouteBoard(seed, { family = 'petals', radius = 3, variant
|
||||
if (family === 'switchback') switchback(design, radius, rng, gateVariant);
|
||||
if (family === 'weave') weave(design, radius, rng, gateVariant);
|
||||
if (family === 'crossing') crossing(design, radius, gateVariant);
|
||||
completePorts(design, family, gateVariant);
|
||||
completePorts(design, family, gateVariant, fill);
|
||||
const { featured, heroStarts } = describeRoutes(design);
|
||||
return {
|
||||
version: 1, id: `route-${family}-${radius}-${seed}-${variant}`, name: `${spec.name} · 路线草图`,
|
||||
|
||||
Reference in New Issue
Block a user