assets/ASSET_GALLERY_LAW.md
"Yeah I need you to be showing me progress. All that's in our build folders are jsons I can't
view as photos. You need to be updating our webpage. I need to see all the base models, the
undergarment passes, the equipment and clothing passes, every asset as generated, architecture,
fauna, bosses, weapons, etc. Did you forget about our webpage?"
— Josh, 2026-08-06
Every generation lane up to this point ended at a mesh on disk and a record describing it. Both
are real work and neither is viewable: a .glb does not open on a phone and a .json does not
open anywhere. Two hundred and ninety-two finished assets therefore read, to the only person whose
read matters, as nothing at all. The lanes were not lying about their output — they simply had no
obligation to make it visible, so nobody built the surface that would have.
A generation wave is not DONE until its assets appear in the gallery. Concretely, every lane
that produces meshes, plates, or any other visual asset closes its wave by running:
python harness/site/render_asset_previews.py # renders anything new; cached assets cost nothing python harness/site/build_progress_site.py --deploy
and reporting the gallery URL for what it landed. A wave that reports finished assets with no
gallery entries is un-DONE, and the director treats it the same way as a wave that reports a
green gate it never ran.
Three clauses carry the weight:
1. NO ASSET IS SKIPPED FOR WANT OF A PICTURE. If a mesh exists, it gets rendered — clay for
the geometry, textured for the surface. "Only a JSON exists" is not a reason to omit an asset;
it is the exact defect this law was written against.
2. A GAP IS NAMED, NEVER ABSENT. An asset that genuinely cannot be rendered — a registry row
with no mesh yet, an id held off import — appears on its class page as a named gap carrying its
stage and the reason. Silence about a gap reads identically to the gap not existing.
3. THE TIER IS DERIVED, NEVER CLAIMED. Every card's rung comes from evidence on disk (is there
a mesh; is there a full baked surface set; is it cooked into the build). AAAAA-VERIFIED has no
derivation path at all and the emitter's self-test fails if anything ever occupies it.
Name this file. The obligation is one line:
Gallery close. At wave close run harness/site/render_asset_previews.py then
harness/site/build_progress_site.py --deploy, and report the gallery URLs for the ids this
wave landed. Per docs/ASSET_GALLERY_LAW.md, a wave without gallery entries is un-DONE.
Nothing else is required of the lane. The gallery is **script-emitted from the records the lane
already writes** — build/3d/candidates/<lane>/<id>/generation.json, the staging manifests, and
the meshes themselves. A lane that writes its normal records gets its assets into the gallery for
free; a lane that skips them is invisible, which is the incentive pointing the right way.
| piece | path | what it does |
|---|---|---|
| the renderer | harness/site/bl_asset_preview.py | Blender headless; clay + textured off ONE import so the two presentations are the same mesh at the same camera |
| the render driver | harness/site/render_asset_previews.py | walks the mesh estate, renders what is missing, emits build/gallery/render_index.json. Resumable: a cached asset costs nothing |
| the gallery emitter | harness/site/asset_gallery.py | joins renders, concept plates, factory records, staging manifests, canon rosters and engine imports into build/gallery/gallery_data.json, and emits the pages |
| the pages | <site>/gallery/ + <site>/gallery/<class>/ | one shelf, one page per class: characters, equipment and clothing, weapons, creatures, bosses, architecture, props, world |
Renders live at D:/assetgen/renders/gallery/ — outside the canon repo, for the same reason the
concept plates live at E:/art/boards. The repo carries the index and the emitter; the site build
downscales the pixels into the deployed tree at publish time.
"The photo book webpage also is still so outdated and this should be updated as it goes. The
sections etc are not correct for each chapter and this should be the concepts your pipelines
compare to and inspire from, and needs to show how they look wired into the game. We need to
build the game as we go and land things into the game."
— Josh, 2026-08-06
The gallery answers *what exists*. The chapter photo book answers *what this chapter needs, what
the pipeline is comparing itself to, and how far the thing has got* — and it was going stale on
exactly the schedule the gallery was written to end, because it rendered one hand-written list of
nineteen categories on all fourteen pages regardless of what each chapter's canon actually names.
ONE RITUAL, BOTH SURFACES. The close command is unchanged — build_progress_site.py --deploy
emits the gallery AND the chapter pages in the same run — so the obligation is a widening of the
report, not a second command:
Gallery and photo-book close. At wave close run harness/site/render_asset_previews.py then
harness/site/build_progress_site.py --deploy, and report both the gallery URLs for the ids
this wave landed and the chapter URLs whose sections those ids appear under. Per
docs/ASSET_GALLERY_LAW.md, a wave without gallery entries is un-DONE; a wave whose assets
appear in the gallery but reach no chapter section is wired to nothing, and the lane says so
rather than reporting green.
Three clauses carry this half:
4. THE SECTIONS ARE CANON'S, NEVER A TEMPLATE. A chapter's sections are parsed from its own
docs/spine/<node>.md — the ## Asset anchors and ## Mechanical anchors bullets plus the
four subject-bearing blocks (key sites, boss structure, side-questlines, key allies). Each
section prints the file and LINE it came from. chapter_anchors.self_test() fails the build if
every chapter parses to the same list, because that is the defect this replaced and it would
otherwise look fine.
5. MOTION IS SHOWN AS MOTION. Abilities and weapons are stepped, never a single still: the
concept ladder is the D1-27 tier bands (novice → adept → mastery, cast and impact) off
harness/ability_vfx/avx_records_*, and the in-engine half is the driven capture out of
QA/combat_feel/*.json with its measured first-effect and cast-beat latencies. An ability with
no ladder, or a ladder with one rung, says so and names the tool that would close it.
6. THE CONCEPT IS THE COMPARATOR, AND IT NAMES WHAT IT IS COMPARED TO. Where a region's art
bible is RATIFIED, the chapters it covers carry the ratified reading at the head of the page,
parsed off the bible with its pick line and its binding conditions. A comparator surface that
does not name the reading it is held to is a mood board.
A character is a special case of clause 5 and gets its own rule: **a character is a STACK, not one
object.** The paper-doll strip — base body, merged base, head, then each garment layer, then the
equipment slots — renders in order with a rung per cell, on the gallery's character page and on the
chapter sections about the protagonist's look. The stage ORDER is declared; the members RESOLVE
against whatever is on disk, so a lane landing a textured leg pass fills that cell on the next emit
with no edit here. A layer with no render is a named gap cell. One composited render standing in
for the stack is the defect this clause exists against.
"If those base body and layer stacks exist and the passes why arent we seeing final of
everything on the webpages and making sure we have things properly organized chapter by chapter
with protagonist, characters, abilities, gear, weapons, aerials of towns and arenas and boss
stages and building layouts and caverns etc, then walkthroughs of everything, all its own
subpages. This is the floor. Remember I cant view jsons and other layers as images. You need to
make sure theyre all viewable by me for review."
— Josh, 2026-08-06
Two clauses, and they are the standing form of the ruling recorded in
docs/spine/DECISIONS_PENDING_JOSH.md under THE VIEWABILITY FLOOR + CHAPTER IA.
7. EVERY LANDED ARTIFACT RENDERS IN THE SAME WAVE IT LANDS. Not every asset with a *record* —
every artifact: each mesh, each layer state, each pass, each rig state. A JSON-only or
record-only landing is un-DONE. Greybox renders publish, with their honest chip, because
the review is of the actual state and not of a curated absence. The trap this clause was
written against is not a missing renderer: it is a mesh estate whose *source table* has no glob
for a lane's output, so the lane's meshes are invisible to the renderer and to everyone. Two of
the protagonist's own layer states — the underwear-grade merged base and the arrival garment as
one layer — sat on disk for a day in exactly that state. **A lane that produces meshes in a new
directory adds its tier to render_asset_previews.SOURCE_TIERS in the same commit**, and the
close report gives the estate's no_render count, which must be zero.
8. THE IA IS THE REVIEW SURFACE, AND IT IS ORGANIZED THE WAY A REVIEW GOES. chapter_ia.py
emits a hub, a per-node index and a subpage per domain, regrouping the canon-ordered sections
into the nine domains Josh named (plus creatures, and a named catch-all). It derives nothing new
— it re-routes what the chapter pages already join — so the two surfaces cannot disagree. These
pages are held to the PUBLISHED template of
docs/pipeline_review/PAGE_CURATION_AUDIT_2026-08-06.md §4.2 and are swept with the **full
curation rule-set**, the missing-chip rule included.
AND NOTHING PUBLISHED READS final TODAY. The viewer-word map was corrected in the same
sitting: POLISH — a cooked engine asset — now reads greybox, because final is a claim about a
*graded* capture and no capture in this estate has been graded. With AAAAA-VERIFIED underivable
from disk by construction, no chip this repo can emit says final, which is the structural form
of the honest register rather than a habit anyone has to remember.
harness/site/firewall.py sweeps every emitted page, with its positivecontrol fired first, and the module REFUSES to write into the site tree on any hit rather than
laundering the text that tripped it. This is not theoretical: the first build of the gallery was
refused, because EQ_0001's registry name is the held realm object. The fix was to publish the
picture and withhold the name, saying so on the card — not to rewrite the string until the
sweep went quiet.
build_progress_site.IMAGE_POLICYlike every other image on the site. A new render directory does not quietly become publishable;
it gets a declared policy line or it is withheld.
character rosters is reported as a number. Listing 284 boss names on a public page would give
away the game to buy a completeness statistic.
canonical_chapter_acquisition andsimilar; the gallery never prints them, because a chapter number past the slice leaks structure
the player has not reached.
asset_gallery.self_test() runs on every emit and has five ways to fail: tiers off the ladder; a
full surface set that does not derive CONTENT-COMPLETE; **removing the surface set and watching the
rung fail to demote** (the mutation control — a tier that survives its evidence being deleted was
never derived from it); totals that are not a walk of the assets; and the held-identity guard,
which must fire on a held canon name and must NOT fire on ordinary text. AAAAA-VERIFIED being
occupied is itself a failure.
Every creature/NPC card - gallery AND chapter photo-book - carries a PHYSICAL PANEL rendered
from its bestiary-sheet data the moment the sheet declares it: weak/strong ZONES (named,
with their biology grounding), MASS/density, body MATERIALS, and its DAMAGE-MATRIX rows
(puncture/blunt/slash/elements effectiveness). Sheets without the fields render the panel
as a NAMED GAP row, never absent. The same fields land ENGINE-READABLE in the same wave
(the registry-to-engine table the engagement ledger seam consumes) - a wave whose physical
data exists only in docs is un-DONE, same as a wave without gallery entries. The first lane
to land a sheet carrying the fields extends the card emitters data-driven (fields present
render, absent gap) so every later wave inherits the panel for free.