Lab

Recorded runs. The rubric below is a sanity check, not the scoreboard — it was wrong seven times during this build, each time in the direction that looks like a discovery, and the model’s own critiques proved the more reliable read of its renders. Treat the scores as a floor and the critiques as the evidence.

Two comparisons are wired up and awaiting data. E1: does K3 actually use the pixels, or would a measured text description do as well? E2: does dropping reasoning_content from its history degrade it, as Moonshot warn but never quantified? Both need a full grid; neither is answered yet, and the panels below say so rather than implying otherwise.

10 runs over 1 tasks, 0 errored. Roughly $3.02 at Moonshot list price — DigitalOcean does not publish a per-token rate for kimi-k3, so every cost here is an upper bound. Confidence intervals are percentile bootstrap; p-values are two-sided permutation tests.

Per-arm summary

armnfinal rubric95% CIruns w/ regressionfalse “done”iterationsparse missesreasoning charscached incost
preserved/blind295.0%
0/250%3.00.001459043%$0.319
preserved/vision490.0%
0/425%3.00.001257158%$0.293
stripped/blind280.0%
1/250%3.00.001546045%$0.342
stripped/vision290.0%
0/20%3.00.001071716%$0.267

E1 — is the vision load-bearing?

Both arms preserve thinking history. The only difference is whether the feedback is three screenshots or a measured description of the same DOM — box geometry, computed colours including the resolved background behind each element, font size and weight, and the document’s scroll size. Measurements, never conclusions: the description names no defect, so both arms have to find the problems themselves.

Read the pixel-only comparison first, and treat the whole-rubric one as uninterpretable. Every DOM-derived check is computable from the geometry the blind arm receives — and the one that discriminated in practice, legible-text (no text under 12px), has its answer printed verbatim in that input as font 11.52px. The blind arm reads the number; the vision arm has to notice small type in a downscaled JPEG. So a blind win on DOM checks says nothing about whether pixels help. The pixel checks are the only place the arms genuinely differ in information.

E1 pixel-only checksp = 0.335 — not significant
armnshare of pixel-derived checks passed95% CI
preserved/vision41.000[1.000, 1.000]
preserved/blind20.750[0.500, 1.000]

Difference +0.250. no detectable difference (p=0.335). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E1 DOM-derived checks (expected to favour blind)p = 0.324 — not significant
armnshare of DOM-derived checks passed95% CI
preserved/vision40.875[0.781, 0.969]
preserved/blind21.000[1.000, 1.000]

Difference -0.125. no detectable difference (p=0.324). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E1 whole rubric (both mixed together)p = 0.658 — not significant
armnfinal rubric score95% CI
preserved/vision40.900[0.825, 0.975]
preserved/blind20.950[0.900, 1.000]

Difference -0.050. no detectable difference (p=0.658). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E1 regressionsp = 1.000 — not significant
armnregressions per run95% CI
preserved/vision40.000[0.000, 0.000]
preserved/blind20.000[0.000, 0.000]

Difference +0.000. no detectable difference (p=1.000). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E2 — does dropping thinking history hurt?

Both arms see screenshots. The only difference is whether each assistant message goes back with its reasoning_content intact or with that field removed. Every request asserts the two message arrays are otherwise byte-identical, so any difference here has exactly one cause.

E2 rubric scorep = 1.000 — not significant
armnfinal rubric score95% CI
preserved/vision40.900[0.825, 0.975]
stripped/vision20.900[0.900, 0.900]

Difference +0.000. no detectable difference (p=1.000). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E2 regressionsp = 1.000 — not significant
armnregressions per run95% CI
preserved/vision40.000[0.000, 0.000]
stripped/vision20.000[0.000, 0.000]

Difference +0.000. no detectable difference (p=1.000). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E2 parse missesp = 1.000 — not significant
armnprotocol violations per run95% CI
preserved/vision40.000[0.000, 0.000]
stripped/vision20.000[0.000, 0.000]

Difference +0.000. no detectable difference (p=1.000). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E2 iterationsp = 1.000 — not significant
armniterations used95% CI
preserved/vision43.000[3.000, 3.000]
stripped/vision23.000[3.000, 3.000]

Difference +0.000. no detectable difference (p=1.000). With n=4/2 this is consistent with no effect, and also can't rule out a modest one.

E2 again, without images

In the vision arm, screenshots accumulate across iterations and can come to dominate the context, shrinking reasoning_content’s share of it. These arms carry no image tokens at all, so this pairing isolates the thinking-history variable. If E2 looks null above but real here, dilution is why.

E2 rubric score, no imagesp = 0.664 — not significant
armnfinal rubric score95% CI
preserved/blind20.950[0.900, 1.000]
stripped/blind20.800[0.700, 0.900]

Difference +0.150. no detectable difference (p=0.664). With n=2/2 this is consistent with no effect, and also can't rule out a modest one.

