refit-weights regresses the weights select-50 scores with against what agents
actually experienced, so a round of play is a flag on the next selection rather
than an edit. Ridge, because seven collinear features on fifty levels otherwise
hand back large coefficients that cancel.
Most of the work here is in refusing to produce a number. The response averages
the difficulty rating with two continuous measures of the same thing, and a
component with no spread is dropped by name instead of being silently averaged
in as zeros — which is what the first version did, reporting R² = 0.75 for a
fit that never saw a rating. When the rating is flat the script now says so and
says what the remaining fit is really measuring, which is board size.
legibility() asks what it costs to know what you may tap: how far the eye must
follow the cheapest free piece, and how many pieces are held by somebody their
line never touches. It was built to explain why ratings came back flat. It does
not — it correlates -0.02 with them — so it stays a measurement, printed on the
review sheet beside the longest route on the board, and gates nothing.
prototype-d5 takes --seedFrom so a wide sweep shards across cores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening the c50 edition threw `campaign.legacyStorageKeys is not iterable`
and left the board blank — the header still drew, so the level set looked
loaded. Every other edition carries the array; the hand-written c50 entry
did not, and 197 passing tests had nothing to say about it, because nothing
tested the registration itself.
The test now walks every edition and asserts the shape app.js relies on,
including that the chapter list can actually hold the levels. It fails on the
entry as it shipped.
The task board was reading levels/prototypes/d5.json while the campaign sat in
levels/campaign-50.json, so it showed "候选关卡 18 / 50" with fifty levels
built. It reads the shipping set when there is one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The deep-lock construction cannot go below three turns, so it cannot supply an
opening band — every level it makes lands mid-difficulty and the curve comes
out flat. harvest-easy pulls the shallow end from the shipped v0.5 set, but
only the 3% of it that holds up: 568 of 1000 need no rotation at all and 51
more are clearable by tapping at random, which is what the monkey threshold is
for. 33 levels survive, at one and two turns.
select-50 scores candidates on mechanical features only, spreads the pick
evenly across that range, and caps any one route grammar at 36% so the campaign
cannot quietly become one template repeated — the shipped 1000 turned out to be
thirty. Weights are a flag, so refitting them from a round of agent ratings is
a re-run rather than an edit.
The result is a curve rather than a plateau: difficulty score 3.85 / 6.24 /
7.60 across the three bands, rotations 1.8 / 3.0 / 3.5, and every level keeps
three or more opening choices with a show of at least a second.
The 50 register as a playable campaign edition, so review happens in the real
game; docs/review-50.html is the handover sheet, one card per level with the
opening position drawn from the solver rather than sketched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
The chain built its own worst moment: hero and tail deadlock at orientations 0
and +2 and open only at +1, with +3/+4/+5 left entirely to chance. That is why
some levels released at exactly one of six orientations, and why every model
kept writing "I don't know which way to turn" — at +2 one direction was right
and the other five were dead ends.
createDeepLock now traces candidates at all six orientations and requires the
tail to also release at +3, certified with the real swept tracer. A player
stuck at +2 turns either way and something moves. Measured by the rule bot
over the regenerated set: blind rotations, wrong-way turns and extra rotations
all go to zero, from 1.17 / 0.44 / 1.22 per level. Endgame orientation counts
shift from {1:4, 2:10, 3:4} to {2:3, 3:13, 4:1}.
It costs yield: switchback r3 and crossing r3 now produce nothing, and the set
comes out at 17 levels rather than 18. The test witnesses moved with it — a
witness is whichever seed still satisfies the construction, so they are
re-derived from the sweep rather than pinned.
docs/level-board.html is the task board for getting to 50 levels. Task state is
in bench/board.json; every number on the page — candidate count, rotation
quota, blind turns, monkey actions-per-piece, the agent batches — is measured
from the level set and the run archive at build time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regenerating levels/prototypes/d5.json renumbers every level, so the previous
build — which resolved runs through the current set's ordinals — silently
dropped every run played against an earlier set. The report I committed one
commit ago was built that way: 130 runs instead of 229, and the test-retest
table down to 7 paired levels from 18.
Levels are now keyed by id throughout. Runs carry their own level, so ids the
current set no longer contains are read back out of the archive and given an
ordinal after the live ones; the cross-model table intersects on ids rather
than assuming two sets number the same level alike.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every model that played the D5 set complained about the same moment: two
pieces left, interlocked, and nothing on the board saying which way to turn.
codex-spark took it to the end — it cycled all six orientations on L18 and
gave up on a level that was solvable, with clarity rated 5. Replaying the
position shows exactly one of the six orientations releases anything.
deadTurnAudit now reports endgameMinOpen: across reachable states with three
pieces or fewer left, the fewest orientations that release anything. It is
computed inside the walk that was already happening, so it costs nothing. The
generator requires at least two.
The gate picks out precisely the four levels every model stalled on, with no
false positives — the other fourteen already had two or three. Regenerating
drops the spiral family entirely: its structure produces single-exit endgames
by construction, not by unlucky seed.
Measured on the regenerated set, rule bot first and then grok-4.6 xhigh over
all 18:
blind rotations 1.17 -> 0.29 per level
extra rotations 1.22 -> 0.35
dead taps 2 -> 0
run time 55.5s -> 34.7s
stuck rating 1.89 -> 1.41 (this metric repeats at r = 0.85)
"which way do I turn" complaints 22% -> 12% of levels
The gate is a floor, not a difficulty dial: difficulty also fell, 2.39 -> 2.12,
because the levels it removes are the hard ones. And it treats a symptom — the
t0/t3 interlock is what the four-piece chain in createDeepLock builds by
construction, so the complaint is halved rather than gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One batch of 18 runs was recorded as codex-spark at xhigh but had actually run
as gpt-6-astra at low effort: the CLI config was rewritten between the check
and the batch, and codex resolved the new model silently. The archives are
relabelled from the CLI's own startup header rather than from what the agent
was told to call itself, and the report now says to trust that header over the
self-reported name. The re-run verifies the resolved model after every level
and aborts the batch on a mismatch.
With the real codex-spark batch added, five independent runs of four models
from three vendors now cover the same 18 levels on the fixed board, and 16 of
17 comparable levels agree exactly on the rotation triple.
codex-spark gave up on L18 after cycling all six orientations, reporting no
exit anywhere — replaying the position shows orientation 1 releases t0, and
that it is the only one of the six that does. The level is solvable and the
routes are legible (it rated clarity 5), but nothing on the board says which
way to turn: a 1-in-6 guess with a dead end behind every other door. That is
the terminal-interlock complaint the other models kept raising, at its limit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the kimi-k3 and codex-spark batches, so the rotation counters are now
compared across four independent runs of three models from three vendors on
the fixed board: 17 of 18 levels agree exactly, triple for triple.
The clean test-retest (same model, same harness, same board, run twice) also
corrects two earlier readings taken from data that had a board-format change
mixed in. Difficulty ratings reproduce at r = 0.93 rather than 0.63 — the
earlier low figure was the format change, not the metric — so an agent can be
trusted to rank 1000 levels by difficulty even though its absolute scale may
be offset. And the mechanical counters are highly reproducible rather than
deterministic: 0.82-0.92, not the 1.00 the confounded pair suggested.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
design-review.html is the gameplay and UI read on the shipped campaign, with
boards rendered from the repo's own geometry rather than redrawn.
bench-reliability.html asks which of the bench's numbers are worth acting on,
and answers it by running the same 18 levels twice under identical conditions.
The mechanical rotation counters reproduce (r = 0.82-0.92) and three different
models agree on them level for level, so they measure the level rather than
the player — a 12-second rule-bot sweep gets the same answer as hours of
model time. Difficulty and stuck ratings also hold up (r = 0.93 / 0.85), but
the ratings for rotation, clarity and fun do not (0.32 / 0.19 / 0.56): asking
a model whether rotation matters is noise, while counting blind rotations is
not.
Every figure is computed from bench/runs by build-bench-report.mjs; nothing in
the page is hand-written, so it can be regenerated as runs accumulate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Four ways in, depending on what you have: a shell agent driving bench.mjs, a
direct-API loop for models with only an endpoint (plain text in, one JSON line
out, so weak and local models can play), a rule-following bot that sweeps 1000
levels in 12 seconds, and a folder you can hand to someone else.
bench-kit.mjs builds that folder: CLI, engine, level pack, observer page and
tile atlas, about 1.5 MB, needing only Node 20 — no repo, no install, no
network. The engine files are discovered by following imports so a new module
can't be left behind, every path rewrite is asserted so a rename fails the
build loudly, and the builder plays a real game inside the result before
declaring it done.
bench-report.mjs aggregates two ways: per level (is this level any good) and
per model (how does this agent play). bench-import.mjs brings runs played
elsewhere back in under a tag.
bench-pack.mjs is the earlier, smaller export with no observer page; superseded
by bench-kit but kept in history rather than dropped silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
Run archives, the aggregated report and the packaged standalone kit are all
regenerated from scripts; bench/runs alone is 16 MB across 1140 files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add the paper direction with thin tracks, outlined hexagons and line arrows, without exterior color markers or piece numbers. Add a unified catalog for five designs and their 25 themes, with direct review links.
Organize each design into an authored design.md, generated tokens.json and preview. Add Agent routing, shared integration guidance, a machine-readable manifest and an executable rendering example. Preserve legacy preview and document links.
Validation: 16 focused design, theme and package tests passed; npm run design and static build passed. Confirmed all eight authored documents survive generation unchanged. Browser checks covered mobile, tablet and desktop layouts, theme links and migrated previews. Native engine integration and visual verification remain pending.
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.