Commit Graph
16 Commits
Author SHA1 Message Date
toshiocn f40efb8ba9 fix(airport): draw apron fans as solid wedges with a gap, not a fade 2026-09-21 19:30:33 +08:00
toshiocn 79afda0085 feat(airport): draw six equal apron fans instead of strip runways
Sectors are 60-degree wedges from the board centre, tucked against the tray. Radial seams mark the ABCDEF flats. Roll back with git checkout feat/airport-edge-runway.
2026-09-21 17:58:02 +08:00
toshiocn c99716b1d5 feat(airport): bind landings to the six visual flats
Entry side is the 60-degree wedge of the port, not the cell-edge direction. Levels regenerated; hint line joins the waiting plane to the on-board path.
2026-09-21 17:55:06 +08:00
toshiocn 0e67c4c188 feat(airport): draw the entry channel to the tray edge
Board-edge runways reach the tray outline with a threshold gate, fixed apron slabs mark each side, and a connected side lights the whole plane-to-stand channel in the neutral hint colour.
2026-09-21 14:38:54 +08:00
toshiocn 1be0e9caf1 feat(airport): add the Hex Airport dispatch lab as a baseline snapshot
Six-side aprons, frozen levels, solver, rewards and verify scripts; the edge-runway readability work continues from here.
2026-09-21 14:25:09 +08:00
cabinwandClaude Opus 5 4c4c250511 fix(levels): stop rejecting crowded boards for an endgame nobody measured
No level in this project is more than a third pieces — 13% to 28% across the
shipping fifty, and the thousand-level set that preceded it peaks at 32% with
exactly one level above 30%. Clearing a full board is the satisfying part of a
clearing game, and it has never been on offer.

It was not a design choice. deadTurnAudit walks the reachable state space with a
20000-state budget, and the endgame gate read its answer. Past about eleven
pieces that budget is gone long before the walk reaches any state with three
pieces left, so the audit returned `endgameMinOpen: null` — and the gate,
written `endgameMinOpen >= 2`, compared null and got false. Every denser level
was thrown out for a bad endgame that was never looked at. `exhausted` was right
there in the audit's return value and nothing consulted it.

Endgames are now enumerated outright rather than walked to: C(26,3) is 2600
subsets, so it finishes on any board this game will ever have. The statistic is
the SHARE of endgames that are tight, not the minimum over them, because a
minimum over more subsets falls for arithmetic alone — the same confound that
made doubleDead look eight times worse for deep levels when the rate was 2%
against 5%. By share, crowded boards are the better ones: worst 0.33% against
1.73% for sparse.

With the gate measuring something, density inverts. At eight pieces per
orientation: 44% density, 18 of 23 candidates passing the whole chain, and not
one failing the dead-turn gate. At two: 19% density, 9 of 23, with eleven
failing on dead turns. perTurn becomes a swept dial, and the hard piece ceiling
that truncated the placement schedule regardless of it is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:04:46 +08:00
cabinwandClaude Opus 5 f846a342d1 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>
2026-09-14 18:52:37 +08:00
cabinwandClaude Opus 5 073b682234 feat(motion): raise the cruising speed, and make the show gates follow it
Pieces still read as slow. Cruising speed goes from 120ms per unit to 85: the
median departure across the shipped fifty drops 970ms → 763ms and a level's
whole animation budget 15.6s → 12.7s, about 18% off. The longest departures do
not move — the cap holds them at 3.46s — so if those still drag, the dial is the
cap, which was settled separately and is left alone.

The gates that guarantee a level opens with something worth watching are stated
in milliseconds, and they were picked while a piece crossed a unit in 120ms.
Held fixed while the speed changes, they stop meaning what they meant: this
change alone made 20 of the 50 shipped levels "fail" an opening gate every one
of them had passed, purely from the unscaled number. Scaled to the dial, none
fail — the set is unchanged, and the alarm was an artifact of the constant. So
the cruise threshold and the opening floor now derive from the speed, naming
the speed they were calibrated at.

exitTiming takes msPerUnit the way it already took knee and maxMs, and the
comparison lab gets a speed slider, reads the levels actually shipping rather
than the prototype sweep they were selected from, and totals every route in the
campaign instead of the four on screen — those four skew long, where the cap
flattens any speed change to 6%.

