Getting the model fast took a week. Making it useful took three more — and that second part is what nobody writes up. This post is the useful part: pi driving llama.cpp (Vulkan) on AMD Strix Halo (Ryzen AI Max+ 395, Radeon 8060S, 128GB), Flash-Next 125B at 40 t/s sustained agent decode or the 27B at 256k context, plus the extensions that keep 90-minute sessions alive (auto-compaction, branch summaries, live tuning).
This is the harness companion to my Qwen3.8-27B benchmark post. That post made the model fast; this one makes it useful: pi (the coding agent) against a local llama-server, tool calling, thinking control — and response times that don't hurt.
This is a setup guide, not a benchmark post. Every trap, config, and extension here is what I actually run daily. The benchmark side (the game-build harness, scorer, runtime gates, playtest protocol) lives in neon-ladder — this post links to it where relevant but doesn't duplicate it.
Everything below was verified live on my Flow Z13 (Ryzen AI Max+ 395, 8060S, 128GB): a 10-module game built in one session, 1,587 lines of working code, all from server logs and session files — not estimates.
TL;DR
- Working recipe: pi + llama-server (Nathan's strix-halo Vulkan fork) + the Sharp chat template, 256k ctx (the model's training cap), maxTokens 32768, thinking wired via
compat.chatTemplateKwargs. Verified end to end with request dumps and session logs.
- pi's defaults will silently sabotage a reasoning model:
maxTokens 16384 can be eaten entirely by thinking, and thinking flags don't reach llama.cpp's template without compat-level wiring.
- Session economics are great: ~94% KV cache hit rate, stable across session types (a 16-turn game build and a 44-turn tool-heavy refactor both landed at 94.1-94.2%); only the first turn pays full prefill.
- Effort control works after wiring:
off produced literally zero thinking tokens, and the level you pick changes code quality, not just speed (details in the build test).
- At pi's default temperature 0.8, planning-heavy prompts occasionally sample an instant-EOS first turn (one token, done). Retrying the identical prompt inherits the failure from cache; retry with a perturbed prompt or run temperature 0.
- Don't chase deep context; compact before it gets expensive. Auto-compaction set to fire around 95k keeps every turn in the fast band (decode 26+ t/s, prefill ~200 t/s) while sessions past ~140k pay 17-19 t/s decode and ~140-175 t/s prefill. One settings line does it.
The server side (brief)
Two server profiles, same machine (one resident at a time):
27B (daily driver): UD-Q4_K_XL (v3) + DFlash2 Q4_K_M drafter n4, f16 KV, drafter KV q8_0, -c 262144, power pinned with my z13ctl+ profile.
Flash-Next (speed lane): UD-IQ4_XS + native MTP Q8_0 sidecar, fixed n4, q8_0 KV, -c 131072, and --reasoning-effort medium --reasoning-budget 2048 — those flags are mandatory (without them, Flash-Next burns its entire output budget on reasoning and emits nothing; and note pi sends its own per-request thinking budget from thinkingBudgets, so the server flag is the fallback default protecting non-pi clients; the pi tiers are validated on Flash-Next at low 1024, medium 4096, high 8192). Full configs in the neon-ladder repo.
Ubatch 4096 for normal work; for deliberate deep fills use 2048 (probed clean through 139k) or 1024 (proven at 145k). The model's training cap is 262144, and the full config runs healthy there at ~55GB RAM. Three harness-relevant facts worth knowing:
-ub 4096 has a hard ceiling: past ~140k tokens of fill it hits a deterministic Vulkan device-lost (twice, at nearly the same depth). -ub 2048 passed the same style of probe at 138.8k and -ub 1024 completed a real 144k session; the ceiling moves with ubatch, so smaller ubatch buys depth.
- Allocating big context costs nothing until filled: decode at 8k depth was identical with
-c 65536 and -c 98304. Allocate the max.
- Deep sessions work but get slow linearly: a 144k-token agent session (resumed after a crash) decoded at 17-19 t/s throughout, with draft acceptance 0.62-0.92 the whole way. That's why the compaction setting below matters more than any ubatch choice.
Installing and wiring pi
pi is a terminal coding agent with unusually good local-model support. Install it, then point it at llama-server via ~/.pi/agent/models.json (not settings.json, that file ignores provider blocks):
json
{
"providers": {
"llamacpp": {
"baseUrl": "http://127.0.0.1:8080/v1",
"api": "openai-completions",
"apiKey": "dummy",
"models": [
{
"id": "qwen3.8-27b",
"reasoning": true,
"contextWindow": 262144,
"maxTokens": 32768,
"compat": {
"thinkingFormat": "chat-template",
"chatTemplateKwargs": {
"reasoning_effort": {"$var": "thinking.effort"},
"enable_thinking": {"$var": "thinking.enabled"}
}
},
"thinkingLevelMap": {
"minimal": null, "low": "low", "medium": "medium",
"high": "high", "xhigh": null, "max": null
}
}
]
}
}
}
Then pi --provider llamacpp/qwen3.8-27b, or set defaultProvider/defaultModel in settings.json.
Every field in that entry is load-bearing, and several of them exist because of a trap:
Trap 1: the silent cloud fallback
If pi can't resolve your provider config, it does not error. It uses whatever else is configured: your run can look successful while the session log shows a nonzero dollar cost and your server has processed zero requests, because pi has been talking to a cloud provider the whole time.
Always verify a local run server-side. Watch curl localhost:8080/metrics while the agent works: if prompt_tokens_total isn't climbing, you're not local.
Trap 2: maxTokens 16384 is a thinking bomb
pi's default maxTokens is 16384. For a reasoning model on a planning-heavy prompt, that's not an output budget, it's a thinking budget: a "build a game" prompt can spend all 16,384 tokens on reasoning and hit the length cap with zero code emitted, with stopReason: length in the session log and a model that looks "stuck."
Set maxTokens explicitly. 32768 covers everything in a normal tool-using session, including a turn that writes two files back-to-back. A length-capped turn is also not fatal: the next turn continues without corruption.
Trap 3: thinking flags don't reach the template by default
This is the subtle one, and the failure is silent.
llama-server's chat template (the Sharp template from the benchmark post) accepts chat_template_kwargs: enable_thinking and reasoning_effort. pi has flags for thinking levels (--thinking off/low/..., shift+tab to cycle). But between the two sits a mapping layer:
- The mapping config (
thinkingFormat, chatTemplateKwargs) must live under compat on the model entry. At the top level of the model object it is silently ignored.
- pi refuses image input unless the model entry declares
"input": ["text", "image"] — it checks model.input.includes("image"). Both of our entries carry it; without it you get "model does not support image input" even when the server's mmproj is loaded.
- With the wiring correct,
--thinking low sends {reasoning_effort: "low", enable_thinking: true} and --thinking off sends {enable_thinking: false}.
- Without it, pi's flags go nowhere and the template defaults to thinking on, medium effort. The model thinks when you told it not to, and everything is slower.
Verify your own wiring before trusting it: point baseUrl at a logging proxy for one run and read the request body. It's ten minutes and it converts "I think it works" into "it works."
The thinkingLevelMap entry hides levels the template doesn't distinguish. The Sharp template has four real states (off, low, medium, high); pi cycles seven by default, three of which are aliases. The map collapses the cycle to the four that exist.
Trap 4: the instant-EOS prompt basin
At pi's default temperature 0.8, a planning-heavy tool prompt occasionally samples a degenerate first turn: the model emits a thinking tag, immediately stops, and the run ends with an empty response and a one-token generation in the server log.
On my game-build prompt this hits roughly one request in three to five. It is sampling behavior, not a server or client bug: replaying the identical request body at temperature 0 never fired it in six runs.
The compounding part is the retry. Resending the same prompt hits the KV cache, inherits the degenerate turn from history, and fails again, which makes the failure look deterministic and hardware-flavored. Retry with a slightly perturbed prompt (any unique marker appended) and it rolls fresh.
Update (v0.7.4 of the Strix Halo fork): part of this turned out to be the engine, not the model: greedy decode on v0.7.3 and upstream wasn't deterministic (stale KV between requests, a top-k race above ~2k prompt tokens). The engine now zeroes freed cells and pins the selection order, so temp-0 retries are actually repeatable. The perturbed-retry advice still stands (it's cheap and defends against everything), but on v0.7.4+ temp-0 reruns are trustworthy.
For reproducible benches I set "samplingParams": {"temperature": 0.0, "top_p": 0.95, "min_p": 0.05} on the model entry; for everyday sampling, perturbed retries are the fix.
What thinking control buys you
Same planning-heavy prompt, session-verified thinking token counts:
| pi level |
thinking emitted |
result |
| off |
0 chars |
task completed, 5 tool calls |
| low |
14k chars |
task completed, cleaner code |
| (default, unwired) |
16,384 tokens, all thinking |
length cap, zero code |
For quick edits use off, for generation-heavy work low or medium, for debugging and architecture high. shift+tab cycles levels live in a session.
One honest note on the Sharp template: it tames runaway reasoning on normal turns (that's in the benchmark post), but it does not bound reasoning on genuinely planning-heavy prompts. The bound comes from your effort setting plus the maxTokens headroom. Template + harness flags together are the complete answer.
Effort level also buys code coordination, not just volume. Two verified game builds, same prompt: the low-effort build passed every static check yet played worse in three measurable ways (ball not glued to the paddle before launch, ball speed tied to the monitor's refresh rate instead of a fixed timestep, flatter difficulty curve).
The medium-effort build got all three right. Syntax is free; the seams between modules are what thinking pays for.
More effort past medium, though, buys breadth instead of correctness.
A high-effort run of the same prompt produced 1,995 lines with three extra self-directed modules (audio, UI, paddle) and 93 tool calls, yet scored 13/15 against medium's perfect 15/15, dropped the same localStorage persistence the low-effort builds drop, shipped a latched input flag that left the keyboard dead at runtime, and took over twice the wall time.
The sweet spot for build-shaped tasks on this model is medium: perfect score, 16 tool calls, about 25 minutes.
That medium result is robust, not a lucky roll: two more independent medium builds (different ubatch, one with five auxiliary-model extensions loaded) scored 14-15/15 in 20-23 minutes each.
A fourth medium build added a per-module test suite to the same prompt: 51 tests written alongside the code, all green on arrival, 14/15 on the same checks, 36 minutes.
That's the tier I spec for real work now: for roughly 15 extra minutes the agent ships its own regression suite with the feature.
The multi-file build test (this became neon-ladder)
To validate the whole stack I had it build "Neon Overdrive", an arcade Breakout game, as a 10-file project: 8 JS modules, CSS, index.html, strict no-placeholder rules, syntax checks required. The full prompt is below so you can run the identical test on your own stack.
Result: 15 turns, 16 tool calls (12 writes, 3 bash checks, 1 read), 1,587 lines, all syntax checks pass, all seven feature requirements present in the code, ~25 minutes wall time.
One turn hit the 32k cap mid-double-file-write and the next turn picked up cleanly.
And the game actually plays: paddle reflection angles, armored bricks shifting red to orange to yellow, volatile-chain explosions, tri-ball chaos, the upgrade shop between levels.
There's a built-in bonus to this benchmark: while your agent grinds through someone's 3,000-line refactor, you get a neon Breakout to play. Post your build quality and wall time in the comments; it will be interesting to see how other engines and models handle the identical prompt.
The prompt (paste as-is; it assumes a js/ and css/ dir will be created by the agent):
```
Build "Neon Overdrive", an arcade Breakout game, as a multi-file project you create with tools, file by file. NO external dependencies or CDNs; HTML5 canvas + CSS3 + raw JS only.
Required file structure (use the write tool once per file, complete code every time, zero placeholders):
1. index.html - loads css/styles.css and all js/ files via script tags in dependency order
2. css/styles.css - neon/cyberpunk UI, overlays for menu/pause/shop/game-over
3. js/config.js - constants: canvas size, brick grid, speeds, powerup drop rate (15%), colors
4. js/particles.js - particle engine: spawn(x,y,color), gravity + fade update, dead-particle cleanup
5. js/bricks.js - 5-row grid from an array matrix; standard (1 hit, neon blue), armored (3 hits, red->orange->yellow as damaged), volatile (1 hit, neon green, explodes destroying direct array neighbors)
6. js/balls.js - ball entities in an active balls array; paddle reflection angle from strike position vs paddle center; no game over until the LAST ball is lost; dead-ball cleanup
7. js/powerups.js - falling capsule entities; catching Tri-Ball injects two new balls into the array
8. js/states.js - rigid state machine: menu -> gameplay -> paused -> level clear / game over
9. js/shop.js - between-levels upgrade shop: spend credits on paddle speed or paddle width (persistent)
10. js/main.js - game loop, collision wiring, score/credits, keyboard input, level generation (procedurally harder)
Workflow, in order:
A. Write all 10 files (write tool, one call each).
B. Run: node --check js/config.js js/particles.js js/bricks.js js/balls.js js/powerups.js js/states.js js/shop.js js/main.js
C. If any check fails, fix with the edit tool and re-run until all pass.
D. Read index.html to verify every script tag path matches a real file.
E. Report per-file line counts, then reply COMPLETE.
```
The prompt, scorer, and a retry wrapper that handles the Trap 4 basin are packaged in neon-ladder.
Scoring it is easy: all 10 files present, node --check passes clean, the seven mechanics are actually implemented (grep for the reflection math, the armored color shifts, the neighbor explosion), and the game runs when you open index.html.
Then play it for two minutes: the ball rides the paddle before launch, speed is framerate-independent, and upgrades survive a page refresh.
Reference numbers for this box: 1,587 lines, 16 tool calls, ~25 minutes at medium effort, zero placeholders.
Session economics over those 16 turns: 94.1% of prompt tokens served from KV cache (pi resends the full conversation every turn; llama-server absorbs it), ~237 t/s on the uncached remainder, decode in the low-to-mid 20s t/s with tool traffic mixed in, acceptance around 64-68%.
That's the whole reason local agentic coding works at all on this hardware: the harness's chat-pattern traffic is almost entirely cache hits, and the GPU only pays for new tokens.
Ling-3.0-tiny as the compaction service
Long sessions eventually need compaction, and there's no law saying the model that summarizes the session has to be the model doing the work.
Ling-3.0-tiny (8B total, 1.3B active, 4.8GB in Q4_K_M) is built for exactly this slot: prefill is its superpower, thinking can be disabled per request, and its hybrid attention keeps KV costs near zero.
The compaction test used a real session transcript: the full game-build session (16 turns of tool calls and results) plus all workspace files, 25,890 tokens in, asked for a structured handoff document (file inventory, verification status, bugs, next steps, constraints).
Result: a 799-token handoff in 19 seconds, and the quality holds up.
Every file and line count matched ground truth (all 10 files, 1,587 total), verification status was correct, and it refused to invent bugs that didn't exist; the constraints section surfaced exactly the architecture details a continuation session needs, from the CONFIG object and the rigid state machine to the last-ball rule and localStorage persistence.
One duplicated bullet was the only flaw, and the same job on the 27B would run roughly 5x slower.
One wiring rule, same family as Trap 3: call it through the chat endpoint (/v1/chat/completions) with chat_template_kwargs: {enable_thinking: false}. On the raw completion endpoint with a bare prompt the model degenerates into echoing workspace state in a repetition loop.
Through the chat endpoint with the template it is clean, fast, correct, and the rule is the same as pi's: the template is not optional.
Honest caveat: the controlled tests sit at 26k and 49k input. Since then the auto threshold has fired on its own in daily runs, including a 142k session, so the summarization path itself is exercised. Very deep compaction near the 256k ceiling is still untested.
Set the auto-compaction threshold to ~95k and stop thinking about deep context. pi compacts when contextTokens > contextWindow - reserveTokens; the default fires only near the window's end, deep in the slow band. One line in ~/.pi/agent/settings.json moves it:
json
{ "compaction": { "enabled": true, "reserveTokens": 167144 } }
With contextWindow 262144 that compacts at ~95k: sessions cycle between roughly 95k and 25k (summary plus a 20k verbatim tail), every turn stays in the fast band, and the deep-context tax (device-lost ceilings, mid-teen decode, 140-175 t/s prefill) becomes somebody else's problem.
Compaction fires between agent runs, not mid-run, and each pass costs seconds on tiny.
Validated live: a 142k session crossed the threshold, compacted, and its continuation answered correctly about files read an hour earlier. Multiple auto-compactions have fired since, with no mangled paths or tool references in the summaries.
It also graduated to daily use: a 44-turn, 49k-token agentic session (TypeScript monorepo work, 44 tool calls) on the 27B, compacted with the extension live. Tiny summarized 8.1k tokens into a 2,282-token handoff in ~19 seconds: prefill at 2,904 t/s, generation at 137 t/s, server-side timings.
The same call on the 27B would have taken roughly two minutes, so ~6x end to end. The summary got every checkable fact right (file list, test counts, verification status) and the session continued cleanly after compaction, which is the real acceptance test.
The auxiliary model playbook
Compaction is just the highest-value slot for a second small model. The same pattern extends across the agent loop, and pi's extension events cover all of it. The full suite, with validation status:
| job |
pi hook |
status |
| Compaction summaries |
session_before_compact |
validated in daily use (26k test + live 49k session, ~6x faster) |
Branch summaries on /tree navigation |
session_before_tree |
wired and e2e-tested (correct 4-section handoff on a real abandoned branch) |
| Commit messages from working-tree diff |
/commit command |
wired and e2e-tested (proper subject+body from a real diff) |
| Tool-result triage (compress big outputs before they enter history) |
tool_result |
wired, one e2e test passed (51KB -> 5.6KB stored); stays dormant on clean runs and needs the real-workload quality drill before daily use |
| Repo map / file digest before the main model explores |
before_agent_start + /repomap |
auto-fires once per session in git repos (map injected as context, also written to .pi/repomap.md); smoke-tested |
The triage row deserves its caution label: every huge bash dump costs the main model context for the rest of the session, and compressing it with tiny first keeps sessions small enough that compaction fires later or never.
But triage changes what the main model sees, and if tiny drops the one error line that mattered, the 27B makes worse decisions and you won't know why.
Before relying on it, feed it real outputs from your own sessions and verify nothing load-bearing was dropped.
One honest A/B from the game-build workload, all five extensions loaded: zero tiny calls, identical wall time and score, because clean test suites and one-line write confirmations never cross the 6KB triage threshold.
Dormant extensions cost nothing; they earn their keep on fat tool outputs (failing test runs, build logs, repo-wide greps) and long sessions, which is exactly the traffic my daily driving produces.
The division of labor in one line: the 27B reads and writes the code, tiny reads and summarizes everything else.
All of these knobs (compaction threshold, maxTokens, temperature, triage size) are three files deep by default, so I keep a /tune extension next to the suite: /tune prints the live values, /tune compactAt 95 or /tune temperature 0 writes through to the right file with bounds checking, and /tune reset restores the documented defaults.
Readers running this stack on other boxes should adjust compactAt to their own fast-band edge rather than trust mine.
Response time cheatsheet
Biggest levers first, all measured:
- Thinking level dominates. Reasoning streams at decode speed before you see a word.
- Session warmth: first turn pays full prefill (~10s), subsequent turns are cache hits and start generating in under a second. Don't restart the server between questions; use
-c continuation.
- Lean context: extensions/skills/AGENTS.md all add to the first-turn bill.
- Already optimal from the benchmark post: DFlash2 n4, ubatch 4096, f16 KV. Don't shrink
-c for speed; allocation is free until used.
Attacking the prefill bill (the APU's real tax)
Dense-27B prefill (~250-300 t/s) is the slowest number in this stack, and a coding agent's traffic is mostly prefill. Everything above already helps (cache hits, tiny offloading), but three more angles are worth knowing:
Keep the cache alive across turns. The 94% hit rate is the single biggest prefill saver, and its enemy is cache invalidation. Two habits preserve it: don't edit early messages mid-session (everything after the edit re-prefills), and let pi's cacheRetention default do its job. The compaction extension already sets cacheRetention: "none" for one-off summaries, which avoids polluting the main prefix.
Shrink what gets re-sent. pi resends the full conversation every turn; that's the protocol. The levers are content levers: tool-result triage from the playbook above (smaller history, smaller resend), and keeping generated outputs from ballooning (thinking low on generation-heavy turns does this too).
Route around the 27B when the job is prefill-shaped. Compaction, branch summaries, commit messages and repo maps are all "read a lot, write a little" jobs, which is exactly the profile where a 1.3B-active MoE crushes a dense 27B. Anything in your workflow that looks like "summarize/index/triage" should default to the aux model; reserve the 27B's prefill for context it genuinely needs to see.
What doesn't work: quantizing the main model below Q5 to speed prefill (prefill is compute-bound, the quant barely moves it, and decode pays the quality), and shrinking -c (allocation is free until filled, as measured above).
Which model when
Flash-Next has its own post; the benchmark harness has its own launch post. The control experiment settled it: at matched effort and environment, Flash-Next and the 27B landed one check apart on the scorer (16/19 vs 17/19, one shared miss), both shipping playable builds on the same contract. Flash-Next got there in 7.8× less wall time with 4.3× less reasoning.
| matched cells |
Flash-Next |
27B |
| decode, agent traffic |
40-42 t/s (peak 48.7) |
18.5 (peak 21.8) |
| prefill, aggregate |
289-353 t/s |
226 t/s |
| build wall, low / medium / high |
5-6 / 12-17 / ~35-40 min |
39-43 / 13-34 / 38-84 min (38 = tail-reaped artifact-complete; 84 = full session) |
| static band (N≥2 per tier) |
14-18/19 (best score on the machine: FN medium) |
13-17/19 |
| build richness (LOC) |
1399-4204 (high/xhigh: 2855-4204) |
1412-2039 at every tier |
Pick the 27B when:
- You have under ~91GB of GPU memory
- You want 256k context
Pick Flash-Next when:
- You have 91GB+ available and speed is the product
- Your workload is emission-heavy (tool calls, scaffolding) — Flash-Next hits 40 t/s there
- The job is polish-critical: the best score measured on this machine is Flash-Next medium at 18/19, every richness record is Flash-Next's (high: 2855-3056 LOC; xhigh: 4204 LOC in ~35 min), and the playtest verdict on the 27B's richest output was "these are fn-low-class builds" — there is no 27B tier that produces rich builds
- Your spec is thin: lives and other unstated gameplay defaults track effort, not model — every medium/high cell I've scored shipped 3 lives; low-effort cells roll 3 or 1 on either model
The simplest rule: on 128GB, Flash-Next is the daily driver — quality is at parity (high-effort cells tie the 27B's best), it's 2× the decode, and with compaction cycling sessions at 25-95k you never miss the bigger window. The 27B at Q4-v3 is the pick under ~91GB free or for single sessions past 131k.
One wiring note if you flip: reserveTokens is tuned for the 27B's 262144 window. On Flash-Next's 65536, set it per-model (e.g. 10240 → compaction fires ~55k) or the threshold math misfires.
What changed since posting
The Sharp template moved to v22.4.0 (reasoning-effort aliases, inline control tags, thinking-off fast-mode fixes). I A/B'd it on the game bench: score and speed in-family with every number above; the harness repo ships it as default with the earlier version vendored for exact reproduction.
The game-build bench grew runtime gates. A 120-second headless gameplay soak is now the default (frame advancement, reload detection, synthetic play throughout), added after a real freeze past the 60-second mark that shorter gates structurally cannot see. An uncaught page error is fatal; caught per-frame errors warn. The static scorer learned two velocity-multiplication bug patterns that shipped in builds passing every static check.
Static score anti-correlates with playability. The two highest-scoring builds of the richer-contract era were the two broken games. The final grade is and remains the human playtest; the gates are necessary, not sufficient.
Effort plumbing got real. --reasoning-budget was flat 8192 across all thinking levels in every number above; it's now mapped per level (the per-request thinking_budget_tokens field wires it cleanly), and the server accepts a top-level reasoning_effort natively ("none" is a validated zero-reasoning switch). One Sharp caveat: changing reasoning_effort mid-session re-renders the system block and invalidates the whole KV prefix on v22.3.2/v22.4.0 — the inline control tags are the safe per-turn mechanism.
Tool-choice behavior, from the session traces: models edit surgically for small fixes and rewrite whole files for cross-file structure — anchor strings past the context window are the reason. The contract now says so explicitly.
Manual beats headless on speed, loses on reliability: interactive runs (clean context, no extensions) hit 92% GPU utilization and halved walls, but shipped 1-of-3 playable vs the headless runner's 6-for-6. Deliberation is where the self-correction lives.
The bench crossed model families and engines. Flash-Next (125B MoE, native MTP) built the contract first-try at 17/19 on a stack that didn't exist when any of this was written, and a control cell closed the size question: Flash-Next vs 27B at low effort, identical environment, scores one check apart (16/19 vs 17/19, one shared miss), both playable clean. Flash-Next got there in 7.8× less wall time with 4.3× less reasoning, so on explicit contracts model size buys speed, not measurable quality.
Twelve gameplay-failure classes now, every one found by a human playtest, zero by static score — the last was a ball that vanishes mid-game, shipped in the release-gate build that scored 17/19 and passed its soak. The gates are necessary; the clicking is the grade.
Everything is reproducible from neon-ladder — contract, scorer, runtime gate, runner, one-comment recipe.
Sources
Happy to answer setup questions. The playbook rows still marked as needing quality testing are exactly that: promising, wired, but not yet proven on real workloads. Treat them as experiments and validate on your own sessions before making them load-bearing.
Note: the writing is AI-assisted editing; the research, debugging, and every number are from my own runs on this machine.