assets/CONCEPT_ART_PRODUCTION_PROGRAM.md
CANON SUBORDINATION — this document is PROPOSAL-TIER: it serves canon and never outranks it.
The canon: the CVD · the T1 foundation docs · the T0 registries (registries/) · the spine
(docs/spine/CH_*.md) · the region pages (_source/02_Tier_2_Region_Pages/). Authority order:
docs/DOC_MAP.md § 0.
Canon served:docs/spine/CH_*.md§## Asset anchors+ §## Mechanical anchors(79 nodes) ·
registries/T0_Chapter_Index [ACTIVE v1.2]·registries/T0_Region_Index [ACTIVE v1.2]·
registries/T0_Creature_Roster·registries/T0_Character_Index [ACTIVE v1.1]·
registries/T0_Boss_Encounter_Registry·registries/T0_Weapon_Registry·
registries/T0_Ability_Tree_Registry [ACTIVE v0.1]·registries/T0_Familiar_Registry·
_source/02_Tier_2_Region_Pages/*.md§Zone Catalog.
READ THAT CANON FIRST — open it and derive from it before you build anything from this document.
If this document disagrees with canon, CANON WINS and this document is the defect — fix the
document, never the canon. Nothing here is applied until it is ratified into canon.
Status: PROPOSAL-TIER. v1.0, 2026-08-02.
It answers Josh verbatim: *"build the concept art (hundreds for the region and thousands for all
the assets architecture fauna npcs etc in the region, and thousands more for everything the player
will walk through and experience, and thousands more for abilities, and thousands more for weapons,
and thousands more for characters and companions and npcs, and thousands more for equipment), not
all is needed at once and you develop those pages and I approve them or make comments on how to
adjust or modify."*
docs/proposals/CONCEPT_ART_PROGRAM.md (1,840 lines) answers WHAT art exists and how much — the
43-class density model, the three-panel board shape, the licence routing. It is the TAXONOMY and it
stands; nothing here re-derives a single one of its counts.
This document is the other half: **HOW ~17,900 plates are produced, stored, reviewed, and kept
honest, by one person with one GPU.** Six questions, six sections, every number traced.
It does not re-rule the board shape (CONCEPT_ART_PROGRAM.md §2.1), the board medium
(docs/art_boards/README.md "The board medium is RULED"), the six-value rights vocabulary
(docs/art_boards/README.md "Conventions the gate enforces"), the licence tiering
(docs/pipeline_review/tech_research/PIPE_ART_2026-07-29.md §1), or the five-state generation
lifecycle (docs/generation_lifecycle.json enum). It **uses all five and invents no parallel of
any of them.** Where it proposes something new the text says PROPOSED.
The failure this program is shaped around. On 2026-08-01 a session built BRD_0001 from the two
program docs and never opened docs/spine/CH_02.md, whose ## Asset anchors block at line 226 had
already typed and id'd the board's entire contents. It passed the structural gate and four
adversarial critics. harness/check_canon_derivation.py (GATE 39) closed the derivation hole after
the fact. **Every mechanism below is designed so that failure is not merely caught but
uneconomic** — the cheapest path through this program is the one that starts at the canon node.
---
Counted from the live registries on 2026-08-02, not from any document's summary.
| Surface | Live count | Source |
|---|---|---|
| Narrative nodes | 79 (+ 80 spine files incl. CH_PROLOGUE / CH_EPILOGUE) | registries/T0_Chapter_Index [ACTIVE v1.2] |
| Region rows | 76 | registries/T0_Region_Index [ACTIVE v1.2] |
| Region PAGES authored | 12 (+ README) | _source/02_Tier_2_Region_Pages/*.md |
| Zone-catalog rows | 84 across those 12 pages | _source/02_Tier_2_Region_Pages/*.md § Zone Catalog |
| Bosses | 281 | registries/T0_Boss_Encounter_Registry |
| Abilities | 185 | registries/T0_Ability_Tree_Registry [ACTIVE v0.1] |
| Characters | 164 | registries/T0_Character_Index [ACTIVE v1.1] |
| Creatures | 150 | registries/T0_Creature_Roster |
| Antagonist operatives | 64 | registries/T0_Antagonist_Network_Registry [ACTIVE v0.1] |
| Weapons | 72 | registries/T0_Weapon_Registry |
| Vril sites | 42 · Interior spaces 44 · Familiars 22 · Flora 25 · Gather nodes 24 · Minerals 19 · Runes 19 · Festivals 16 · Schools 13 · Trades 10 | the named registries |
| Equipment | 1 row | registries/T0_Equipment_Registry — the emptiest art-bearing registry in the repo |
**Multiplied to plates by docs/concept_art/density_model.csv (43 classes, emitted by
harness/concept_art/emit_density_model.py, never typed):**
| FLOOR | TARGET | |
|---|---|---|
| Slice (Prologue→Ch 13; 14 nodes / 15 legs) | 2,525 | 3,471 |
| Full game (79 nodes / 76 region rows) | 12,799 | 17,876 |
Two of those counts are ceilings, not rows, and the program must not pretend otherwise. The
density model sizes creatures at 420 and named entities at 1,000+ against live registries holding
150 and 164 — that is ~2,900 plates budgeted against rows that do not exist yet
(CONCEPT_ART_PROGRAM.md §6.2 item 1 states this openly). The production consequence is stated in
§5: you cannot generate art for a row that has not been authored, so registry population is a
hard predecessor, not a parallel track.
Plates are not the review unit — a SHEET_4 boss is four plates and one decision. Dividing each
class's plate count by its art_unit arity gives the number of things a human must actually look at:
| plates | review units | of which parametric ("cheap") | of which authored ("expensive") | |
|---|---|---|---|---|
| Slice target | 3,471 | 2,368 | 664 units / 855 plates | 1,704 units / 2,616 plates |
| Full game target | 17,876 | 12,840 | 2,851 units / 3,745 plates | 9,989 units / 14,131 plates |
cheap_or_expensive is a live column of density_model.csv; the split is the model's own, not a
judgement made here. 20.9 % of all plates are parametric — they are produced by applying a rule
that one board sets (the rarity ladder, the per-element VFX law, the weapon grade ladder). Those do
not get looked at one by one, and §6 says exactly what they get instead.
---
Not one giant board. **A LEAF: one canon anchor line-set, at most 12 entity rows, one page, one
scroll.** A leaf is the atomic reviewable, and its cap of 12 is measured, not chosen.
Across all 80 spine files, every bullet under ## Asset anchors and ## Mechanical anchors that
carries a canon id was counted:
| ids per anchor LINE | ids per anchor SECTION | |
|---|---|---|
| lines / sections measured | 547 | 135 |
| min · median · mean | 1 · 1 · 2.4 | — · 9 · 9.2 |
| p75 · p90 · p95 | 3 · 6 · 8 | — |
| max | 12 | 35 |
| share ≤ 12 | 100 % | — |
No anchor line anywhere in the spine names more than 12 ids. So a cap of 12 can always split a
node's anchor section on a LINE boundary and never cut a line in half — the leaf boundary and the
canon boundary are the same boundary, at every node, by measurement. A cap of 10 would split
CH_02.md's creatures_and_fauna line (7 ids) away from nothing, but a cap of 8 would eventually
cut one; a cap of 16 buys nothing the canon grain uses. **12 is the smallest cap that never cuts a
canon line.**
And because the median anchor SECTION carries 9 ids, **most nodes' entire ## Asset anchors block is
one leaf.** The reviewable unit and the canon unit coincide without either being bent.
Assumptions, stated so they can be argued with or replaced by a measurement after the first ten
leaves: an approve is ~12 s (look, recognise, tap), a comment is ~45 s (look, decide what is
wrong, type it), and ~20 % of rows draw a comment. Mean 18.6 s/row.
| cap | time per leaf | leaves, slice (expensive) | leaves, full game (expensive) |
|---|---|---|---|
| 10 | 3.1 min | 171 | 999 |
| 12 | 3.7 min | 142 | 833 |
| 16 | 5.0 min | 107 | 625 |
Josh's phrase is *"scannable on a phone in a few minutes."* 12 lands at 3.7 minutes. The row count is
the lever, not the plate count: a leaf of 12 boss rows is 48 plates and still 12 decisions.
The total human cost, which is the number that decides whether this program is real:
142 leaves for the entire Prologue→Ch 13 slice ≈ 8.8 hours of Josh, and ~833 leaves ≈ 52 hours
for the full game — plus ~20 law leaves (§2.3) that cover the other 2,851 parametric units.
Spread across the build that is roughly one leaf a day. The GPU is not the bottleneck; this is —
which is why §5 orders the work by what unblocks the build rather than by what is cheap to render.
| kind | bound to | how the residue is computed | count |
|---|---|---|---|
| ANCHOR LEAF | one spine node's anchor section, or a ≤12 slice of it | exactly GATE 39 D2 over that section — the ids canon names in scope | ~150 for all 79 nodes (135 sections, median 9 ids) |
| CLASS LEAF | one density class × one region leg, for classes homed in a registry rather than an anchor line (ambient crowd buckets, foliage sets, minerals) | D2 over the registry rows filtered to the node | the balance of the 833 |
| LAW LEAF | a rule, not a place — the existing LANGUAGE_BOARD unit (CONCEPT_ART_PROGRAM.md §1.1) | n/a — one pick sets a law applied hundreds of times | ~20, and they come FIRST (§5) |
The ANCHOR LEAF is the load-bearing one and it is the direct structural answer to the 2026-08-01
failure: **a leaf cannot be authored without opening the anchor section it is bound to, because the
anchor section is what enumerates its rows.** There is no leaf-shaped thing you can build out of
CONCEPT_ART_PROGRAM.md.
One row = one entity = one decision. Recommended plate at full page-width; the two alternates as a
collapsed strip; the entity's canon id and its [SRC:] anchor visible as text; one approve control
and one comment field. Below the rows, unchanged from GATE 39 D5: **the residue block — every id
this leaf declares pending, with its reason and its owning registry — rendered on the page's own
face**, so a leaf that realized nothing is visibly a leaf that realized nothing.
---
plate_id (PLT_0001_A1_1) and board_id (BRD_0001) are landed keys in
docs/art_boards/PLATES.csv / MANIFEST.csv. They stay. Nothing here mints a parallel id space.
What is added is a path, because the landed asset_path column has no ruled form and 17,876
free-form paths is not a scheme.
AMENDED IN PLACE 2026-08-03 (thirty-first sitting — the pipeline-hardwiring mandate, applied
through the four-lane compatibility pass). The path form below is unchanged and is exactly what
harness/check_art_pointers.py enforces. What this amendment adds is the LAW that fills the second
segment, the token grammar that carries the derivation onto the row instead of losing it at the CSV
boundary, the column set the row needs, the stage discipline, and the render-tier axis. Every clause
here was forced by a measured defect: the 2026-08-02 lane generated 76 plates whose second segment
held a prompt-pack kebab slug, so GATE 39 moved by nothing and no dispatcher could find a plate for
any canon row. Nothing below re-rules the path FORM — the form was right; the VALUES were not.
$HUMANITY_ART_ROOT / <class_id> / <canon_id> / <plate_role>__<hash12>.<ext>
D1-15 CR_0001 silhouette__9f2a1c4b7e03.png
<class_id> — the density_model.csv class. Files sort into the same 43 buckets the schedule budgets in, so "how much of D1-15 exists" is ls | wc -l and never a query.
<canon_id> — the canon row the plate realizes. The path itself carries the derivation. Aplate that cannot name a canon id has nowhere to be written.
<hash12> — the first 12 of generation_prompt_hash, which harness/generation_prompt_hash.py already computes over a CLOSED payload **including the model's
licence class**.
That last choice is the whole design. Because the hash covers the prompt, the seed, the sampler
config, the tier and the licence class, *any* regeneration — a comment-driven re-prompt, a seed
change, a model demoted from SHIPPABLE to PREVIZ_ONLY — produces a different filename. So:
docs/art_boards/README.md's "Rejected outputs … are kept, never deleted" stops being a habit andbecomes a property of the naming scheme;
harness/generation_prompt_hash.py was built for now reaches thefilesystem, not just the row.
AMENDED IN PLACE 2026-08-03 (director ruling R-G, the art final build). The sentence below used
to read "Nothing else may occupy segment 2 — not a prompt slug, not a beat id, not a site name, not a
title." That wording contradicted this section's own armed tooth: harness/check_art_pointers.py
P9 has always resolved zone: (against build/zones/<page_slug>.csv), beat: and scene: (against
docs/spine/CH_NN.md) as PRIMARY tokens, and site: through the class's key registry — so a law that
banned a beat id from segment 2 was banning something the gate lawfully admits, and the first row that
needed one (the landed fire-night row, D1-03/CH02_B02) had to be declared a deviation instead of a
key. The FORM is unchanged. What is corrected is which token families may be the PRIMARY.
SUPERSEDED: the old prohibition on beat ids in segment 2. STANDING: the prohibition on a
prompt slug and on a title, which is the defect the section was written for.
<class_id> SELECTS a registry. <canon_id> is a LIVE PRIMARY KEY — a key that a named repo source
resolves, which is exactly the set check_art_pointers.py resolve_token() implements:
key_registry / key_column (entity:, boss:, insc:, site:, law:) — thedefault and the majority case;
zone:<zone_id>@<page_slug> — resolved against build/zones/<page_slug>.csv, page-qualified because a zone_id is unique only within its page. Segment 2 is the bare zone_id;
beat:CHNN_BNN and scene:SC_CHNN_<slug> — resolved against docs/spine/CH_NN.md's own§10 beat lines and §13 scene-anchor table. A staging or key-frame plate whose subject IS a beat
keys on the beat; a beat with no §13 scene anchor may never have one minted for it to key on,
because the spine is what declares scene anchors and an invented SC_ id fails P9 by construction.
A prompt slug and a title remain banned, and so does any token no source resolves. The refusal at
§3.2b is untouched: a row with no live primary key is NOT GENERATED.
docs/concept_art/density_model.csv carries key_registry and key_column on all 43 class rows, so the class-to-registry rule is derivable rather than
remembered. key_registry is the docs/generation_lifecycle.json enrolment key
(T0_Creature_Roster/Sheet1) and key_column is that registry's own primary-key column
(creature_id) — the same pair generation_lifecycle.json already declares under id_columns
for the twelve enrolled registries. It is a re-point of live data, not a new id space.
ruled_source named a cell with no primary key. D1-16 folklore beings moved off T0_Chapter_Index.folklore_entities_in_chapter — a
semicolon-joined token list on a NODE registry — onto T0_Creature_Roster/Sheet1 creature_id,
because CR_0151 (the Polo) and CR_0010 (Ebu Gogo) are the live rows those tokens name. D1-03 and
D1-04 layouts moved off "spine 3b site rows" onto the ZONE key: build/zones/<page_slug>.csv
zone_id, which is the spine §3b site_id carried verbatim by harness/regen_zone_catalog.py.
(law) with key_column art_bible_id is the LAW LEAF(§2.3) — a class whose subject is a rule, not a place, keys its plates on the art-bible artifact
id and never on a fabricated canon row; the two law leaves are the LANGUAGE_BOARD classes, D1-28
Hollow surfaces and D1-34 UI iconography and rarity language, and art_unit == LANGUAGE_BOARD is
the machine discriminator so the set cannot drift by opinion. build/zones/<page_slug>.csv is the
ZONE key, page-qualified because a zone_id is unique only within its region page. (none) means
no live primary key exists ANYWHERE, so the class cannot dispatch at all — D1-12 non-boss enemy
classes is the only one, and its home is DESIGN_GAP_REGISTER gap 1.
order in both columns (D1-20 minerals and gather nodes; D1-37 scripts and inscription spine). The
plate row's own canon_registry then says which of the two that plate keyed on, so the ambiguity
is resolved per row rather than left in the class.
BE_0002.P2 is written BE_0002_P2 in the path while the untouched token stays on the row in canon_ref. A filesystem must never be the
reason a canon key changes shape, and a canon key must never be the reason a path breaks.
registries/** and build/zones/*.csv when thisamendment landed — 0 unresolved, with a positive control run on a deliberately wrong registry and
a deliberately wrong column so the zero is a real zero and not a dead search.
"One row = one entity = one decision" (§2.4) is not advice; it is what makes the path form
expressible. A row that names more than one canon id takes exactly one of three dispositions.
naming CR_0003, CR_0004, CR_0005 and CR_0006 is four rows at D1-15/CR_0003, /CR_0004,
/CR_0005, /CR_0006 — which is what D1-15's own formula (species x 3) already asks for.
THE VIEW, and every other id rides canon_id_refs. An antagonist operative who is also a named
character and a boss is D1-11/NW_0001 with canon_id_refs CHAR_0008|BE_0002 — and he
separately OWES a D1-09/CHAR_0008 portrait and a D1-13/BE_0002 boss sheet. Three classes, one
man, three plate sets; never one plate wearing three hats.
style register, the Hollow surface language, the rarity language) keys on the art-bible artifact
id under (law). It names no canon row because it realizes none.
GENERATED. It goes to its board's coverage block as pending with its reason and its owning
registry, and the composer refuses the work order. This is the tooth that would have stopped the
four unmintable rows of 2026-08-02 (a realm object routed to a registry holding one row, a carved
mark with no home anywhere, a beat id, and a hard-line id that is a rule and not a thing).
canon_ref TOKEN GRAMMAR — the derivation, carried on the rowcanon_ref is a pipe-joined list of prefix-typed tokens. The FIRST token is the PRIMARY, and the
path's second segment is its path-safe form. The polymorphic-reference precedent the repo already
carries is T0_Dialogue_Line.speaker_ref and T0_RNG_Drop_Table.linked_entity_id; nothing new is
invented here except the prefix set.
entity:CR_0001 · entity:CHAR_0008 · entity:NW_0001 — a registry row in an id family.zone:mount_inerie_cone_country@flores_island — a zone row, page-qualified.beat:CH02_B06 — a spine §3c beat. Mandatory on any scene or quest-moment plate, because the beatrow is the only key that resolves both the level and the sequence slot.
scene:SC_CH02_polos_offer — a §13 scene anchor, when the beat carries one.boss:BE_0002.P2 — a boss encounter, PHASE-qualified wherever the phase breakdown names more thanone phase, because two phases can be two different zones with two different tells.
site:VS_CH02_001 · insc:INSC_77H_CH02_FRAGMENT — vril site and inscription spine.law:material_palette_and_style_register@flores_island — a law leaf.pending:<reason-slug> — the only legal value for a row with no canon target, and it is illegal on a row whose generation_status is complete. A pending token obliges a matching pending row
in the owning board's coverage block, in the same commit.
AMENDED IN PLACE 2026-08-03 (director ruling R-B). pending is deliberately NOT a member of
harness/read_boards.py CANON_REF_PREFIXES, and the two do not contradict once the SCOPE is
stated, which it was not: CANON_REF_PREFIXES is the LANE-G/P prefix set. T-CITE-REQUIRED
inspects canon_ref only on lane G and lane P (read_boards.py, the if lane in ("G","P")
guard), which is exactly §3.2b's REFUSAL stated as arithmetic — a dispatchable row may never key a
pending: token, and a lane-R / generation_barred row may, because it dispatches nothing. The
guard §3.2c owed was not generation_status but LANE, and it was already armed. Nothing in the
code changes; the sentence now says what the tooth does. The live occasion: six Ch-2 rows were
authored at lane G/P on pending: keys, and R-B re-keyed all six to live primaries rather than
widening the prefix set to admit them.
docs/art_boards/PLATES.csv keeps its existing seventeen columns in their existing order (renaming
or reordering them re-baselines every consumer and every fixture) and gains thirteen, appended:
| column | what it is |
|---|---|
density_class_id | FK to density_model.csv class_id; must equal asset_path segment 1 |
canon_id | the PATH-SAFE primary key; must equal asset_path segment 2 |
canon_registry | the key_registry the id belongs to — what makes the id unambiguous across id spaces |
canon_ref | the §3.2c token list; first token is the primary |
canon_id_refs | pipe-joined secondaries, the PRIMARY+REFS disposition |
subject_slug | the human-readable name. Keep it — it is good copy for the site. It is simply not an address |
care_tier | copied from the subject's own row, never hand-typed |
protected | copied from the subject's own row; with care_tier it is what makes the Lane-G refusal arithmetic instead of habit |
stage | draft · review · hero (§3.2e) |
render_tier | ITERATION · REVIEW · HERO_FAST · HERO (§3.2f) |
directional_label | MANDATORY and non-empty on any previz_only row (docs/art_boards/README.md, the thirty-first-sitting previz amendment) |
regeneration_trigger | the lifecycle column DR-2 §6.3 fires through; without it a hash change has nothing to fire on this table |
last_generated_timestamp | the lifecycle column its siblings already have on every enrolled registry |
The four-seeds-per-subject sweep is the DRAFT stage of ONE subject. Recording those four as
generation_status=complete makes every downstream count wrong by about 4x and inflates D1 progress
against floors that count FINISHED plates.
stage carries draft, review or hero, and generation_status then means "complete AT THISSTAGE" — which is the only reading under which a candidate sweep and a delivered plate are not
byte-identical from the outside.
density_model.csv'sfloors, so "how far is D1-01 against 180" becomes a query. That ledger does not exist yet; it is
named as NOT BUILT here and its rider (FR-084) carries the honest-NONE tooth until it does.
density_model.csv are hero-stage counts. A schedule that counts drafts againstthem is not optimistic, it is wrong.
render_tier IS ITS OWN AXIS — RATIFIEDRATIFIED 2026-08-03 (thirty-first sitting, the pipeline-hardwiring mandate). render_tier joins the
CLOSED generation_prompt_hash payload key set as its own key, with the closed vocabulary
ITERATION · REVIEW · HERO_FAST · HERO — the render presets §4 measures. Adding a key to a
closed set is a RATIFICATION and never an edit, which is why it is recorded here with its sitting.
generation_tier stays what DR-2 §6.1 froze it as: the FIDELITY and BUDGET class, closed at background · region_standard · region_hero · realm · apex. It is not a render preset and
it never was.
generation_tier: "REVIEW" into 76 stored hashes — the render preset'sname inside the budget-class enum. That is the exact five-senses-of-tier collision
docs/ASSET_DROPIN_CONTRACT.md §1 bans a bare tier column to prevent, and it happened one level
down where no tooth was looking. Two axes, two keys, two closed vocabularies is the fix.
render_tier folds into the hash, a plate re-rendered at HERO gets a different filenamefrom its REVIEW predecessor by construction — the same self-enforcement the licence class already
gets, applied to resolution.
docs/FACTORY_CONTRACT.md carries the per-chapter obligations that ride this section, so the art
program is measured by the same machinery that measures everything else: FR-083 (key every plate
on a live canon id), FR-084 (deliver against the per-class floors, counted not asserted),
FR-085 (the Lane-G care refusal, as arithmetic), FR-086 (a resolvable licence record per
generated plate), FR-087 (previz honesty and the marketing-clear publish gate). All five are
SEED: they surface in every chapter checklist and gate nothing yet, which is the honest state.
Git carries rows and hashes only — the landed rule (docs/art_boards/README.md: "Plate BINARIES
never enter this repo"). The arithmetic behind it, now that the corpus is sized:
| Plate corpus, full game at target | 286.8 GiB (docs/concept_art/art_throughput_plan.json) |
|---|---|
| Entire packed repo history | 43.71 MiB (git count-objects -vH) |
| Ratio | ~6,700 × |
E: free (the ruled art root's drive) | 7.3 TB → the corpus uses 3.8 % |
Storage does not constrain this program. Git would have — by four orders of magnitude. The
pointer discipline is not fastidiousness; it is the only thing that keeps the repo clonable.
harness/check_art_pointers.pySpecifying this was the brief; a landed gate beats a spec, so it is landed and mutation-proven.
The hole it closes. read_boards.py T-PLATE-FILE checks that a declared asset_path exists and
is non-empty (read_boards.py:33). sha256 is a live PLATES.csv column carried into the critics'
worklist and no tooth in the 42-gate roster reads it. Today, before this commit: a plate file
swapped on disk passes; a complete row with an empty sha256 passes; a regeneration that
overwrites its predecessor passes; and the entire table passes on any machine where
HUMANITY_ART_ROOT is not mounted, because a path that is never resolved never fails.
| tooth | what it makes unrepresentable |
|---|---|
P0-VACUITY | a missing/unreadable/empty PLATES.csv, or one missing a required column, reporting the same green as a table that was checked |
P1-COMPLETE-CLAIM | generation_status=complete without a path and a 64-hex sha and a prompt hash |
P2-PATH-FORM | an absolute path, a drive letter, a .., a backslash, or the wrong depth — a row that resolves on exactly one machine |
P3-CONTENT-ADDRESSED | a filename that is not __<hash12>.<ext>, or whose hash12 disagrees with generation_prompt_hash — i.e. a regeneration that *could* overwrite its predecessor |
P4-PATH-COLLISION | two rows claiming one file with different sha256 |
P5-DUPLICATE-CONTENT (WARN) | byte-identical images under two plate ids — at volume, the generator returning one image for four seeds |
P6-INTEGRITY | bytes on disk that are not the bytes the row approves. Byte-exact, no tolerance |
P7-BLIND-MODE | a gate that cannot see the drive reading exactly like a gate that looked. With the root absent it runs MANIFEST_ONLY, says so on the scorecard, and FAILs any row claiming complete |
P7 is the tooth this repo's own history demands: GATE 36 and GATE 39 both exist because a check
that cannot match reads as a check that passed. An unmounted volume is that same defect wearing a
drive letter.
Proof, not assertion. 8 teeth · 20 must-fire fixtures + 2 must-not-fire baselines, run on every
real invocation (exit 3 if a ruler rotted). All 8 were additionally mutation-proven **live against
docs/art_boards/PLATES.csv**, each mutation firing its named tooth and no other, with the file
restored byte-exact (sha ed9dd9ec… before and after). One mutation — complete with an empty sha,
on a machine with no art root — legitimately trips two teeth (P1 and P7); re-run with a root
present it fires P1 alone, which is the isolation proof.
It runs green today: MODE=MANIFEST_ONLY declared=0 complete=0 verified=0 PASS. Every plate in the
repo is still pending, so there is nothing yet to verify — and the gate says which of those two
things it means, which is the point.
---
Not a published benchmark. A real timing sample against the live ART ComfyUI instance
(127.0.0.1:8189, the only instance that exists on this box —
docs/5090_SETUP_RUNBOOK.md:1276), day window, GPU otherwise idle, 16 generations.
| tier | resolution | steps | s/plate | plates/hr | MB/plate | peak VRAM |
|---|---|---|---|---|---|---|
| ITERATION | 1024² | 8 | 2.78 | 1,295 | 1.53 | 20,653 MiB |
| ITERATION (batch 4) | 1024² | 8 | 2.91 | 1,239 | 1.64 | 20,623 MiB |
| REVIEW | 1344² | 8 | 5.35 | 673 | 2.75 | 20,623 MiB |
| HERO-fast | 2048² | 8 | 15.04 | 239 | 6.64 | 20,623 MiB |
| HERO | 2048² | 20 | 34.38 | 105 | 6.67 | 20,344 MiB |
Generator: Z-Image-Turbo bf16 (Tongyi-MAI/Z-Image-Turbo, Apache-2.0, SHIPPABLE per
PIPE_ART_2026-07-29.md §1) + qwen_3_4b text encoder, res_multistep/simple, cfg 1.0, shift 3.0.
Record: docs/concept_art/art_throughput_measurements.json. Re-measure with
python harness/art_throughput.py --measure.
Finding worth more than the headline: BATCHING IS NOT A LEVER. Batch 4 is *slower* per plate
(2.91 s) than batch 1 (2.78 s). The queue is compute-bound, not overhead-bound, so no scheduling
cleverness buys throughput — only resolution and step count do. Recorded because the obvious
optimisation is the wrong one and someone will otherwise spend a day on it.
A one-stage model is what makes art schedules lie. The real pipeline: DRAFT k=4 candidate seeds
per plate at ITERATION tier (you do not ship the first sample) → REVIEW the one selected
candidate at REVIEW tier for the leaf page → HERO only APPROVED plates at HERO tier → REGEN
10 % of plates repeat DRAFT+REVIEW after a comment. k and the 10 % are PARAMETERS, printed with the
plan so nobody mistakes them for measurements.
| scope | plates | DRAFT h | REVIEW h | HERO h | REGEN h | TOTAL GPU-h | nights | GiB |
|---|---|---|---|---|---|---|---|---|
| P0 law set (slice) | 465 | 1.4 | 0.7 | 4.4 | 0.2 | 6.8 | 0.8 | 7.5 |
| Slice FLOOR | 2,525 | 7.8 | 3.8 | 24.1 | 1.2 | 36.8 | 4.6 | 40.5 |
| Slice TARGET | 3,471 | 10.7 | 5.2 | 33.1 | 1.6 | 50.6 | 6.3 | 55.7 |
| Full game FLOOR | 12,799 | 39.5 | 19.0 | 122.2 | 5.9 | 186.6 | 23.3 | 205.3 |
| Full game TARGET | 17,876 | 55.2 | 26.6 | 170.7 | 8.2 | 260.7 | 32.6 | 286.8 |
Emitted by python harness/art_throughput.py → docs/concept_art/art_throughput_plan.json. Not one
of these numbers is typed into this document by hand.
**The headline: the entire Prologue→Ch 13 art slice is ~6 nights of unattended GPU. The entire game
is ~33 nights.** Against ~8.8 h and ~52 h of Josh respectively. The GPU is not the constraint and
never was — the review surface is, which is what §2 is for.
docs/5090_SETUP_RUNBOOK.md:1572 (D-11b) rules: *"keep 04:00 as the serialized nightly reservation;
never overlap asset-gen on VRAM."* That ruling is now also a measurement: the ART lane peaks at
20,653 MiB of 32,607, leaving 11,954 MiB — not enough to hold a UE 5.8 editor session with a
loaded landscape beside it with any margin.
the 04:00 soak and does not resume until the soak reports.
demand while Josh is actually commenting. A comment answered in the same sitting is worth more
than a night of throughput, and it fits in the headroom.
always wait for the night.
PIPE_ART_2026-07-29.md §1 rules HiDream-O1-Image-Dev (MIT, 28 steps) the T2I PRIMARY and
Qwen-Image-2512 the co-primary. Neither is installed —
models/diffusion_models/ holds z_image_turbo_bf16 only. Every figure above is therefore a
ceiling on speed and a floor on cost.
Isolating the fixed term from the two 2048² points — (34.38 − 15.04)/12 = 1.612 s/step, fixed
= 2.147 s — and scaling the sampling term to 28 steps: **the full-game target becomes ~396 GPU-hours
(≈49.5 nights)** if HiDream replaces Z-Image at equal resolution. The schedule is stated at both ends
so nothing rests on the fast model staying the pick. (The ITERATION multiplier ×1.57 uses the
2048²-derived fixed term, which understates it at 1024²; treat it as a lower bound.)
---
Josh's ask: *"you develop those pages and I approve them or make comments on how to adjust or
modify."* Four mechanisms, all four already exist — this section wires them, it mints nothing.
The leaf is published as a default-private artifact page (the ruled medium —
docs/art_boards/README.md "The board medium is RULED"), re-published to the SAME url recorded in
MANIFEST.csv.published_url. Under the rows it renders a PICK BLOCK: pre-formed lines, already
in the ruled intake grammar, behind one copy control.
- [ ] BRD_0142 — A2 · B1 · C-SQ-3 - [ ] PLT_0142_CR_0001_sil — regen: too many dorsal spines, the real Komodo reads smoother - [ ] PLT_0142_CR_0010_beh — hold
That grammar is not invented here. It is docs/JOSH_REVIEW_INBOX.md's ruled line format
- [ ] <TARGET> — <comment>, and python harness/queue.py intake already turns each line into a
work-queue item with its gates, its critic and its derivation attached, ticking the line to
- [x] WQ_00NN in place so intake is idempotent. **Josh copies one block on his phone and pastes it;
nothing is transcribed by hand and nothing is dropped** — an unrecognised target becomes a BLOCKED
item with reason unresolved_target, never a silent drop.
Dependency, stated plainly:docs/JOSH_REVIEW_INBOX.md,harness/queue.pyand
docs/AUTONOMOUS_RUN_LOOP.mdare the run-loop lane's artifacts, landed at04aa2a7e
("GATE 42 queue"). ItsBRD_target row already routes a board id to aconcept-art-region
regeneration item with gatesboards+canon_derivationand the four image critics attached —
which is exactly the receiver §5.2 needs, built independently and arrived at from the other side.
What that row does not yet resolve is a PLATE id. A leaf comments per row, not per board, so
PLT_…must joinqueue.py's target resolver or every plate-level comment lands as a BLOCKED
unresolved_target item. That is one resolver case, it is named here rather than assumed, and
until it lands the honest fallback is a board-level comment naming the plate in its text.
1. intake creates the work item. The comment text is preserved verbatim.
2. The comment joins the prompt payload. harness/generation_prompt_hash.py recomputes
generation_prompt_hash over the closed key set. The hash necessarily changes.
3. A changed hash is the landed regeneration trigger (docs/generation_lifecycle.json
trigger_column: regeneration_trigger): the row moves to regeneration_required — one of the
five ratified states, not a sixth — and regeneration_count increments.
4. Because the hash changed, <hash12> changed, so the new plate writes to a new path. The
predecessor survives as the evidence a rejected output is required to be. GATE 43 P3 is what
makes that true rather than intended.
5. first_pass_rate = boards_with_zero_regenerations / boards_reviewed stays a query over
MANIFEST.csv — which is why regeneration_count is a column and not a memory
(docs/art_boards/README.md "The review cadence is RULED"). It drives the ruled cadence: one leaf
at a time until ~90 % first-pass over a trailing ten, then all remaining at once.
Nothing in that chain required a new state, a new column, or a new hash function. The licence
self-enforcement rides along free: a model demoted to previz_only changes the same hash and fires
the same regeneration, with no one re-reading a licence.
An approved plate is not canon and does not become canon. It becomes a **locked
canon-adjacent reference** by being written into the art bible — docs/art_bibles/<subject>.md, which
REALM_DESIGN_PROGRAM.md §3.6 makes the only artifact the asset factory reads, precisely so
boards never become a second source of truth. The bible field carries plate_id + sha256 +
generation_prompt_hash.
Three properties follow, all mechanical:
assetgen-3d (docs/task_router.json) reads the bible; the bible names the plate; GATE 43 P6 proves the plate's bytes are the approved bytes. The
chain from a 3D asset back to the image Josh approved is hash-verified end to end.
P6 goes red. "Locked"means locked to a sha, not to a filename.
docs/task_router.json › art-bible: a subagent may createAWAITING PICK; only the director writes RATIFIED, "a subagent writing a user ruling into canon
reads as fabricated authority." This program does not touch that line.
---
Ranked by what unblocks the most downstream work per plate, not by cheapness. The tier column of
density_model.csv supplies P0/P1/P2; the ordering *within* and the two hard predecessors are this
document's, and are named as such.
Populate the rows the art is for. T0_Equipment_Registry holds 1 row;
T0_Environment_Grammar_Registry 2; T0_Quest_Seed_Registry and T0_RNG_Drop_Table 0; and
the density model budgets creatures against 420 where 150 exist. You cannot generate a plate
for a row that has not been authored, and a leaf cannot enumerate rows that are not there. This is
registry work (docs/task_router.json), it is zero GPU, and it gates everything below it.
Naming it Rank 0 is this document's call, and it is the single most important line in this section.
One pick sets a rule that applies hundreds of times. Every one of these built *after* dependent art
means regenerating that art.
| # | class | slice plates | why it is first |
|---|---|---|---|
| 1 | D1-34 UI iconography and rarity language | 67 | LANGUAGE_BOARD. The rarity ladder colours, shapes and beam VFX are inherited by every weapon grade, every drop, every card. Building 432 grade variants before this is 432 regenerations |
| 2 | D1-28 Rifts and Hollow surfaces | 15 | LANGUAGE_BOARD. The Hollow's surface grammar recurs across the whole arc |
| 3 | D1-27 Magic and ability VFX — mastery-tier variants | 69 | the per-element visual law. 185 abilities × tiers inherit it; full-game 1,116 plates |
| 4 | D1-23 Weapons — grade variants | 165 | the grade ladder, parametric off D1-34's pick |
| 5 | D1-26 Equipment and carried objects | 15 | includes the Ch-2 realm object, whose art brief is already written in CH_02.md's anchor line and which is carried unbroken to the Ch-13 gift — it is on screen for the entire slice |
| 6 | D1-05 Architecture kits per culture | 134 | every settlement, interior, dwelling variant and civic monument derives from the kit. Kit before dwelling, always |
D1-01 region key art (180 slice plates) per leg, in the ruled build order Ch 2 → Ch 13. Key art
sets palette and light; §7's palette-conformance tooth measures every later plate in that region
against it, so the key art must be picked before the region's volume runs.
Anchor leaves in node order, each derived from that node's ## Asset anchors: creatures and fauna →
folklore beings → NPC personas → bosses → flora/minerals/gather → festivals → interiors. Ch 2 first,
because CH_02.md's anchor block is already complete, already id'd, and is the node the 2026-08-01
failure was supposed to have realized.
D1-32 realms and the fairy-realm material for the Prologue and Ch 1. This is not a scheduling
preference; it is REALM_DESIGN_PROGRAM.md's ruling that the fairy realm and Prologue/Ch 1 are built
LAST, on the matured concept-art pipeline — restated in CLAUDE.md § Model seats.
D1-17 legendary species, D1-24 regional weapon skins (no formula possible — the multiplier is
unruled), D1-25 legendary bespoke weapons, D1-36 cinematic key frames, D1-38 schools, D1-39 runes,
D1-41 dungeon interiors. density_model.csv sizes each at 0 for the slice; that is the model's
own arithmetic, not an omission here.
---
Four adversarial critics per board is the landed bar (docs/art_boards/README.md "The four critics
run BEFORE Josh sees anything"). It does not survive 17,876 plates — and pretending otherwise is
how a quality bar becomes ceremonial. The bar is therefore split three ways by what each mechanism is
actually good at.
Already landed and running on every plate: read_boards.py (18 teeth),
check_canon_derivation.py (10 teeth, D2 residue at zero), check_art_pointers.py (8 teeth).
PROPOSED additions — image-space measurements, numpy/PIL only, ~ms per plate, no model. These are
not taste; each is an arithmetic property whose violation is always a defect:
| measure | catches | why deterministic is right |
|---|---|---|
| luminance std-dev floor | blank, near-blank, blown, and crashed outputs | a plate with no tonal range is broken, never a style |
| perceptual hash (dhash) collision within a class | the generator returning one image for four seeds | at volume this is THE failure mode. It is exact, and P5-DUPLICATE-CONTENT already catches the byte-identical case — this catches the *visually* identical one |
| resolution / aspect conformance | a plate rendered at the wrong tier | arithmetic |
| palette ΔE against the region's locked key-art palette | the generic teal-and-orange drift, per region | this is the real mechanical anti-slop tooth. The key art is picked FIRST (§6 Rank 2), its palette is locked, and every later plate in that region is measured against it. "Culturally registered, accurate-not-generic" (CONCEPT_ART_PROGRAM.md §2.4) becomes a number |
Generation defects at volume are systematic, not independent: a bad prompt template, a wrong
LoRA, a mis-set palette. Systematic defects are exactly what a small sample catches.
To catch a defect running at rate *d* with 95 % confidence, sample *n = ln(0.05)/ln(1−d)*:
| defect rate | sample size |
|---|---|
| 30 % | 9 |
| 20 % | 14 |
| 10 % | 29 |
| 5 % | 59 |
The rule: 14 plates sampled per class × leg. Any systematic defect running at 20 % or worse is
caught with 95 % confidence. And the response is not per-plate: **one sampled failure rejects the
whole batch** back to regeneration. The sample is a tripwire, not a verdict on the plates it drew.
That is a ~140× reduction in critic invocations against per-plate review, with a stated and
defensible confidence — instead of an unstated one.
Every care-gated subject takes all four critics, every plate. The scale is known: **47 of 88
slice site rows are care-gated (53 %), and independently 46 of 84 zone rows are protected true**
with 51 of 84 at care_tier elevated (CONCEPT_ART_PROGRAM.md §0.1, whose zone figure this
document re-counted from the region pages and confirms at 84).
The principle: sample the taste questions, exhaust the care questions. A cultural-authenticity
defect on a protected living tradition is not a statistical matter and a 95 %-confidence tripwire is
not an acceptable answer to it. This also honours the CVD §17.1 calibration in BOTH directions —
docs/art_boards/README.md is explicit that care-tier inflation is itself a defect and "a critic that
only ever pushes toward restraint produces reverent empty places."
failed a floor; that is the landed rule and it does not change.
(§1.1) are covered by a law he picked, but a law can be picked correctly and applied wrongly. Each
batch surfaces 3 randomly drawn parametric plates on the leaf that set their law. It costs seconds
and it is the only thing standing between "the ladder was approved" and "the ladder was rendered."
---
Landed and running:
harness/check_art_pointers.py — GATE 43 art_pointers, 8 teeth, 20 must-fire fixtures + 2 must-not-fire baselines, all 8 mutation-proven live against PLATES.csv with byte-exact restore.
harness/art_throughput.py + docs/concept_art/art_throughput_measurements.json + art_throughput_plan.json — every figure in §4 emitted, re-measurable with --measure, 7 self-test
fixture groups including a monotonicity check on the committed record and an integer-parse check on
every density row (a silent 0 would shrink the schedule and look green).
NOT landed, and named so it is not mistaken for done:
art_quality) and a secondcommit; nothing in §7.1 is running today.
PLT_ target case in harness/queue.py's intake resolver (§5.1). Board-level commentsroute today; plate-level ones do not.
harness/boards/render_board.py renders the landed 3-panel board; the12-row leaf is a new template.
live under D:\ComfyUI\output\timing_*, and are not canon-adjacent, not registered, and not
routed to the art root.
---
| § | claim | re-derive with | |
|---|---|---|---|
| 1 | every registry row count | csv.DictReader over registries/<name>/*.csv | |
| 1 | 84 zone rows / 12 pages | count `\ | -rows under ## Zone Catalog in _source/02_Tier_2_Region_Pages/*.md` |
| 1 | 17,876 / 12,799 / 3,471 / 2,525 plates | harness/concept_art/emit_density_model.py; sum the four columns of docs/concept_art/density_model.csv | |
| 1.1 | 12,840 / 2,368 review units; the cheap/expensive split | plates ÷ art_unit arity, grouped by cheap_or_expensive | |
| 2.1 | 547 lines · max 12 ids · 135 sections · median 9 | scan ## Asset anchors + ## Mechanical anchors bullets across docs/spine/CH_*.md for canon-id tokens | |
| 2.2 | 142 / 833 leaves | expensive units ÷ 12 | |
| 3.3 | 286.8 GiB vs 43.71 MiB | art_throughput_plan.json · git count-objects -vH | |
| 4 | every s/plate and MB/plate | python harness/art_throughput.py --measure | |
| 4.1 | every GPU-hour and night | python harness/art_throughput.py | |
| 7.2 | the sample table | n = ceil(ln 0.05 / ln(1−d)) |
The two numbers in this document that are NOT measured are §2.2's 12 s / 45 s review times and its
20 % comment rate. They are labelled MODELED where they appear, the sensitivity is tabulated at caps
10/12/16, and they should be replaced by a measurement after the first ten leaves — at which
point §2.2's table is re-run and the cap is re-derived rather than defended.