systems/LOOKDEV_STANDARD.md
The ruling this realizes, verbatim. *"CALIBRATED LOOKDEV as the polish judge: the W-LOOK
standard (declared HDRI set, grey/chrome/Macbeth, de-light mandatory) becomes the light under which
'textured and polished' is graded — polish claims under uncalibrated light are not claims."*
(Josh, 2026-08-06 night, THE FACTORY ENHANCEMENT ROSTER item 4;
docs/spine/DECISIONS_PENDING_JOSH.md L4712-4714.)
Authority. OPERATIONAL STANDARD. It rules nothing about the world — not a chapter, not a
region, not a creature. It rules how a LOOK CLAIM is evidenced. Canon, the CVD and Josh's rulings
bind every line of it, and Josh's eye outranks every number in it. It is the standard's job to make
his judgement reproducible, never to substitute for it.
The law, on the face of the doc. A polish, texture, material or appeal claim made under
uncalibrated light is not a claim. It is an impression of a lighting setup. From this point on,
a claim of the form "this asset is textured / polished / finished" cites a LOOKDEV RECORD produced
under this standard, or it is refused — the same way an appeal claim already cites a judged plate
ladder and a fit claim already cites a signed-distance distribution.
What this standard is NOT. It is not a beauty rubric. Every instrument here measures light,
range and round-trip; not one of them measures whether an asset is good. The appeal rubric
(harness/qa/comparator_rubrics/character_appeal.json) and the cold judge keep that job, and Josh
keeps the final one. The honest register is unchanged by anything in this file: every generated
asset class remains GREYBOX until its plates pass Josh's graded floor, and calibrated light does
not upgrade a tier — it only makes the grading trustworthy.
---
docs/pipeline_review/AAA_WORKFLOW_CORRESPONDENCE_2026-08-06.md §3.13 (L832-868) scores our
lookdev stage PARTIAL and names the missing half exactly: *"What is missing is the STANDARD.
There is no declared neutral-HDRI set, no grey/chrome/Macbeth convention, no per-asset lookdev
sheet, and no multi-environment requirement."* Two consequences were already measured before this
wave existed, and both are the kind a calibrated rig catches BEFORE a capture rather than after:
change** (APP-C5; build/3d/characters/CHAR_0001_FACE_PIVOT_RECORD.json
gaps_named_not_papered_over), carried as AAAAA register row 6, cost class free/config
(docs/pipeline_review/AAAAA_IMPROVEMENT_REGISTER_2026-08-06.md L74). The de-light pass existed
and was simply never run on it.
against a 128,000 lux key, 0.0065% of key and 36× weaker than that map's own "trace" fill
(register row 26, L99). A grey ball in frame is how that becomes visible in one glance instead of
in an arithmetic audit.
§3.6 (L387-456) adds the two checks the dossier says we do not have at all and could build cheaply:
a PBR range validator against the published 30-240 sRGB dielectric band, and **Guerrilla's rule
that review happens in engine at ship LOD** rather than on an offline render. This standard is both,
plus the environment set and the reference geometry, plus the de-light pass promoted from optional
to mandatory.
---
The set is declared in GAME:Tools/lookdev/hdri_set.json and fetched by
GAME:Tools/lookdev/fetch_hdri.py, which refuses any file whose md5 does not match the manifest —
a silently-wrong environment would corrupt every judgement made under it, which is worse than
having no environment at all. Total spend: zero. All four are Poly Haven CC0
(https://polyhaven.com/license); the author is recorded per row anyway, because a licence with no
source of record is not a licence.
| Slot | HDRI | Author | What it is FOR |
|---|---|---|---|
| E1_NEUTRAL_STUDIO | studio_small_09 | Sergej Majboroda | THE JUDGING LIGHT. Every appeal, texture and polish judgement is made here first. Medium contrast, soft key, real fill — a hard key hides roughness errors inside blown highlights. It is also the most-downloaded studio HDRI in the open ecosystem, so a reviewer elsewhere can reproduce our light exactly. |
| E2_SUNLIT_EXTERIOR | kloofendal_43d_clear_puresky | Greg Zaal | THE HARD-LIGHT TEST. Clear midday sun on a clean sky, so the only directional source is the sun and the grey ball's terminator reads as a light-direction measurement. Blown speculars and plastic roughness surface here first. |
| E3_OVERCAST | overcast_soil_puresky | Sergej Majboroda / Jarod Guest | THE FLAT-LIGHT TEST, and the de-light verdict. A near-uniform dome with almost no directional cue. Under it an honest albedo goes flat and a baked key light does not — this is the environment that convicts a plate-derived basecolor by eye. |
| E4_NIGHT_INTERIOR | fireplace | Greg Zaal | THE LOW-LIGHT / WARM-PRACTICAL TEST. A single warm practical in a dark interior. Dark albedo that crushes to black and skin that goes orange-dead surface here. Chosen over an office-strip-light night interior because the slice's night beats are firelit camps and interiors, not offices. |
4k is the declared working resolution. 2k rows are carried in the manifest so a contended or
low-VRAM run can degrade DELIBERATELY and record which rung it used, instead of silently
substituting a different environment.
Adding a fifth environment is a change to this file, not a per-lane choice. The whole value of
a declared set is that two sheets from two lanes on two days are comparable. A capture made under
an environment not in this manifest is not a lookdev capture.
---
Every lookdev capture carries all three calibration objects in the same frame as the subject, at
fixed position, under the same light. Reviews arriving without them are returned before the asset is
looked at (the convention the dossier records at §3.13). What each one is for:
environment's exposure, which makes two sheets comparable only when this number is. It is also
where light direction, intensity and shadow become readable at a glance, which is what makes an
arithmetically-dead rim light visible without arithmetic.
sheet and never reduced to a number, because what it carries is a picture of the environment.
values come out, and **one linear scale per channel, fitted across the six neutral patches, must
then explain all twenty-four**. Residual non-linearity after that single scale means a transfer
function is wrong somewhere in the pipeline.
The chart's honest claim, stated so it is never overread. The reference values are the
conventional X-Rite/BabelColor sRGB set, not a measurement of a physical chart in this room. So this
is not an absolute colorimetric claim — it is a ROUND TRIP, and a round trip detects a broken
transfer function whatever the absolute reference is. Under a hard directional environment the chart
legitimately clips at exposure 0 and the residual rises for a reason that is exposure and not a
pipeline fault; every record therefore carries patches_clipped, and **E1_NEUTRAL_STUDIO is the
environment whose residual is the pipeline check**. The other three are informational.
The rig is a constant and the subject moves. A turntable is a rotating TABLE: the subject turns
on a pivot while the camera, the balls, the chart and the environment stay fixed. Rotating the
camera instead would drag the calibration objects through the light and destroy the one property
they exist to have. Any change between two sheets is therefore the ASSET changing, never the rig.
---
One sheet per asset per variant, produced by GAME:Tools/lookdev/run_lookdev.py (which drives
bl_lookdev_sheet.py in headless Blender and composites the contact sheet). Shape:
sheet reads on a phone in one vertical scroll per the standing boards law.
view transform, look, exposure, gamma, renderer, sample count, device, and the subject's triangle
count.
chart's fitted scale with its neutral and chromatic residuals.
THE CALIBRATION LOCK, and why it is Standard and not AgX. Renders are Cycles at view transform
Standard, look None, exposure 0.0, gamma 1.0, world strength 1.0. AgX is a
display look: it is the right choice for a beauty frame and the wrong one for a measurement, because
it deliberately does not round-trip a known sRGB value, and the chart is only a pipeline check if a
known input can be compared to a measured output. A beauty pass under AgX is a **different artifact
and must say so on its face**.
A/B arms are re-rendered by the instrument doing the comparing. Both arms of any comparison run
at identical camera, lights, views and samples — the discipline made law in
docs/pipeline_review/tech_research/PIPE_CHARACTER_MODELS_2026-08-06.md §2.1 after a bench compared
one lane's cavity-shaded clay against another lane's flat turnaround and read backwards. The A/B
composer (run_lookdev.py --ab) reads two render reports and can only pair frames that came from the
same rig.
---
The rule. Any basecolor that is CAPTURED, PHOTOGRAPHED, PLATE-DERIVED or GENERATED passes
through the de-light stage before it is treated as albedo. There is no route in which it is skipped
because a lane was busy — that is exactly what happened to the pivot head, and the dossier's own
verdict is that *"the de-light stage exists and skipping it is a scheduling failure, not a
capability gap"* (§3.6).
Where it sits in the chain. Immediately after the texture bake and before any material
assembly, any import, any lookdev sheet that will be used to judge appeal, and before the albedo
validator's verdict is recorded as the asset's band. Our ratified texture route already emits the
inputs it needs — the MV-Adapter bake writes an object-space _NRM.exr normal pass and a _MASK.exr
coverage pass beside every basecolor — so the stage costs a pass over a texture and no new capture.
The tool path. GAME:Tools/lookdev/delight.py. Method: an order-2 spherical-harmonic fit of
log-intensity against the texel's own object-space normal, per colour channel, two trimmed robust
passes so a specular highlight does not drag the fit; the directional bands (l = 1, 2) are divided
back out and the masked per-channel mean is preserved. It removes the light's DIRECTION and colour
gradient; it does not re-expose and does not grade. Comparators in the public record: Unity's
open-source De-Lighting Tool (lit diffuse plus object-space normals, bent normals and AO) and
Agisoft De-Lighter (no additional maps) — both cited in dossier §3.6.
Its declared limits, printed on every record it writes.
false of a material that genuinely changes with facing. Declared for captured and generated
basecolors, never for hand-authored art.
SH-2 directional component, so re-fitting SH-2 to its own output must return near zero. The
load-bearing numbers are the BEFORE figure, the visual A/B under E3_OVERCAST, and the self-test,
where ground-truth albedo is known and the level-matched recovery error is measured rather than
assumed.
The refusal. Below 0.20 stops of fitted directional term the pass declines to run and says so:
a basecolor that carries no light must not be "corrected", or the tool is inventing a change. Below
64 masked texels it refuses outright — nine coefficients fitted to a handful of texels is noise
wearing a correction.
---
The band. Dielectric base colour sits between roughly 30 sRGB (tolerant; 50 strict) and
roughly 240 sRGB — charcoal at ~4% albedo lands near sRGB 50 and fresh snow sits at the top, and
nothing real sits outside (racoon-artworks, "PBR: from rules to measurements", via dossier §3.6).
Measured per texel as Rec.709 luminance computed in LINEAR light and re-encoded to the 0-255 sRGB
scale — averaging the encoded channels instead is the classic gamma error and moves the measured
value by several sRGB units on saturated pigment, which is the size of the effect being gated.
Why it is a lighting detector wearing a colour check. A basecolor with lighting baked in carries
the capture's shadow terminator and specular highlight as pigment. Shadow crushes below the dark
bound; highlight blows past the bright one. That is why the band check and the de-light pass are one
wave and are run as a pair.
The metal band is DERIVED-PROVISIONAL and labelled so on every record. Metal base colour is the
F0 reflectance, roughly 0.5-1.0 linear (gold ~255/226/155, silver ~252/250/245, iron ~198/198/200).
No source publishes a single agreed numeric gate, so the tool declares 150-255 to leave headroom
below the darkest common metal rather than to assert a measurement, and it REPORTS rather than
refuses on that class until a population exists to calibrate it. This follows the same discipline
texel_coverage.json was built with, and the same one the ROM spec's M5/M6 bands are held to.
The masking rule, because an atlas gutter is not albedo. Unused atlas space is black, and
counting it would fail every texture on a defect it does not have. Resolution order is declared and
recorded, never guessed: an explicit --mask, then a sibling _coverage.png, then the texture's
own alpha, then all texels with the substitution named in the record. A mask that selects zero
texels is a VACUOUS MEASUREMENT and fails — it is never a clean texture.
The ratchet, on the standing declared-backlog pattern. Run against a baseline the validator
behaves like build/3d/schema/anatomy_conformance_baseline.json: a finding not in waived FAILS, a
waiver naming a texture the run never measured FAILS, and **a waiver that matched nothing measured
also FAILS** — so the list can only shrink, and it shrinks in the same commit as the fix.
Tool path. GAME:Tools/lookdev/albedo_validator.py. Exit-code bearing, pure numpy plus Pillow
so it runs under the plain system interpreter, with per-texel violation-map and histogram evidence
written as images.
---
The rule, and it outranks every offline number in this document. Final look review happens in
the engine, at ship LOD, on the target spec classes. Offline renders — including every sheet this
standard produces — are WORKING EVIDENCE ONLY, and every sheet says so in its own footer.
Why it is stated as a rule rather than a preference. The best gate argument in the public record
is a failure report: Guerrilla's own Horizon Forbidden West postmortem says review happened too much
on Maya rigs and not enough in engine at LOD, and normal maps were baked only for PS5 — so
characters shipped *"overly smooth on PS4"* (dossier §3.6, L418-421). Our appeal judgements have
been running on offline Blender renders, which is the same review-surface mistake.
What the in-engine review must additionally carry here, derived from our own doctrine. It is not
enough to look once in the editor:
docs/ENGINE_OPTIMIZATION_DOCTRINE.md §5.3 makes the LOD chainGATE-TIER, and the dossier §3.14 records that no gate reads the budgets. A look approved at LOD0
and shipped at LOD2 is the Guerrilla defect in our own units.
a per-tier paired capture at minimum AND recommended spec, judged on one question, because a
compliant implementation can silently degrade what it was supposed to preserve (§5.4, L1417). Look
approval inherits that shape.
three ruled spec tiers with an authored MegaLights-free path at minimum spec (L1133, FINDING
W3-4). An asset approved only under MegaLights has not been approved at minimum spec, and the
fallback is authored, never discovered when a min-spec capture goes dark.
Status: OWED, and named rather than quietly deferred. The in-engine half is specified here and
is not executed. Engine access serializes on GAME:.engine_lane_lock, which at this wave's close is
held by the director's own engine window (`engine-window (hf parallel director seat, jmilks) …
runs W8x7 + laneAx4 + laneBx6 then releases) with UnrealEditor-Cmd.exe` live inside it.
Wait-never-steal applies, and that window already reserves this lane's engine items. These are those
items, ordered, so the window can run them without re-deriving anything:
T_CHAR_0001_FACE_BC_DELIT.png and T_CR_0002_BC_DELIT.png against the existing materials, then
capture each asset at LOD0 and at ship LOD, in the same shot, with metadata read back the way
every other import already reports it. A look approved at LOD0 and shipped at LOD2 is the
Guerrilla defect in our own units.
HWRT, Lumen High + MegaLights, and Lumen Lite with the **authored MegaLights-free minimum-spec
path** (ENGINE_OPTIMIZATION_DOCTRINE §7.3, R-08, FINDING W3-4). An asset approved only under
MegaLights has not been approved at minimum spec.
same LOD, judged on one question — does the material read as the same material at both classes?
A fail re-authors the scalability entry, not the sheet.
exist as engine assets with the same declared values, or the in-engine capture is uncalibrated in
exactly the way this standard was written to stop. Their creation is part of the first engine
window that runs A-ENG-1, and every capture above carries them in frame.
Until those four land, every look number in this repo is off-engine working evidence and must be
labelled as such.
---
Armed as a gate: lookdev_albedo in harness/gates_config.json, running
harness/check_lookdev_albedo.py. It executes the must-fire / must-not-fire self-tests of all three
instruments — the albedo validator, the de-light pass, and the HDRI set's shape and md5 plumbing —
and refuses a vacuous pass if zero instruments resolve.
What the gate deliberately does NOT gate, stated so a green run is never overread. It does not
gate any asset's actual albedo band. The instruments read texture bytes that live on the D: working
tier and never enter either git repo, and a gate whose green depends on unversioned media goes red
for reasons that are not defects. The corpus verdict lives in each asset's LOOKDEV RECORD. What the
gate asserts is that the instruments still fire, which is the property this repo's named failure
class destroys: a check that is not in the runner is not armed.
Not armed, and named: the in-engine at-ship-LOD review (§7, owed on the engine lock); a
project-wide texel-density standard (dossier §6 rank 10, a different wave); any band threshold on
the metal class; any automatic enrolment of every basecolor in the corpus into a per-run sweep.
---
Run 2026-08-07 on the protagonist head and the limbless serpent, every pass imaged to
GAME:QA/lookdev/. Four sheets, 128 calibrated frames, two de-light records, four albedo-band
records with per-texel evidence images.
The named exemplar defect was caught and measured. The CHAR_0001 pivot head's baked plate
lighting — register row 6, previously carried as a prose note — now has a number:
surface normals (a 3.15× light-to-dark swing baked into pigment). After the pass, the level of the
map is unchanged and the directional term is gone.
0.0092, and the 1st-percentile value moved 27.3 → 31.0 sRGB**, i.e. from outside the tolerant
dielectric bound to inside it.
reading is the more interesting one and is reported as it measured, not as it flatters:
13.43% of masked texels sit below the 30 sRGB bound before the pass and 12.26% after. That
is a real finding about the serpent and NOT a de-light failure: most of that darkness is pigment
and hole-fill, not directional light, and the pass correctly declined to brighten pigment.
The instrument's own positive control. On a synthetic fixture with known ground-truth albedo, a
known normal field and a planted warm key, the pass takes the level-matched albedo error from
0.1475 to 0.0208 (7.1×) and the directional term from 3.647 to 0.170 stops. That is the
number that says the method recovers albedo; the near-zero residual on real assets does not, and the
records say so.
The colour pipeline round-trips. Under E1_NEUTRAL_STUDIO a single linear scale fitted across the
six neutral patches explains the neutrals to 0.15 sRGB and the eighteen chromatic patches to
0.26 sRGB on the face sheet — sub-unit residual on an 8-bit scale. Independently, the grey ball's
implied irradiance and the chart's fitted scale agree to three decimal places on the serpent's first
probe (0.8215 against 0.8188), which is two instruments measuring the same exposure by different
routes and arriving at the same answer.
Honest tier, unchanged. Nothing in this proof upgrades either asset. Both remain GREYBOX. What
changed is that from here their look is judged under a light we declared, with the calibration
objects in frame, and the claims point at a record.
Pointing the validator at the whole ratified texture-bake output (GAME:QA/lookdev/CORPUS_ALBEDO_SWEEP.json,
17 basecolors, evidence images per texture) returns a number nobody had:
are overwhelmingly at the DARK end, which is the signature of baked shadow rather than of dark
pigment choice.
T_EQS_0013_BC at 30.06% of texels below 30 sRGB (p01 = 7.5), T_EQS_0085_BC at 28.03% (p01 = 9.0), T_EQS_0037_BC at 20.48% (p01 = 6.9).
small fractions, but every one of them on a dielectric that has no business being there.
Read this as a queue, not a verdict. It is exactly the class §5 predicts: these bakes were made
from lit plates and never de-lit, so the light is in the pigment. The remedy is the de-light pass
this standard makes mandatory, run over the equipment class, and it is free. It is recorded here as
a finding for the director rather than actioned inside this wave, because the equipment textures
belong to another lane's route and this lane does not edit another lane's assets.
---
import → LOOKDEV SHEET (§4) → appeal judgement → IN-ENGINE AT SHIP LOD (§7). An appeal or
polish judgement taken before the de-light stage is taken on the wrong asset.
RECORD path. Un-recorded look claims are un-DONE, on the same shape as the landing contract's
wiring clause.
python harness/run_gates.py keeps the instruments armed (§8).boards-publish-phone-first law.
---
Derived from. The W-LOOK charter row (dossier §6 rank 5, L1291) and the stage sections it points
at — §3.6 texturing/materials/de-lighting (L387-456), §3.13 lookdev (L832-868), §3.15 on gates that
do not block (L915-964); the roster ruling at docs/spine/DECISIONS_PENDING_JOSH.md L4712-4714;
AAAAA register rows 6 and 26 (L74, L99); docs/ENGINE_OPTIMIZATION_DOCTRINE.md R-08 (L1133), §3.2
rule 1 (L1018) and D10 (L1417); the ratified texture route's own record at
build/3d/V2_ROUTING_RATIFIED.json; and the two asset records the proof ran against
(build/3d/characters/CHAR_0001_FACE_PIVOT_RECORD.json,
build/3d/mva_texture_serpent_rebuild_20260806T134742Z.json).
Honest limits.
not colorimetry.
DERIVED-PROVISIONAL, and it reports rather than refuses.
genuinely varies with facing, this pass would remove signal. It is declared for captured and
generated basecolors only, and that boundary is a judgement a lane can get wrong.
standard's own rule says off-engine numbers are working evidence.
are; the files live on the D: working tier and are re-fetchable from a CC0 source at zero cost.
---
What this closes. likeness_lens.py L12 — the texture read at the size the player sees — REFUSED
on every invocation since it was armed, because ship-scale on-screen pixel height was undefined in
every doc in this repo and the lens will not invent a footprint (SHIP_PX_OWED_BY, its own words).
POSTMORTEM_LIKENESS_2026-08-08 action G and roadmap row C-03 assign the ruling to the director. This
section is that ruling; the machine-readable twin every caller reads is
build/3d/eval/likeness/SHIP_SCALE_RULING.json.
The ruling is a FORMULA plus declared constants, never a taste number. Figure on-screen fraction
at distance d is stature / (2 · d · tan(vFOV/2)), evaluated at the age-14 stature 166.72 cm
(CRAFT_ANATOMY_STANDARDS.md §3.1), UE's default 90° horizontal FOV at 16:9 (tan half-vFOV = 0.5625
exactly — a DECLARED default that the engine lane's future camera ruling supersedes, at which point
the table re-derives), and the doctrine's modal shipped resolution 1080p
(ENGINE_OPTIMIZATION_DOCTRINE.md:19; 1440p/4K scale factors published). The practice distance
ladder (hero ~5 m → 320 px, gameplay ~15 m → 107 px, recognition 50–100 m → 32/16 px,
CRAFT_ANATOMY_STANDARDS.md:379-390) supplies the supporting reads.
The gating framings, per class (full table in the JSON): the hero character gates at CLOSEUP
— head-and-shoulders framing, head at half frame height, implied whole-figure footprint **3,834 px at
1080p** (7,668 at 4K). This is the operational form of the FF7R zoom-in bar, and the adversarial pass
ran BOTH directions: gating the hero at gameplay distance (107–320 px) would let mush ship on the
class whose mandate is closeup perfection — the timid option is the wrong one here; gating every
class at hero-closeup would fail props nobody ever frames — so ambient NPCs gate at the 15 m read
(107 px), dialogue-framed NPCs at bust framing (2,556 px), familiars and boss-class creatures at
full-frame (1,080 px), weapons at inspect framing (648 px), and walk-up architecture is **out of L12
scope by construction** (no exemplar plate to compare against; its texture bar is the per-region
texel-density instrument, px/m).
The operative cap, published rather than hidden. An L12 read above the plate's native figure
height upsamples the REFERENCE past its recorded pixels, and a comparison against invented reference
is not a comparison. The cap is the compared plate's MEASURED native figure height — for the age-14
V4 FRONT plate that is 2,822 px (the lens's own source bbox; consistent with the carry record's
"promoted range 2139-2833 px") — so today's operative hero read is min(3,834, 2,822) = 2,822 px,
and the 1.36× gap is the published SHIP_SCALE_DEBT: the texture bar between 2,822 and 3,834 px
is UNREFERENCED until the P3 ship-footprint re-render (the lens's own named surrogate replacement)
and higher-resolution plates (the same B6 ceiling the postmortem names) land. The V4 gate's
figure_h_px_min 1,300 is the gate's ACCEPTANCE FLOOR, not a cap — a first draft of this section
used it as one and published a 2.95× debt; the fresh-context critic caught the substitution and the
correction is recorded here and in the JSON rather than silently rewritten. The lens itself is
UNCHANGED — it takes the number as a caller input by design; wiring it into a capture/publish path
stays the game-repo lane's owed item (charter S16).
Proof, first scored read. --score --ship-px-height 2822 on the landed V4 FRONT plate against
the committed V1 baseline render: L12 SCORED for the first time — at the near-ship footprint the
render carries 28.5% of the plate's high-frequency energy (hf ratio 0.2850, spectral slope delta
−0.3987; build/3d/eval/likeness/L12_PROOF_RUN_SHIP_SCALE.json). Descriptive, not a gate verdict —
and the bias is stated per-run rather than borrowed from boilerplate: on THIS pair the plate ran
effectively unresampled (×1.0) while the 1100 px turntable surrogate was UPSAMPLED ×2.88, so the
lens's general-case OPTIMISTIC stamp is not established here — an upsampled surrogate understates
what the real asset would resolve rendered natively at this footprint, and the direction stays
unresolved until the P3 re-render. The L10 bands on the same pair remain DESCRIPTIVE_CANNOT_GATE.
The refusal that blocked the instrument is closed; the honesty stamps that keep it from
over-claiming are not.