Same prompt, two engineers:
Fable and Opus build the same DOOM

Both got the identical brief — a mobile-portrait DOOM-style raycaster, gpt-image-2 wall panels + enemies, "make it look good." Both wrote a from-scratch software raycaster in TypeScript, no engine. Clean rooms, zero shared code. Then we went part by part through what each one chose. The same fork shows up at every layer.

fable build: HULLROT  ·  opus build: IRONMAW  ·  same model for the art (gpt-image-2)  ·  June 2026
renderer code
485 lines vs 215
screenshots it read
18 vs 7
verification effort
41% vs 25%
combat QA
video frames vs a counter
The headline in one sentence: wherever there's a hard-but-pretty way and an easy-but-plain way to render something, Fable keeps picking pretty and Opus keeps picking plain. It's not a knowledge gap — Opus knows every technique. It's a hundred small taste decisions, and they compound.
NEW CHAPTER ↓  After this autopsy we ran an experiment: can fusion — two blind Opus builders + an Opus judge, the OpenRouter "compound model" trick — squeeze a better game out of Opus than a single run? Jump to the fusion experiment →

what you actually see

fable combat
FABLE / HULLROT — textured walls with depth, a lit floor, fog that grades to black
opus combat
OPUS / IRONMAW — flat muddy walls, dark void floor, and an enemy fused to the gun

part 1 — floors & ceilings

01The ground under your feet

Fable wins. Opus didn't skip floors — it drew them the cheap way, as two flat gradients, so the ground reads as a featureless dark void. Fable cast a real textured, perspective-correct floor.

Fable / HULLROT
Textured floor & ceiling casting full solve
// per screen row, project the world-space
// ground point and sample a floor texture
for (let y = 0; y < VIEW_H; y++) {
  const isFloor = y > horizon;
  const rowDist = halfH / (isFloor ? y-horizon : horizon-y);
  const shade = this.fog(rowDist) * (isFloor?1:0.62);
  // ...sample floorTex per pixel across the row
}
Opus / IRONMAW
Two flat canvas gradients shortcut
this.ceil  = ctx.createLinearGradient(0,0,0,half);
this.ceil.addColorStop(0, `rgb(${CEIL_RGB})`);
this.ceil.addColorStop(1, `rgb(${FOG})`);
this.floor = ctx.createLinearGradient(0,half,0,H);
// ...filled as 2 solid bands. no texture, no perspective.

Why this fork?

Textured floor-casting is the single fiddliest, most break-prone piece of a raycaster — ~30 lines of perspective math that's easy to get subtly wrong. The gradient is 4 lines and cannot break. Opus made a risk-aversion trade: take the robust-but-plain win. Fable spent the complexity for the ground texture. Remember this trade — it repeats.

part 2 — wall texturing

02How a wall column gets drawn

Fable wins on sharpness. Both run the identical ray math. The split is the final draw: Fable writes texels by hand into a pixel buffer (crisp). Opus slices a 1px column and lets the browser stretch it (soft/blurry).

Fable / HULLROT
Manual per-pixel framebuffer write full solve
for (let y = y0; y < y1; y++) {
  const px = tpx[ty*tw + texX];      // nearest texel
  let r=(px&255)*shade, g=..., b=...;  // fog × pixel
  u32[y*VIEW_W + x] = 0xff000000|(b<<16)|(g<<8)|r;
}
Opus / IRONMAW
drawImage stretches a 1px slice shortcut
ctx.drawImage(tex, texX,0,1,tex.height, x,start,1,lineH);
// browser bilinear-stretches the 1px column
// vertically → soft, slightly blurred walls

part 3 — fog & distance

03How distance darkens the world

Fable wins. Fable folds fog into the pixel's own color — true darkening that keeps contrast. Opus paints a semi-transparent fog-colored rectangle on top, which uniformly washes distant detail toward flat black. That overlay is exactly the "muddy" look.

Fable / HULLROT
Fog multiplied into each texel full solve
let shade = this.fog(perpDist) * boost
           * jitter[cell] * zoneLight[cell];  // + light zones
r = texel_r * shade;  // darkens the real color
Opus / IRONMAW
Fog-colored alpha rectangle on top shortcut
const fog = Math.exp(-dist * FOG_DENSITY);
ctx.fillStyle = `rgba(${FOG},${1-b})`;
ctx.fillRect(x, start, 1, lineH);  // veil over the wall

part 4 — lighting

04Light in the room

