WootBuild.

Studio/docade/Advisory 05

Advisory · advisory-05.md

Advisory 05 — six lanes would produce 50 pieces at the wrong size

To: docade · From: WootBuild · Date: 2026-08-19 Status: open — docade decides Trigger: your assets/REQUESTS.md entry for empty-states/today-done. Assessing which lane it belongs to surfaced that the lane it belongs to is already wrong.


The finding

A WootBuild lane is one style contract, one output size, one QA rule. Six of your fourteen lanes claim pieces your own manifest declares at a different size.

LaneIts specPieces that disagreeSizes your manifest declares
ui-artscene-512 512×51218 of 24256, 320, 480, 512
arcadescene-512 512×51221 of 2124, 128, 256, 1024
cranescene-512 512×5125 of 14128, 256, 512, 1024
brandbrand-var 512×5123 of 1032, 72, 192, 512
storestore-var 1284×27782 of 8512, 1024
glyphglyph-24 24×241 of 6524, 240

Fifty pieces. Only arcade was known — advisory 04 §2 flagged it and it is marked do-not-produce. The other five were not.

Why nothing would have caught it

This is the part worth your attention. Every one of those fifty would have rendered successfully, passed QA, and been approved by a human — because QA checks the output against the lane's spec, and the lane's spec is the thing that is wrong. The render is correct, the verdict is honest, the file is the wrong number of pixels for the slot it was made for, and the first thing that disagrees is a phone.

It is the same bug as advisory 04 §3, where room-treatments would have shipped at half size. That one was caught by hand. This is the same sweep run across every lane, which is the only reason five more turned up.

Broken down, it is three causes — not fifty problems

Grouping the fifty by declared size makes the structure obvious, and most of it is not a hard decision.

Cause 1 — a row in the wrong category. Two pieces, and both look like typos.

pieceits laneits sizethe lane's size
admin/empty-adminglyph24024
store-listing/play-icon-512 · app-icon-1024store512 / 10241284×2778

empty-admin is a 240px empty-state illustration sitting in the 24px icon lane. play-icon-512 and app-icon-1024 are app icons in the store screenshot lane. Move three rows and two lanes go clean — including glyph, which is 65 pieces and next in the forging order after plush. This is the cheapest fifty-piece problem you will ever be handed.

Cause 2 — one artwork at several resolutions. Three pieces.

identity/favicon (32) · app-icons/badge-72 (72) · app-icons/icon-192 (192), all in brand against 512.

These are almost certainly one master exported at four sizes, not four artworks. Your manifest already models exactly this for the 18 silhouettes — "DERIVED from alpha channel, not drawn". Same shape, same answer, and it is the same question as advisory 04 §2's 128/256 pairs. One answer settles all three.

Cause 3 — a lane genuinely holding several sizes. The rest.

lanegroups
ui-art11 × streak-visuals @256 · 5 @320 · 2 entry heroes @480 · 6 @512
crane3 scene elements @1024 · cable @128 · button @256 · 9 @512

This is not chaos — each is coherent sub-groups. streak-visuals is eleven pieces at one size; entry heroes are two at another. They were bundled into one lane by us, and the bundling is what is wrong, not your manifest.

Two ways out, and it is genuinely our capability gap either way:

Our recommendation: split. It needs nothing from you, it is reversible, and the groups are already clean in your data. We will propose the split as an advisory before changing anything.

Which side is wrong is your call

For each lane, one of two things is true, and we cannot tell which from here:

  1. Your manifest is right and our lane is too coarse. Then these lanes need per-piece specs — WootBuild capability gap, tracked as ATL-39. ui-art at four sizes is not one lane in our current model.
  2. The lane is right and the manifest sizes are aspirational. Then the sizes want normalising, the way advisory 04 §2 asked about the 128/256 pairs.

glyph is probably the cheapest to answer — one piece at 240 in a lane of 65 at

  1. That single row is either miscategorised or a different asset entirely.

And the request that found it

empty-states/today-done maps to ui-art — an existing lane, unstarted, 24 pieces, no candidates. So the answer to "new lane or existing lane" is existing.

But your request describes it as full-screen portrait, 390×844, never seen small. That is neither 512 nor 320. It would be a third size inside a lane that already holds four. So the request is not blocked by anything about the request — it is blocked by the same question as the table above.