The test that pins constant cruising speed now reads the dial from the token
instead of hard-coding 120, so tuning motion no longer looks like a broken
guarantee.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:46:32 +08:00
cabinwandClaude Opus 5 a250baba53 fix(motion): stop a long departure from playing for seventeen seconds
Exit timing was linear in path length with no ceiling, so the longest route in
the shipped set animated for 17.4s and 19% of all departures ran past two
seconds. MOTION already carried exitMaxTravelMs and exitLongMaxMs for this;
nothing read them.

Below the knee nothing changes — every piece still cruises at the same speed,
which is the property the old test protected and the reason motion reads as
physical. Past it the piece accelerates rather than taking longer, approaching
a ceiling. Length is still legible: longer routes still play longer, just with
a compressing tail.

The test now states the rule it is protecting instead of "no duration cap":
constant speed below the knee, a bounded and monotone tail above it.

labs/exit-timing plays the same real route side by side, uncapped against a
candidate setting, with knee and ceiling on sliders — the numbers say 3.5s but
only watching it says whether that is right. Defaults are provisional at
2.4s / 3.2s pending that call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:41:06 +08:00
cabinwandClaude Opus 5 643cba0ed9 feat(levels): D5 three-rotation prototype
The shipped 1000 levels have a hole: 568 of them need no rotation at all, and
none needs more than two — the mechanic the game is built on is mostly
decoration. This fills the empty band with levels that need at least three.

createDeepLock builds a four-piece chain where each link only releases at a
later orientation, and growDeepLevel packs the rest of the board around it
without opening a shortcut. Candidate pools are built per orientation and left
untruncated; ranking by bend count first starved the packer and capped levels
at four to six pieces instead of twelve.

deadTurnAudit enumerates every reachable (orientation, remaining-set) state to
prove no dead ends exist, and counts how long a player can be left with
nothing to do — solvable and feeling solvable are different things, which is
what "level 2 is impossible" turned out to be about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:00:33 +08:00
cabinwandClaude Opus 5 47be0ad9c4 feat(bench): live observer page
Watch an agent play in real time, replayed with the main game's renderer and
animations. The board holds the stage with the live panel beside it: a
millisecond stopwatch, how long the current step has been thinking, steps,
decisions, pieces cleared, dead taps and rotations, then the agent's running
commentary. A banner over the board names who is playing, self-reported by the
agent via --agent.

The page polls unconditionally; the checkbox only decides whether the cursor
chases the newest step, and it follows the agent into its next run once this
one ends. Clicking any step in the feed replays the board at that moment, and
the arrow keys walk it — there is no scrub bar, since the feed already is one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:00:33 +08:00
cabinwandClaude Opus 5 c760c23fd1 feat(bench): agent bench session core and CLI
Let any agent with a shell play a level the way a person does: look at the
board, decide what to tap or which way to turn, read the feedback, decide
again. Every step carries the agent's own reason, so a human can watch and
judge the level from how it was played.

observe() returns what the eye sees — each piece's drawn route, which cells it
crosses, who is sitting on it, and who it brushes past — never "which pieces
can leave". act() goes through the real Game rules; metrics() scores the run
afterwards with the solver.

Pieces are wide, so a route with nobody standing on it can still be blocked by
a neighbour the curve hugs. That is invisible in a cell listing, so every
obstacle is reported, on-route and grazing alike: reporting only the leading
one hid the rest, and in 113 of 581 such cases clearing the blocker sprang a
collision the agent had never been shown.

AGENT.md is the single file an agent reads — rules, board reading, commands,
rating scale, worked example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 14:00:33 +08:00
cabinw 4644e975b0 feat: add production campaigns, gameplay labs, and review documentation 2026-09-13 12:27:10 +08:00
cabinw d1dffa9338 Add three playable puzzle variants in an isolated lab 2026-09-13 12:19:13 +08:00
cabinw e92cccba82 feat: version campaigns and document original puzzle design
Restore v0.1 and v0.2 archives with separate progress, publish the curved v0.3 campaign, and document the original 24-level design with reproducible evidence. Refine board presentation and movement, and keep video lab tooling within its own directory.
2026-09-09 13:17:30 +08:00
cabinw 70b021d4cc feat: expand campaign and refine puzzle visuals and controls 2026-09-09 11:02:45 +08:00