Fable wins. Fable gives each cell its own animated light zone — broken-ballast flicker, hell-pulse, dim — updated every frame. Opus uses one global light scalar times a static per-cell hash. One breathes; one is uniform.

Fable / HULLROT
Per-cell animated light zones full solve
private zoneLight: Float32Array;  // one per cell
// each frame: pulse / flicker / dim modes + muzzle boost
if (z.mode==='flicker') /* harsh broken-ballast */
if (z.mode==='pulse')   /* slow hell breathing */
Opus / IRONMAW
One global scalar × static hash shortcut
function cellBright(x,y){ /* static hash jitter */ }
let b = fog * sideShade * cellBright(mx,my) * light;
// `light` is a single number for the whole scene

part 5 — doors

05Sliding doors

Fable wins. Fable modeled real Wolf3D-style sliding doors right inside the ray loop — the ray hits the door plane at the cell midline and slides it into the jamb. Opus's loop has no door case at all: every solid cell is just a wall.

Fable / HULLROT
Sliding door at the cell midline full solve
if (cell===DOOR) {
  const dMid = side===0 ? sideX-deltaX*.5 : sideY-deltaY*.5;
  let u = hitCoord - Math.floor(hitCoord);
  u -= open;             // door slides into the jamb
  if (u >= 0) { tex = door; break; }
}
Opus / IRONMAW
No doors in the renderer shortcut
if (tile > 0) break;  // every solid cell = a plain wall.
// no midline, no slide, no door texture branch.

part 6 — the enemy fused to the gun

06The bug only video could catch

Opus shipped IRONMAW with a demon glued on top of the gun. Its QA never caught it — and here's the twist: it wasn't a code bug at all. The fix came only when Opus was told to record combat video and read the frames.

opus bug
OPUS v1 — the demon is fused to the gun barrel. Shipped clean past QA.
opus fixed
OPUS v2 — given video QA, it root-caused + fixed it: the gun art sheet had a brute baked in above the weapon; the chroma key kept it. Cropped the sheet, warmed the fog → the skull wall reappears.

Why the first QA missed it

Opus verified combat numerically — fire, then check the __game.enemies counter dropped. ✅ passes. But a counter is blind to where a sprite is drawn. Fable, on the same kind of fast-state problem, had escalated on its own: "timing's against me — let me record video and pull frames with ffmpeg." It watched combat in motion. The bug that's invisible to a number is obvious in a frame.

part 7 — how each tested itself

07Numbers vs eyes

Fable wins — and this is the deepest split. Both had ffmpeg + Playwright sitting right there. Fable reached for video; Opus reached for assertions.

Fable / HULLROT
Records combat, reads the frames visual
// 8 self-play bot runs · 5 ffmpeg calls · video frames
"Timing luck is against me — let me record a
 video instead and pull frames with ffmpeg."
// → looks at combat IN MOTION
Opus / IRONMAW
Asserts the counter changed numeric
// e2e: "shoot → enemy drops, no console errors"
expect(__game.enemies).toBeLessThan(before);  // ✅
// verifies combat WORKS, never that it LOOKS right

the pattern

Read down the list — it's the same decision every time

Floors: textured cast vs flat gradient. Walls: hand-written pixels vs browser stretch. Fog: multiplied-in vs painted-on. Lighting: animated zones vs one scalar. Doors: modeled vs skipped. QA: video vs counter.  Fable eats complexity for the look at every fork; Opus takes the robust shortcut at every fork. That's why Fable's renderer is 485 lines and Opus's is 215 — and why one looks like DOOM and the other looks like a tech demo. The knowledge is identical. The taste isn't.

SO WHAT'S SCAFFOLDABLE?

When we forced Opus to do the video loop it skipped — record combat, read frames — its screenshot-reads jumped 7 → 46, it out-verified Fable, and it caught the gun bug with a sharper diagnosis than any human review. The diligence was 100% scaffoldable.

But on the parts that need taste from a blank page — should the floor be textured? should fog darken or veil? is that material "painted metal" or "brown cardboard"? — no scaffold closed the gap. Opus could see its materials were wrong (it scored them 4/10) and still couldn't make them right.

The clean line: a harness can make any model see its own bugs. It can't make it generate beauty.  Catch is scaffoldable. Create is intrinsic. That's where the moat is.

the fusion experiment — can two blind Opus beat one?

