ASSET_GALLERY_LAW.md

assets/ASSET_GALLERY_LAW.md

THE ASSET GALLERY LAW

"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

The defect this closes

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.

THE LAW (standing, from 2026-08-06)

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.

How to satisfy it in a lane charter

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.

What the surface is

piecepathwhat it does
the rendererharness/site/bl_asset_preview.pyBlender headless; clay + textured off ONE import so the two presentations are the same mesh at the same camera
the render driverharness/site/render_asset_previews.pywalks the mesh estate, renders what is missing, emits build/gallery/render_index.json. Resumable: a cached asset costs nothing
the gallery emitterharness/site/asset_gallery.pyjoins 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 SECOND SURFACE (extension, 2026-08-06): THE CHAPTER PHOTO BOOK RE-EMITS TOO

"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.

THE THIRD SURFACE (extension, 2026-08-06): THE VIEWABILITY FLOOR AND THE CHAPTER IA

"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.

The disciplines it inherits

control 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.

like 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.

similar; the gallery never prints them, because a chapter number past the slice leaks structure

the player has not reached.

Positive controls

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.

THE PHYSICAL PANEL (Josh 2026-08-06: "Build it into the game and photobook webpages as you go")

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.

Generated by harness/site/structure_site.py — the URL path is the repo path. review root