Batch one measured 0.004

Batch one came back with an amber share of 0.004. The corpus median across the 95 hero images already live on dxdev was 0.022. That is off by more than 5x, and we couldn’t see it just by looking.

That’s the part that still bugs us. We put the first batch of generated heroes next to the shipped ones and they looked fine: dark, moody, amber accents, the same abstract 3D geometry the house style calls for. The gap only showed up when we measured.

The job was to gap-fill hero images for the 104 dxdev posts that had shipped without one, all tracked in a single backlog ticket. Every image had to match the “dark data topography” look the other 95 already had: near-black background, off-white as an accent only, and amber as the thing lighting the scene rather than a dot of paint on it. We wrote the spec once and handed the generation to an autonomous agent tool, since watching a string of image-gen calls finish is a poor use of a session that can do other work in parallel.

The spec has one hard number in it: at least 70% of every frame near-black or deep slate. That was the constraint we read first, so it was the one we optimized for. The first batch shipped fast and technically hit it. It just didn’t sit right next to the existing heroes in the actual post grid, and we couldn’t say why.

Turning up the wrong knob

Our first fix was to lean harder on the prompt language. The brief already said amber “must be the thing lighting the scene, and it needs real presence: glowing seams between forms, illuminated edges and undersides, luminous pathways.” We assumed the model wasn’t taking that seriously enough, so we intensified it. More adjectives, more emphasis on glow, and an explicit restated line that amber is a light source, not paint.

Batch two came back with the same amber footprint, just brighter. Batch three did the same thing again. That made three regeneration rounds and more than a dozen images redone, and the set still read as generically dark next to the ones already shipped.

The problem was area, and we had spent three rounds tuning brightness. We were turning up the glow on a light source that covered about two percent of the frame, when the shipped heroes were closer to eight. Making a small light source more intense gives you a more intense small light source. It doesn’t make it bigger.

Making it measurable

Once we stopped rewriting prompt adjectives and started measuring the two axes directly, darkness and amber share, against the actual pixels of the 95 shipped heroes, the target stopped being a feeling and became a number. Darkness was already fine everywhere. That constraint was easy for the model to hit and easy for a human to eyeball. Amber share was the one deciding whether an image matched the site, and eyeballing couldn’t catch it. A person looking at a dark, orange-lit image registers “yes, that’s the style” long before “that’s 2% of the frame” occurs to anyone.

With a number to hit, the prompt fix was mechanical. We described amber coverage in terms of which structural surfaces have to carry it, instead of how brightly the surfaces that already had it should glow. From there we gated every batch: generate, measure both axes against the corpus range, reject and regenerate anything short, and only write files into the repo once a batch passed. The final set landed at a 0.022 amber median across all 104 images, every one inside the corpus range on both axes.

The bug that almost shipped anyway

The first deploy went red anyway, for a reason that had nothing to do with amber. CI flagged a duplicate YAML frontmatter key in a blog post file, left sitting in the same working tree by an unrelated earlier session. Nothing local caught it, because whatever ran locally didn’t validate frontmatter the way the CI build did. We staged the hero-image changes in isolation from that other session’s edits, fixed the duplicate key on its own, and pushed clean.

That isolation step mattered as much as the amber measurement. A batch touching 104 files is one broad git add away from shipping someone else’s half-finished edit under your commit message, and the failure looks nothing like the thing you were working on.

The agent tool billed zero credits across roughly 200 generated images, counting every regenerated batch. Money was never the cost of getting this wrong. The cost was three rounds of tuning prompt adjectives before we measured what we were optimizing. Once we had the number, the fix took one batch.

What to measure first

When a generated asset looks right but sits wrong next to the existing set, measure it against the shipped corpus before you keep rewriting the brief. Here the missing number was amber share, 0.004 against a corpus median of 0.022. Gate every batch on those measurements, and stage a large change in isolation so someone else’s unfinished edit doesn’t ship under it.