The page above ends on a wall: create is intrinsic. So we tested the most-hyped way around it. OpenRouter just shipped "Fusion" (Fable-tier answers from cheaper models); an open-source clone runs two Opus 4.8 on the same task, blind to each other, then a third Opus reads both and writes the final — "one run can be confidently wrong; two, cross-examined, can't hide it." We ran exactly that on the Opus DOOM: two blind builders each pushed the same v2 toward HULLROT with video QA, then an Opus judge fused them into v3. Clean rooms, a blindness watchdog, every score checked against the reference by eye.

builder A — self score
~6.8 / 10
builder B — self score
~7.5 / 10
fused v3 — score
~8.4 / 10
cost of the fusion
3 Opus runs · ~$39

part 8 — the five frames, side by side

08v2 → A → B → fused v3 → the fable ceiling

Same corridor, same vantage. Watch the floor tone and the walls as you read left to right — the two blind builds each fixed a different thing and broke a different thing.

v2
v2 START
busy red walls, dark void floor
A
BUILDER A
✓ warm floor, lit gun
✗ saturated walls, glow-orbs
B
BUILDER B
✓ muted walls, real lighting
✗ floor reads purple
v3
FUSED v3
B's walls + A's warm floor + A's gun
hullrot
FABLE / HULLROT
the ceiling — none reach it

part 9 — where fusion earned its keep

09Complementary mistakes, cross-examined

Fusion genuinely beat both single runs — for the precise reason the trick predicts. The two blind builders converged where it mattered and diverged where it counted.

They AGREED (→ high confidence it's the real bug)
Both, blind, independently named the same #1 flaw — "geometry floats in a black void, the flat gradient floor is the biggest 'this isn't 3D' tell" — and both fixed it with textured perspective floor-casting. Two strangers reaching the same diagnosis is strong signal it's correct.
They DIVERGED (→ the judge gets to pick the winner)
A got the warm-dark floor right + fire-lit the gun, but left walls garish and added blown-out glow-orb "lights." B built real directional lighting + tempered the walls to match the reference, but its floor lit up purple in every frame. Each nailed the other's weak spot.

The cross-examination moment

The sharpest play wasn't a merge — it was a rejection. Builder A scored its own glow-orbs a 7/10 and shipped them, confidently wrong. Builder B, blind to A, simply never built them. The judge, holding both against the reference, used B's clean build as evidence that A's confident feature was a regression — and dropped A's entire glow path. Then it took B's walls+lighting, swapped in A's warm floor, grafted A's lit gun. Result: v3 has B's muted walls AND A's warm floor — the one frame that has neither build's wart. That's "two cross-examined can't hide it," and the screenshots confirm it.

part 10 — what fusion could NOT do

10It fixes what they disagree on. It inherits what they share.

Now look at v3 against HULLROT again. v3 is the cleanest Opus frame — but it's still not in the same room as Fable, and the reason is the whole point.

The gap fusion closed
Per-build warts — wrong floor tone, a bad lighting model, a blown-out feature. These were divergent: one builder got each right, so the judge could pick the winner. Cross-examination thrives on disagreement.
The gap fusion left untouched
All three Opus runs share the same ornate, saturated skull-bulkhead walls and the same flat, focal-light-less composition — versus HULLROT's plain grimy stone and single warm doorway light. A shared blind spot. No builder offered a better answer, so the judge had nothing to cross-examine against. It survived straight into v3.

The judge said it itself

Its own verdict file: "Fusion was worth it precisely because the failures were complementary rather than shared — had both builds made the same mistake, fusion would have inherited it." That's the law of this trick. It is a disagreement engine: it amplifies the cases where one run caught what another missed. It is powerless over the cases where every run is wrong the same way — which is exactly where taste from a blank page lives.

SO WHERE DOES FUSION SIT?

Fusion is real, and it worked: v3 (~8.4) cleanly beat both single runs (~6.8 and ~7.5) by taking the right half of each and rejecting one builder's confident mistake using the other as a witness. On the build axis, two-blind-plus-judge genuinely lifts quality — for free of any new model.

But it lifts the same axis the rest of this page already named. Fusion is a catch mechanism: cross-examination catches divergent errors. It is not a create mechanism: it produced nothing neither builder had, and it never dented the shared blind spot that keeps every Opus run a tier below HULLROT. Three Opus that all lack the same taste don't vote their way to it.

The clean line, updated: a harness can make a model see its own bugs (video QA) and now cross-examine away its confident ones (fusion). It still can't make it generate the taste no copy of it has.  Fusion widens "catch." The moat is still "create."

Built by BMO · all runs in clean rooms, same prompts · live demos: HULLROT (fable) · IRONMAW (opus v1) · IRONMAW v2 (video-fixed) · IRONMAW v3 (fused)