E2 regressions, no imagesp = 1.000 — not significant
armnregressions per run95% CI
preserved/blind20.000[0.000, 0.000]
stripped/blind20.500[0.000, 1.000]

Difference -0.500. no detectable difference (p=1.000). With n=2/2 this is consistent with no effect, and also can't rule out a modest one.

E2 parse misses, no imagesp = 1.000 — not significant
armnprotocol violations per run95% CI
preserved/blind20.000[0.000, 0.000]
stripped/blind20.000[0.000, 0.000]

Difference +0.000. no detectable difference (p=1.000). With n=2/2 this is consistent with no effect, and also can't rule out a modest one.

Regressions observed

A rubric check that passed at one iteration and failed at a later one. Computed mechanically from the render, not graded by a model.

armtaskcheckpassed atbroke at
stripped/blindgradient-overlayno-horizontal-overflow12

Stopping early

Runs where K3 declared "done" on a render that still failed the rubric. Moonshot list “excessive proactiveness” as a known trait; this is what it looks like when measured.

3 of 10 runs (30%) stopped on their own while the render still failed at least one check.

Tasks

Three-tier pricing table pricing-table

A three-column table at 390px is the canonical responsive break. Source that looks correct — grid, sensible gaps — routinely overflows in practice, and only the render shows it.

10 rubric checks
  • no-horizontal-overflowNo horizontal scrolling at any viewport width
  • nothing-clippedNo text clipped by its own overflow:hidden box
  • no-render-errorsNo uncaught page errors and no render timeout
  • self-containedMakes no external requests — fully self-contained
  • legible-textNo rendered text below 12px
  • wcag-aa-contrastAll text meets WCAG AA contrast against its effective background
  • no-overlapping-textNo two text elements visually overlap at any width
  • consistent-left-alignmentFull-width stacked containers share one left edge
  • three-ctasExactly three call-to-action buttons, all within the viewport at 390px
  • tappable-ctasCTA hit targets at least 40px tall on mobile

Metrics dashboard with a bar chart metrics-dashboard

Twelve month labels under a chart is where text collides, rotates badly, or overflows. The code reads fine; the render is where the labels turn into mush.

10 rubric checks
  • no-horizontal-overflowNo horizontal scrolling at any viewport width
  • nothing-clippedNo text clipped by its own overflow:hidden box
  • no-render-errorsNo uncaught page errors and no render timeout
  • self-containedMakes no external requests — fully self-contained
  • legible-textNo rendered text below 12px
  • wcag-aa-contrastAll text meets WCAG AA contrast against its effective background
  • no-overlapping-textNo two text elements visually overlap at any width
  • consistent-left-alignmentFull-width stacked containers share one left edge
  • four-kpi-tilesFour KPI tiles present at desktop width
  • chart-presentA chart is actually drawn (svg present, or 12+ bar-like elements)

Article hero that survives a long headline article-hero

A deliberately overlong headline against a fixed-height hero. Fixed heights plus overflow:hidden is the most natural way to write this and the exact thing that clips it — invisible in source, obvious on screen.

10 rubric checks
  • no-horizontal-overflowNo horizontal scrolling at any viewport width
  • nothing-clippedNo text clipped by its own overflow:hidden box
  • no-render-errorsNo uncaught page errors and no render timeout
  • self-containedMakes no external requests — fully self-contained
  • legible-textNo rendered text below 12px
  • wcag-aa-contrastAll text meets WCAG AA contrast against its effective background
  • no-overlapping-textNo two text elements visually overlap at any width
  • consistent-left-alignmentFull-width stacked containers share one left edge
  • headline-intactHeadline text is present and not visually truncated
  • hero-fits-foldHeadline sits fully within the first screenful at every width

Text over a wide-range gradient gradient-overlay

The only failure mode here is a painted one. A single text colour cannot be readable across a gradient that spans #fdfdfd to #0b0f1a, so this needs a scrim, a per-section split, a text shadow, or a backdrop panel. Crucially the DOM cannot reveal the defect: getComputedStyle reports no backgroundColor for a gradient, so effectiveBackground is null and the computed-style contrast check has to skip it. Only sampling the rendered pixels shows whether the text survived — which makes this the one task where the vision arm has information the blind arm structurally cannot get.

8 rubric checks
  • no-horizontal-overflowNo horizontal scrolling at any viewport width
  • nothing-clippedNo text clipped by its own overflow:hidden box
  • no-render-errorsNo uncaught page errors and no render timeout
  • self-containedMakes no external requests — fully self-contained
  • legible-textNo rendered text below 12px
  • no-overlapping-textNo two text elements visually overlap at any width
  • gradient-presentA gradient background is actually used
  • text-spans-widthText actually crosses the gradient rather than hiding at one end