Design Tournament
blind-critic answers "did we hit the intent?" It needs a reference. This skill
produces one when none exists, using the same comparison mechanism for a different
job: "which intent is best?"
Both sides of every comparison are generated here, which makes this more blindable than conformance — there is no authentic-versus-imitation signal for the critic to detect and defer to.
Phase 0 — Interrogate
You do this with the human. Do not automate it. Automating the interrogation of someone's requirements defeats its entire purpose: every question extracts information that exists nowhere in the codebase.
One question at a time. Recommend an answer to each. Look up anything discoverable from the filesystem rather than asking.
Terminate in a written split, not prose:
{
"surface": "checkride-readiness",
"constraints": [
"must show eligibility status without interaction",
"instructors manage 20+ students; per-student drill-down is secondary"
],
"axes": [
{ "name": "disclosure", "options": ["everything visible", "summary then drill-in"] },
{ "name": "grouping", "options": ["by student", "by deadline"] }
]
}
Constraints are branches the human decided — every candidate must honour them. Axes are branches they could not decide — the tournament resolves them.
If phase 0 yields more than four axes it has not converged. Push for more decisions rather than generating a larger field; an unresolved question is cheaper to answer in conversation than in ten screenshot comparisons.
Phase 1 — Diverge
N = axes + 2. One candidate per axis, plus a baseline, plus a real-world seed.
| Candidate | Varies | Purpose |
|---|---|---|
baseline |
nothing | the control every axis is measured against |
axis-<name> |
only that axis, all else at baseline | isolates one decision |
seed-<source> |
modelled on a real shipped product | anchors the field outside the model's own distribution |
Each candidate is a standalone HTML file under
design/prototype/<surface>/candidates/, rendered by the same pipeline as every
other reference. Invoke frontend-design to generate them.
Vary one axis per candidate. Candidates that differ on several axes at once make the tournament merely selective — you learn "v3 won" and cannot say which decision earned it, so the result transfers to nothing. One axis per candidate makes it attributive: a win is an answer to a specific open question.
The seed candidate matters more than it looks. Every other entrant comes from one model and inherits its blind spots; a tournament can only find the best of what it generated. Seeding from something a human shipped is the only entrant that can reveal the whole field is mediocre.
Phase 2 — Round-robin
cd apps/web && TOURNAMENT_MANIFEST=design/prototype/<surface>/tournament.json \
pnpm critic:tournament
Emits one directory per pair under .rounds/tournament-<surface>/, plus key.json.
A planted loser — the first candidate with its vertical rhythm degraded — is added
automatically and plays everyone.
Dispatch one blind subagent per pair, concurrently, using the prompt and dimension
table from blind-critic/SKILL.md and lenses/visual.md. Each agent gets two image
paths and nothing else. Never read key.json before dispatching.
Collect verdicts as [{ "dir": "...", "winner": "A"|"B"|"tie" }], then:
node e2e/critic/tournament-tally.mjs \
e2e/critic/.rounds/tournament-<surface> verdicts.json
The tally gates in order:
- Planted-loser check. A degraded entrant must lose every pairing. One win and the run is not measuring quality — discard the standings, do not promote.
- Transitivity. ≥2 three-cycles means the candidates are not separable on these dimensions. Return to phase 0 and sharpen the axes; do not crown whoever topped the table. A bracket would have hidden this by never running most pairings.
- Standings by win count — never by score. Across fifteen calibration runs the undegraded side scored 6 nearly every time; score has no usable dynamic range.
Phase 3 — Synthesize
Read the per-dimension findings, not just the winner. When axis-density won on
density while baseline won on hierarchy, graft them and run the graft against
the champion as one final pair.
This step also covers the known weakness of varying one axis at a time: interaction effects are invisible to a fractional design, and the graft is where a combination that beats both parents shows up.
Phase 4 — Promote
Winner moves to design/prototype/<surface>.html and gains an entry in
critic-map.json. From here the ordinary conformance loop applies and this skill is
done.
Record the axis decisions the tournament settled. That record is the durable output — more valuable than the HTML, because it transfers to every future surface.
Applying this to non-visual work
The mechanism is medium-agnostic; only the rendering changes. For API design,
generate N designs, render each as a normalised HTTP request/response transcript, and
compare on error-shape, naming coherence, pagination & idempotency,
discoverability — never on status codes or schema validity, which OpenAPI tooling
already decides.
The blinding objection that rules out API conformance — that generated and shipped code carry different fingerprints, so the critic infers which is real and defers to it — does not apply here, because in a tournament both sides are generated from the same brief.
Do not build a second lens until the first has cleared its gates.