We have not costed it. It is filed, it is assessed, and it needs your answer on lane shape before it can be specced. Nothing about it is wrong: it is a good request and the reasoning for it is better than the reasoning in some of our own lane briefs.

One thing you flagged that we should confirm

You noted npm run assets:sheet has been exiting with a complaint about §4 and §3 disagreeing. WootBuild had no visibility into that at all. Our bridge reads your files, not your exit codes — a check failing on your side is invisible to us until a person mentions it. Worth knowing on both sides; not something we are proposing to fix today.

You were also right not to invent the manifest key to quiet the script. Filing the request and settling the catalogue are different acts, and keeping them separate is exactly what the drop-box is for.


Correction, 2026-08-19 — causes 1 and 2 were wrong

docade answered with evidence and most of this advisory did not survive it. The corrections are kept here rather than folded in, because why the assessment was wrong is the useful part and it is a gap in the bridge, not carelessness.

The root cause of my error: "already built" is unrepresentable

WootBuild read a work-list in which a delivered piece and an unmade piece look identical. Your CSV has a Done column — and WootBuild reads it in two places, console/lib/studio.mjs and art brief --undone — but scripts/asset-csv.mjs never populates it. Zero of 337 rows are marked done. Dead at both ends, and invisible from either side alone.

So five pieces were assessed as needing a spec decision when they had been generated weeks earlier. That is not a subtle failure mode; it is the default one until the channel carries completion.

Fixed on our side, and not by asking you to tick a column. art intake now reports pieces whose declared path already holds a file in your repo — free, it cannot go stale, and it is self-verifying in a way a manual column is not. It currently finds four: icon-192, icon-512, icon-maskable-512, badge-72.

It finds four rather than ten because the other six are delivered to a path the manifest does not declare — which is your point about the store icons, arrived at from the other direction.

Cause 1 — one of three, not three

admin/empty-admin stands. The other two do not.

play-icon-512 and app-icon-1024 are built, at assets/identity/png/icon-store-1024.png and icon-light-512.png. The manifest declares them under assets/store/, so check-assets reports them missing and WootBuild costed them as work. You are right that the fix is a delivery step in package-identity.mjs — store goes clean without anything being produced.

Cause 2 — does not exist as described

All delivered, and your breakdown is more precise than the question:

icon-192 / icon-512one master, two resolutions
favicon (32)same master plus a documented fallback — below 24px it becomes the flag alone, because the counters close
icon-maskable-512separate artwork — inset to 46% for Android's circle crop
badge-72separate artwork — monochrome silhouette; Android tints and discards detail

So "one answer settles all three" was wrong twice over: they are not one master, and none of them needs a spec. brand needs to know they are done, and that the size rules live in a script rather than in a lane.

What actually remains

admin/empty-adminone categorisation decision — answered below
ui-art, crane, arcadeCause 3. Coherent sub-groups bundled by us. We split the lanes. Nothing needed from you
storeyour delivery step, then clean
brand, app-icons, identityalready delivered. Ours to stop counting as work

admin/empty-admin — go, with one thing to decide deliberately

Move it. Its own subject line — "Shared empty state for queue, activity and kids before anything exists" — describes an empty state, and it is the only reason glyph is not clean. glyph is 65 pieces and the next lane forged after plush, so this single row is the most valuable one on the list.

Do not inherit 240 and SVG. Its five siblings in empty-states are 512 PNG, and today-done just joined them at 512. Landing empty-admin at 240 SVG makes empty-states a two-size, two-format category on the day it stopped being one — trading a glyph mismatch for a ui-art one.

Recommendation: empty-states/empty-admin, 512, PNG.

⚠ One thing to check before you move it, and it is yours: whether your database stores the key or the path. empty-admin as a key is unchanged, so if rows reference the key this is a file move. If anything stores assets/admin/empty-admin.svg, it is a migration and your §6 applies. We cannot see that from here.

And your withdrawal of the 390×844

Right call, and worth saying so plainly: the drop-box does say not to specify a size, the size is what landed it in ui-art as a third one, and 512 alongside its siblings is the correct default. Whether it should be a full-screen takeover is a real question and it should be decided on its own, not inherited from a sentence in a request.