JUDGE.md — the vision judge (the taste in the loop)

The feel gates (Channel 4) prove the floor mechanically: grace windows exist, events answer on two senses, the avatar out-contrasts the scenery, the camera doesn’t snap. But a game can pass every gate and still look empty, flat, or like a debug layout — the gap the game-director critique named “green tests, dead game.”

The vision judge closes that last mile. The engine renders headlessly to SVG; npm run judge rasterizes to PNG (via @resvg/resvg-js, zero browser); then a multimodal model looks at the actual pixels and scores them against the rubric below. It is the one judge that can’t be a pure function — so it’s the one an AI-first engine is uniquely built to run in a loop (deterministic sim + headless render make “render → look → fix → re-render” cheap and reproducible).

Real example: a game passed the salience and camera gates, yet the judge saw its parallax mountains were invisible (colored a shade off the sky, and anchored off-screen). The fix — a pinned, graded night sky with a moon and layered ridges — turned an empty void into an atmospheric ascent. No mechanical gate caught it.

The loop

npm run judge [slug…]        # render hero + motion PNGs → shots/<slug>/judge/
  → VIEW each PNG            # the model actually looks
  → score against the rubric # findings as { file, severity, fix }
  → apply high-severity fixes (cosmetic only — never touch world.state/logic)
  → npm run judge <slug>     # re-render, confirm the finding cleared
  → repeat until it looks shipped, not debug

Run it with the /judge skill (.claude/skills/judge/), which drives exactly this loop. Fixes must stay cosmetic — the judge changes how a game looks, never how it plays; the golden replay hash must be unchanged after a judge pass (view nodes are cosmetic, so it will be).

Rasterizer robustness. @resvg/resvg-js 2.6.2 has a bug: it panics (an uncatchable native abort) when an element that forces an isolation layer (opacity<1, filter, mask, clip-path) sits entirely off-canvas — the layer’s canvas-clipped region is empty and it unwraps None. scripts/svg-sanitize.mjs culls exactly those off-canvas layer-forcing elements before rasterizing — lossless, since an off-canvas element draws nothing — so all games render. Rasterization also runs in an isolated child process, so any other pathological SVG skips one image rather than aborting the run.

Fonts. scripts/judge-fonts.mjs feeds resvg a MINIMAL controlled set — one serif (the default family) plus a symbol font — with system fonts OFF. That renders text TRUE: with system fonts on, a run containing one exotic glyph (e.g. ) makes resvg reassign the whole line to a glyph-poor CJK font → the entire line turns to boxes; a small set keeps Latin in the serif and falls back per-glyph, so at worst a single decorative emoji shows as , never a whole line. Candidate font paths cover macOS and common Linux locations; if none exist it falls back to system fonts.

The rubric

Score each 1–5. A game is “shipped” when every axis is ≥ 4 and nothing is a 1–2.

  1. Readability — is the avatar instantly findable? Do threats read as dangerous, pickups as desirable? Is text legible over its background? (The salience + layout gates set the floor here; the judge checks it feels clear.)
  2. Depth & composition — is there a foreground / midground / background, or do objects float in a void? Does the frame have a focal point and breathing room, or is it empty / cramped / evenly-gray? Emptiness is the most common failure. A scene with a real light layer (LightLayer + PointLight) should read with depth — pools carve a focal point out of ambient darkness and shadows ground the geometry, not a flat scrim; the lit-scene feel-gate lightingIssues (npm run feel) OWNS lit-scene readability, since multiply lowers post-hoc palette contrast (the Regalia AA guarantee holds only PRE-lighting) — so judge lit contrast from the rendered PNG, never the hexes.
  3. Palette harmony — do the colors belong to one world (the Regalia set), or clash? Is there a controlled range of value (not all mid-tone)?
  4. Juice restraint — is feedback present but not noise? Particles/shake that punctuate, not smother. (In motion strips: do impacts read? is anything seizure-bright or frantic?)
  5. Motion clarity — across the motion strip, is the action legible frame to frame? Does the eye track the important thing, or is it visual soup?
  6. Chrome & finish — HUD framed and unobtrusive; margins consistent; no debug-square look; nothing clipped at the edges; the title/first screen invites.

Severity → action

What the judge does NOT do

It doesn’t judge fun (that’s playtest + the skill-delta proofs) and it doesn’t touch gameplay. It judges the image. Pair it with the feel gates (mechanics floor) and the winnability/determinism proofs (correctness) for the full picture: correct · fair · and it looks the part.

This module rendersthe repo's markdowndirectly — edit it there, it changes here.