RC3_NATIVE_TEXTURE_ADJUDICATION.md

pipelines/RC3_NATIVE_TEXTURE_ADJUDICATION.md

RC3 — "NATIVE GENERATOR TEXTURE WAS NEVER SHOWN" — ADJUDICATION + THE FIRST TEST

Lane: read-only post-mortem, RC3. Nothing was created, modified or deleted under

C:/dev/humanity-forgotten or C:/dev/Humanity/Humanity. No GPU / Blender / Unreal was launched.

---

0. DERIVATION

Contract item 2 note. harness/route.py has no work-kind for a pipeline post-mortem

(docs/task_router.json covers canon work kinds — chapter/region/registry authoring). I did not

substitute a proposal for a canon node. This lane touches no canon: every claim below is read off

build artifacts, run records, vendor source on D:/, and licence files already pinned in the repo.

DERIVED FROM (evidence actually read, in full):

grade; item 3 is this lane's assignment.

compared and which routes were struck.

build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json (L1-61) — both TRELLIS.2 run records.

build/3d/finish_a14/renders/textured_tt/TURNTABLE_TEX_A14_TEX_V3.json (L1-161) — the graded

turntable and the instrument that made it.

POINT_COUNTS, subject) — the likeness harness and its two refusing lenses.

trellis2/pipelines/trellis2_image_to_3d.py (L119-200, L456-592),

trellis2/modules/image_feature_extractor.py (L59-118),

o-voxel/o_voxel/postprocess.py (L1-331), o-voxel/setup.py,

D:/assetgen/models/TRELLIS2/pipeline.json, texturing_pipeline.json, ckpts/ listing,

TRELLIS/trellis/pipelines/trellis_image_to_3d.py L343-373 (the TRELLIS-1 multi-image API).

NOT DERIVED (authored judgment, and why it had no home):

but names no instrument. Authored to reuse the lane's own existing instruments unchanged.

evidence in §4 shows TRELLIS.2 has no upstream multi-view API and a naive multi-view hack

reproduces the geometry of a failure already on the record.

authored from the licence text's own §3.3 wording; the repo has the read but never applied it to

a texture run because no texture run was ever proposed.

---

1. VERDICT ON RC3: CONFIRMED, and stronger than the charter states

The charter says the bake-off "compared geometry only." That is true, and understated. The

stronger true statement is:

**TRELLIS.2's full PBR output was COMPUTED on this box, twice, paid for in GPU time, and then
deliberately discarded at the export line. It was never written to disk, never rendered, never
scored, and never shown to Josh.**

1.1 The bake-off's own instrument list contains no colour

build/3d/bakeoff/build_sheet.py L8-15 names every number the sheet prints:

sym p95   -- 95th percentile mirror residual across the FITTED symmetry plane, over figure height
drift     -- range of per-band centroid offset from that plane, over height; the scoliosis number
w-asym    -- mean per-band left/right width mismatch
parts     -- connected components, and the share of faces in the largest one
lumpy     -- fraction of face pairs meeting at more than 60 degrees

Confirmed against the artifacts rather than the docstring: all four measurement files

(build/3d/bakeoff/metrics/{TRIPOSG_A14_FULL,HI3DGEN_A14_FULL,HUNYUAN21_A14_FULL,HUNYUAN21_A14_O512}.json)

carry exactly the key set ['bands','height_units','mesh','surface','symmetry','tag']. No tone, no

colour, no albedo, no likeness, no perceptual term of any kind. The turntables under

build/3d/bakeoff/renders/** are the material-blind WORKBENCH clay

(build/3d/cage/render_turntable.py L14-17: house shading constants, one flat colour, cavity).

1.2 TRELLIS.2 was not on the sheet at all

build/3d/bakeoff/build_sheet.py L32-42: the three ARMS are THE REJECTED CHAIN, TripoSG, Hi3DGen.

L53-59 puts TRELLIS.2 in STRUCK as *"INSTALLED — blocked on one gated dependency"* (DINOv3). So

at sheet time the only generator that ships native PBR was not an arm.

1.3 It ran afterwards — twice — and both runs threw the texture away

The gate opened and the arm ran. build/3d/hi3dgen/run_trellis2_a14.py L1-7 says so explicitly.

Two run records exist:

recordconditioningoutgeometry time
build/3d/hi3dgen/a14/logs/A14_TRELLIS2_RUN.json L11, L44, L49-51build/3d/hi3dgen/a14/cond/A14_full_cut.png (single view).../meshes/A14_TRELLIS2_s42.obj, 894,379 v / 1,803,286 f121.7 s
build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json L11, L44, L49-51build/3d/finish_a14/v3/cond/A14V3_front_cut.png (single view).../v3/meshes/A14V3_TRELLIS2_s42.obj, 940,424 v / 1,895,540 f15.6 s

Both records carry the identical export_note (A14_TRELLIS2_RUN.json L60; RUN_V3_TRELLIS2.json L60):

`"OBJ, geometry only. GLB export goes through o_voxel.postprocess.to_glb, which the licence guard
refuses; geometry needs no exporter and geometry is what this comparison measures."`

And the script states the decision in prose at L22-26:

*"the texture path is not on trial here, because the bake-off compares GEOMETRY — symmetry,
straightness, part count, lumpiness, and girth fit. Texture is a later stage with its own
already-proven Blender-headless route."*

The export line is run_trellis2_a14.py L224-231: it takes mesh.vertices and mesh.faces,

builds a bare trimesh.Trimesh, and writes OBJ. Nothing else on the returned object survives.

1.4 The discarded thing was a full PBR volume, and it was not optional

Trellis2ImageTo3DPipeline.run() has no geometry-only mode. Every branch of

D:/assetgen/TRELLIS2_code/trellis2/pipelines/trellis2_image_to_3d.py L548-590 samples

tex_slat before decode_latent, and decode_latent (L456-486) returns

MeshWithVoxel(m.vertices, m.faces, origin=[-0.5,-0.5,-0.5], voxel_size=1/resolution,
              coords=v.coords[:, 1:], attrs=v.feats, ..., layout=self.pbr_attr_layout)

attrs + coords + layout are the PBR volume. D:/assetgen/models/TRELLIS2/pipeline.json

L9-11 loads tex_slat_decoder, tex_slat_flow_model_512 and tex_slat_flow_model_1024

(2.58 GB + 2.58 GB + 0.95 GB of the 16 GB on disk), and L129 sets

"default_pipeline_type": "1024_cascade", which L570-576 routes through

tex_slat_flow_model_1024. The channel set is named at o-voxel/o_voxel/postprocess.py L281-284:

base_color, metallic, roughness, alpha — the audit's own claim

(docs/MODEL_STACK_AUDIT_AUG2026.md L106: *"Ships full PBR from the decoder — Base Color,

Roughness, Metallic, Opacity, up to 4096²"*) is confirmed at source.

**So the ~5 GB texture decoder stack was loaded and sampled on both runs, and its output went to

/dev/null at line 231.**

1.5 No other arm could have shown native texture either

(build/3d/bakeoff/meshes/*.gen.json) carry no texture field, and Hi3DGen's only image output is

a *normal* map (HI3DGEN_A14_full.gen.json, "normal_map": ".../HI3DGEN_A14_full_normal.png").

(build/3d/bakeoff/meshes/HUNYUAN21_A14_full.gen.json, "model" field) — its paint model was

never invoked, and it is permanently struck on licence anyway

(build_sheet.py L48-52; docs/MODEL_STACK_AUDIT_AUG2026.md L358-373).

**Therefore: across the entire generator bake-off, exactly one arm could produce native PBR, and

that arm's PBR was computed and dropped. RC3 is CONFIRMED. The claim "the bake-off compared

geometry only" is true and the discard was deliberate and documented — it was a scope call, not an

oversight, and it is the scope call that left the LOOK question unanswered.**

1.6 The consequence, measured

The thousand-point harness already caught this from the other end.

build/3d/eval/thousand_point/EVAL_A14_ASSET_TEXTURE_LEG.jsonVERDICT.refused:

L04_tone — *"the mesh carries no texture: materials present. A tone lens run on an untextured clay
would score the renderer's shading constants, not the asset, and that is a vacuous pass this
harness will not issue."*
L05_colour — *"…There is no asset colour to compare, and a colour lens that scores a clay's
shading constant is worse than no lens."*

Two of eight lenses have been armed and refusing since they were built. And of the six that did

score, L02_silhouette = RED and L03_face_structure = RED — which is the number-form of

Josh's grade.

---

2. WHAT WAS SHOWN TO JOSH (the thing the native output must be compared against)

The graded turntable is build/3d/finish_a14/renders/textured_tt/A14_TEX_V3_az{000..315}.png

(8 frames, written 2026-08-07 21:12), recorded in TURNTABLE_TEX_A14_TEX_V3.json:

build/3d/finish_a14/uv/TROUSERS_A14_MID_UV.obj

baked atlas for this tile"` (L99-107)

lighting, so this is not a lookdev render."*

This is the comparison target. Note for the post-mortem's RC1/RC2 lanes: the graded frame carries

16 material slots across a partitioned assembly, one of them unbaked — i.e. the thing Josh graded

is an assembled, re-UV'd, re-baked reconstruction, not a generator surface.

---

3. TRELLIS.2 LOCAL INSTALL STATE (read-only, verified on this box)

It is fully installed and the native-texture path is physically runnable today.

itemstateevidence
codeD:/assetgen/TRELLIS2_code/ (git checkout, MIT LICENSE)dir listing; app_texturing.py, example_texturing.py present
weightsD:/assetgen/models/TRELLIS2/ckpts/16.0 GB, 10 safetensorslisting; incl. slat_flow_imgshape2tex_dit_1_3B_{512,1024}_bf16.safetensors @ 2.58 GB each, tex_dec_next_dc_f16c32_fp16.safetensors @ 948 MB, tex_enc_… @ 709 MB
pipeline configspipeline.json (image→3D, texture models included) and texturing_pipeline.json (mesh+image→texture) both on diskboth read in full
venvD:/assetgen/venv_trellis2_win/ — 158 packages, cp312listing
torch2.8.0+cu128, sm_120 confirmed at runtimeA14_TRELLIS2_RUN.json L4-9
CUDA extcumesh (with _C, _cubvh, _xatlas .pyd), flex_gemm, o_voxel (_C.cp312-win_amd64.pyd) — all builtsite-packages listing
nvdiffrast0.4.0 INSTALLED AND COMPILED (nvdiffrast/, _nvdiffrast_c.cp312-win_amd64.pyd, nvdiffrast-0.4.0.dist-info)site-packages listing
DINOv3 encodergate ACCEPTED; runs via a transformers-5.x shimrun_trellis2_a14.py L147-174; A14_TRELLIS2_RUN.json L32-35
RMBG-2.0gated AND non-commercial — correctly routed around by feeding pre-cut RGBArun_trellis2_a14.py L73-81, L176-199
attentiondense sdpa; sparse xformers pinned to the CUTLASS kernel (Hopper flash kernel fails on sm_120)run_trellis2_a14.py L126-145
BlenderD:/assetgen/tools/blender-5.2.0-windows-x64/blender.exeverified present
measured costload 50-52 s; geometry 15.6 s (V3) / 121.7 s (A14 1024-cascade); peak VRAM alloc 2.74 GBboth run records

**The only thing standing between this box and a native full-PBR GLB is the lane's own import

hook**, run_trellis2_a14.py L44-54, which raises on any import of nvdiffrast / nvdiffrec /

nvdiffrec_render. It is policy, not a missing dependency.

3.1 Exactly how much nvdiffrast the texture export needs — measured, not assumed

o-voxel/o_voxel/postprocess.py to_glb() is 318 lines. nvdiffrast appears in **one block, lines

229-249**, and does exactly one job: rasterize the UV layout so each texel learns its 3D position.

230   ctx = dr.RasterizeCudaContext()
232   uvs_rast = cat([out_uvs*2-1, 0, 1])                 # UVs as clip-space
237   rast_chunk, _ = dr.rasterize(ctx, uvs_rast, out_faces[i:i+100000], resolution=[T,T])
249   pos = dr.interpolate(out_vertices.unsqueeze(0), rast, out_faces)[0][0]

Everything else in the function is licence-clean: cumesh cleaning/simplify/uv_unwrap/BVH

(L105-216), flex_gemm.grid_sample_3d trilinear attribute sampling (L260-266), cv2.inpaint

dilation (L289-292), trimesh.visual.material.PBRMaterial assembly (L296-304). This matches the

audit's independent read (docs/MODEL_STACK_AUDIT_AUG2026.md L332-336: *"nvdiffrast sits on

TRELLIS.2's render/preview side, not its generation side… The PBR channels come out of the decoder

as per-vertex attributes, not from an inverse-rendering solve"*) — and now with the line numbers.

One residual the audit already named and this lane confirms: cumesh ships

_cubvh.cp312-win_amd64.pyd — the vendored NVIDIA instant-ngp BVH

(docs/MODEL_STACK_AUDIT_AUG2026.md L342-343). to_glb calls it at L122 and L254. A ship-tier

build must exclude it; an evaluation-tier run need not.

---

4. THE MULTI-VIEW V3 PLATES — located, and one hard finding about them

4.1 The plates

Authored, ruled by Josh 2026-08-07, inventoried with measured registration in

build/3d/characters/CHAR_0001_V3_MULTIVIEW_READINESS.json. Seven ages × 4 sheets, on E:.

For age 14 (verified present on disk):

viewpathframing
FRONTE:/art/boards/D1-09/CHAR_0001/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD.pngfull figure, 705 px subject
BACK…/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD_BACK.pngfull figure, 651 px
SIDE…/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD_SIDE.pngfull figure, 677 px
3/4 BUST…/CHAR_0001_EXEMPLAR_BUST_14_V3_BALD_34.pnghead-and-shoulders BUST — head lane only

Upscaled _W1344 siblings exist for all four. Background-cut RGBA derivatives already exist in-repo:

build/3d/finish_a14/v3/cond/A14V3_{front,back,side}_cut.png (+ _normal.png), cut by BiRefNet.

Two constraints the readiness record publishes and the test must respect:

*"reproduces the EXACT geometry of the failed FULLBUST arm"* — the giant-head-with-body-inside

failure at all three seeds, docs/PIPELINE_LEDGER.md 2026-08-06.

front/back/side is 7.66% of max, systematically taller in FRONT. Drawn sheets, not a turntable.

4.2 THE HARD FINDING: TRELLIS.2 has no multi-view conditioning API

run_multi_image** (method list of trellis2_image_to_3d.py: preprocess_image, get_cond,

sample_sparse_structure, sample_shape_slat, sample_shape_slat_cascade, decode_shape_slat,

sample_tex_slat, decode_tex_slat, decode_latent, run).

(trellis2/modules/image_feature_extractor.py L107-118) stacks a list into a batch

(B, N, D). Three views would be sampled as three independent objects, not one object seen

thrice.

D:/assetgen/TRELLIS/trellis/pipelines/trellis_image_to_3d.py L343-373 —

run_multi_image(images, …, mode='stochastic'|'multidiffusion') with

inject_sampler_multi_image context managers. That is what the V3 conditioning A/B actually ran

(build/3d/finish_a14/v3/metrics/RUN_V3_HI3DGEN.json: arm_table = `{"V3FRONT": ["front"],

"V3MV": ["front","back","side"]}, mv_mode: "stochastic"`).

Consequence for §8.14 item 3 as literally written. "TRELLIS.2 full-PBR from the multi-view V3

plates" is not a one-command run: TRELLIS.2 must first be *given* a multi-view path (authored

sampler injection, exactly as TRELLIS-1 does it), and an unvalidated version of that hack is the

same class of change that already produced the catastrophic FULLBUST result on the sister engine.

Both TRELLIS.2 runs on record were single-view FRONT. So the multi-view question and the

native-texture question have never been separated. They must be, or a bad result cannot be

attributed.

---

5. THE FIRST TEST — authored, runnable, NOT RUN

Design principle: change one thing. The texture question is answerable tonight with zero new

model behaviour; the multi-view question needs an authored sampler and gets its own arm behind a

positive control.

5.0 Licence routing — settled before the first command

docs/licence_records/nvdiffrast_EXCLUDED/LICENSE.txt §3.3 (L54-59), verbatim:

*"The Work and any derivative works thereof only may be used or intended for use non-commercially…
As used herein, 'non-commercially' means for research or evaluation purposes only and not for
any direct or indirect monetary gain."*

Read face-value under the standing law (licence reads are never conservative; flag only explicitly

triggered prohibitions): **a bake-off arm rendered to decide a route is research/evaluation, which

is the licence's own named permitted use.** The prohibition triggers on the shipped artifact, not

on the decision.

So the test runs in two tiers, and the tier is written on the artifact:

a quarantine directory whose name says so, carries "LICENCE_TIER": "EVALUATION_ONLY", and **may

never be imported into a shipping asset, a bake source, or a retopo input.** It answers the LOOK

question, nothing else.

UV rasteriser (build/3d/finish_a14/bake_plates.py L134 `"""Rasterise the atlas. Returns, per

covered texel, the face id and barycentric weights."""` — the single rasteriser landed in commit

bc093158), plus a cumesh rebuild excluding vendored cubvh. That is a half-day of work and

is not on the critical path of answering Josh.

If the director rejects the evaluation-tier read, T-EVAL still runs — with the substitution done

first. The sequencing is a speed choice, not a licence choice.

5.1 Files to create (all new; nothing existing is modified)

build/3d/bakeoff/native_tex/run_trellis2_native_pbr.py    # the run + native GLB export
build/3d/bakeoff/native_tex/render_glb_turntable.py       # Blender-headless GLB turntable
build/3d/bakeoff/native_tex/sheet_native_vs_chain.py      # the side-by-side sheet

5.2 run_trellis2_native_pbr.py — the arm

Derived from build/3d/hi3dgen/run_trellis2_a14.py with four deltas and no others, so the

geometry is bit-comparable to the run already on record:

1. the LicenceGuard meta-path finder (L44-54) is removed, and replaced by an explicit

"LICENCE_TIER": "EVALUATION_ONLY" field plus a hard refusal to write outside the quarantine

directory;

2. the o_voxel positive control (L106-114) inverts — import o_voxel must now succeed;

3. the export becomes o_voxel.postprocess.to_glb(...) on the full MeshWithVoxel;

4. the record gains texture-side facts (atlas size, channel presence, PBR value ranges).

Everything else is held: ATTN_BACKEND=sdpa, SPARSE_ATTN_BACKEND=xformers pinned to CUTLASS

(run_trellis2_a14.py L126-145), the transformers-5.x DINOv3 shim (L147-174), the RMBG sentinel

(L176-199), seed 42, and the same pre-cut RGBA so the conditioning is identical.

#!/usr/bin/env python3
"""ARM T-EVAL -- TRELLIS.2's OWN PBR, kept this time.

The bake-off dropped it at the export line. build/3d/hi3dgen/run_trellis2_a14.py L224-231 took
mesh.vertices and mesh.faces into a bare trimesh and wrote OBJ; the attribute volume the decoder
had already produced (mesh.attrs / mesh.coords / mesh.layout) went nowhere. This file changes that
ONE thing. Seed 42, the same pre-cut RGBA, the same attention backends, the same DINOv3 shim -- so
if the geometry differs from build/3d/finish_a14/v3/meshes/A14V3_TRELLIS2_s42.obj, something else
moved and the run is void.

LICENCE TIER: EVALUATION_ONLY. o_voxel.postprocess.to_glb rasterises the UV layout with nvdiffrast
(o-voxel/o_voxel/postprocess.py L229-249), whose licence
(docs/licence_records/nvdiffrast_EXCLUDED/LICENSE.txt sec 3.3) permits "research or evaluation
purposes only". Deciding a route is evaluation. SHIPPING IS NOT. Everything this file writes lands
under build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/ and must never be imported into a shipping
asset, a bake source, or a retopo input. The ship-tier route is the licence-clean substitution of
those twenty lines with build/3d/finish_a14/bake_plates.py's own atlas rasteriser.

Usage (absolute paths ONLY -- this script chdir()s into the TRELLIS.2 repo at import, and
build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json L61 records a run whose relative --record
landed under D:/assetgen and was lost):

  D:/assetgen/venv_trellis2_win/Scripts/python.exe ^
    C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/run_trellis2_native_pbr.py ^
    --image C:/dev/humanity-forgotten/build/3d/finish_a14/v3/cond/A14V3_front_cut.png ^
    --out   C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/A14V3_TRELLIS2_NATIVE_s42.glb ^
    --record C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/RUN_NATIVE_PBR_A14V3_s42.json ^
    --seed 42 --texture-size 2048 --decimation 200000
"""
import argparse, json, os, subprocess, sys, time

TRELLIS2_REPO = r"D:/assetgen/TRELLIS2_code"
WEIGHTS       = r"D:/assetgen/models/TRELLIS2"
QUARANTINE    = r"C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY"

os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
os.environ.setdefault("ATTN_BACKEND", "sdpa")            # held equal with the Hi3DGen arm
os.environ.setdefault("SPARSE_ATTN_BACKEND", "xformers")
os.environ.setdefault("XFORMERS_DISABLED", "1")
os.environ.setdefault("OPENCV_IO_ENABLE_OPENEXR", "1")
sys.path.insert(0, TRELLIS2_REPO)
os.chdir(TRELLIS2_REPO)

import numpy as np
import torch
from PIL import Image
from trellis2.pipelines import Trellis2ImageTo3DPipeline

# --- the xformers CUTLASS pin, byte-identical to run_trellis2_a14.py L135-142 ---
import xformers.ops as _xops
_mea = _xops.memory_efficient_attention
def _mea_cutlass(q, k, v, attn_bias=None, *a, **kw):
    kw.pop("op", None)
    return _mea(q, k, v, attn_bias, *a, op=(_xops.fmha.cutlass.FwOp, None), **kw)
_xops.memory_efficient_attention = _mea_cutlass

# --- the transformers-5.x DINOv3 shim, byte-identical to run_trellis2_a14.py L155-169 ---
import torch.nn.functional as _Fn
from trellis2.modules import image_feature_extractor as _ife
def _extract_features(self, image):
    image = image.to(self.model.embeddings.patch_embeddings.weight.dtype)
    hidden = self.model.embeddings(image, bool_masked_pos=None)
    pos = self.model.rope_embeddings(image)
    inner = getattr(self.model, "model", None)
    if inner is None:
        raise RuntimeError("DINOv3 encoder not found at .model -- the shim needs re-deriving")
    out = inner(hidden, pos)
    h = out.last_hidden_state if hasattr(out, "last_hidden_state") else out[0]
    return _Fn.layer_norm(h, h.shape[-1:])
_ife.DinoV3FeatureExtractor.extract_features = _extract_features

# --- the RMBG-2.0 sentinel, byte-identical to run_trellis2_a14.py L181-197 ---
from trellis2.pipelines import rembg as _rembg
class _RefusedRembg:
    def __call__(self, *a, **k):
        raise RuntimeError("REFUSED: briaai/RMBG-2.0 is gated and non-commercial. This arm is "
                           "conditioned on a pre-cut RGBA and must never reach the remover.")
    def to(self, *a, **k): return self
    def cpu(self): return self
for _n in [n for n in dir(_rembg) if not n.startswith("_")]:
    if isinstance(getattr(_rembg, _n), type):
        setattr(_rembg, _n, lambda *aa, **kk: _RefusedRembg())


def smi_free_mib():
    try:
        o = subprocess.run(["nvidia-smi", "--query-gpu=memory.free",
                            "--format=csv,noheader,nounits"],
                           capture_output=True, text=True, timeout=20)
        return int(o.stdout.strip().splitlines()[0])
    except Exception:
        return None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--image", required=True)
    ap.add_argument("--out", required=True)
    ap.add_argument("--record", required=True)
    ap.add_argument("--seed", type=int, default=42)
    ap.add_argument("--weights", default=WEIGHTS)
    ap.add_argument("--texture-size", type=int, default=2048)
    ap.add_argument("--decimation", type=int, default=200000)
    ap.add_argument("--remesh", action="store_true")
    a = ap.parse_args()

    # THE QUARANTINE IS ENFORCED, NOT ANNOUNCED. An nvdiffrast-derived artifact that escapes into
    # the shipping tree is the failure this whole tier exists to prevent.
    for p in (a.out, a.record):
        if not os.path.abspath(p).replace("\\", "/").startswith(QUARANTINE):
            raise SystemExit("REFUSED: EVALUATION_ONLY output must land under %s" % QUARANTINE)

    # POSITIVE CONTROL, INVERTED. The bake-off arm asserted o_voxel was REFUSED. This arm asserts
    # it IMPORTS -- if it does not, the texture path never ran and any 'no texture' finding here
    # would be an install artifact masquerading as a model result.
    import o_voxel
    assert hasattr(o_voxel.postprocess, "to_glb"), "o_voxel.postprocess.to_glb missing"

    rec = {"_doc": "ARM T-EVAL -- TRELLIS.2 native full-PBR, the output the bake-off discarded.",
           "LICENCE_TIER": "EVALUATION_ONLY",
           "LICENCE_WHY": ("o_voxel.postprocess.to_glb L229-249 rasterises UVs with nvdiffrast; "
                           "docs/licence_records/nvdiffrast_EXCLUDED/LICENSE.txt sec 3.3 permits "
                           "'research or evaluation purposes only'. This artifact decides a route "
                           "and never ships. The ship-tier route substitutes those 20 lines with "
                           "build/3d/finish_a14/bake_plates.py's own atlas rasteriser."),
           "model": "TRELLIS.2-4B", "weights": a.weights,
           "torch": torch.__version__, "device": torch.cuda.get_device_name(0),
           "device_capability": list(torch.cuda.get_device_capability()),
           "seed": a.seed, "image": a.image,
           "env": {k: os.environ.get(k) for k in
                   ("ATTN_BACKEND", "SPARSE_ATTN_BACKEND", "XFORMERS_DISABLED")},
           "held_identical_with": ("build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json -- same "
                                   "cut, same seed, same backends, same shim, same pipeline type"),
           "smi_free_mib_before": smi_free_mib()}

    torch.cuda.reset_peak_memory_stats()
    t0 = time.time()
    pipe = Trellis2ImageTo3DPipeline.from_pretrained(a.weights)
    pipe.cuda()
    rec["t_load_s"] = round(time.time() - t0, 2)

    img = Image.open(a.image)
    if img.mode != "RGBA":
        raise SystemExit("POSITIVE CONTROL FAILED: conditioning image must carry alpha")
    if bool((np.array(img)[:, :, 3] == 255).all()):
        raise SystemExit("POSITIVE CONTROL FAILED: alpha is fully opaque, so it is not a cut")

    t1 = time.time()
    mesh = pipe.run(img, seed=a.seed, preprocess_image=True)[0]
    torch.cuda.synchronize()
    rec["t_geometry_and_texture_s"] = round(time.time() - t1, 2)
    rec["pipeline_type"] = pipe.default_pipeline_type

    # THE THING THE BAKE-OFF DROPPED. Named on the record so a later reader can see it existed.
    rec["pbr_volume"] = {
        "attr_layout": {k: [v.start, v.stop] for k, v in mesh.layout.items()},
        "attr_voxels": int(mesh.attrs.shape[0]),
        "attr_channels": int(mesh.attrs.shape[1]),
        "voxel_size": float(mesh.voxel_size) if np.isscalar(mesh.voxel_size)
                      else [float(x) for x in mesh.voxel_size],
        "_what": ("base_color / metallic / roughness / alpha, decoded by "
                  "tex_dec_next_dc_f16c32_fp16 from tex_slat_flow_model_1024. "
                  "run_trellis2_a14.py L224-231 threw exactly this away."),
    }
    rec["raw_geometry"] = {"vertices": int(mesh.vertices.shape[0]),
                           "faces": int(mesh.faces.shape[0])}

    t2 = time.time()
    glb = o_voxel.postprocess.to_glb(
        vertices=mesh.vertices, faces=mesh.faces, attr_volume=mesh.attrs,
        coords=mesh.coords, attr_layout=mesh.layout, voxel_size=mesh.voxel_size,
        aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
        decimation_target=a.decimation, texture_size=a.texture_size,
        remesh=a.remesh, remesh_band=1, remesh_project=0, verbose=True)
    rec["t_to_glb_s"] = round(time.time() - t2, 2)

    os.makedirs(os.path.dirname(os.path.abspath(a.out)), exist_ok=True)
    try:
        glb.export(a.out, extension_webp=True)
        rec["export_texture_codec"] = "webp"
    except TypeError:
        # trimesh 5.0.0 is what this venv has; if extension_webp is not its kwarg, PNG is fine and
        # the difference is codec, not content. Recorded rather than silently swallowed.
        glb.export(a.out)
        rec["export_texture_codec"] = "png (extension_webp unsupported by this trimesh)"

    # THE MAPS ARE ALSO WRITTEN FLAT, so a sheet can show them without opening the GLB, and so a
    # texture claim cites pixels rather than a file size.
    mat = glb.visual.material
    maps = {}
    for name, im in (("BASECOLOR", getattr(mat, "baseColorTexture", None)),
                     ("METALLICROUGHNESS", getattr(mat, "metallicRoughnessTexture", None))):
        if im is None:
            maps[name] = None
            continue
        p = os.path.splitext(a.out)[0] + "_%s.png" % name
        im.save(p)
        arr = np.array(im.convert("RGB"))
        maps[name] = {"file": p.replace("\\", "/"), "size": list(im.size),
                      "mean_rgb": [round(float(x), 2) for x in arr.reshape(-1, 3).mean(0)],
                      "p02_rgb": [int(x) for x in np.percentile(arr.reshape(-1, 3), 2, axis=0)],
                      "p98_rgb": [int(x) for x in np.percentile(arr.reshape(-1, 3), 98, axis=0)]}
    rec["maps"] = maps
    if maps.get("BASECOLOR") is None:
        raise SystemExit("POSITIVE CONTROL FAILED: no basecolor texture came out of to_glb -- "
                         "this run proves nothing about native texture")

    rec.update({
        "out": a.out.replace("\\", "/"),
        "textured_vertices": int(len(glb.vertices)), "textured_faces": int(len(glb.faces)),
        "has_uv": bool(getattr(glb.visual, "uv", None) is not None),
        "bytes": os.path.getsize(a.out),
        "texture_size": a.texture_size, "decimation_target": a.decimation, "remesh": a.remesh,
        "peak_vram_alloc_mib": round(torch.cuda.max_memory_allocated() / 2**20, 1),
        "peak_vram_reserved_mib": round(torch.cuda.max_memory_reserved() / 2**20, 1),
        "smi_free_mib_after": smi_free_mib(),
        "t_total_s": round(time.time() - t0, 2),
        "HONEST_TIER": ("GENERATOR-NATIVE surface, unretouched. No de-light, no lookdev "
                        "calibration, no cleanup. docs/LOOKDEV_STANDARD.md means nothing here is "
                        "evidence for a POLISH claim -- it is evidence for a LIKENESS claim only."),
    })
    with open(a.record, "w", newline="\n") as fh:
        json.dump(rec, fh, indent=1); fh.write("\n")
    print(json.dumps({k: rec[k] for k in ("out", "textured_faces", "texture_size",
                                          "t_total_s", "LICENCE_TIER")}, indent=1))


if __name__ == "__main__":
    main()

5.3 render_glb_turntable.py — the straight render, on the SAME instrument

The comparison is void if the two arms are rendered by two instruments

(build/3d/bakeoff/build_sheet.py L5-6). So this is build/3d/finish_a14/render_textured.py

(L81-183) with one change: import a GLB (materials already bound by the file) instead of an OBJ

plus a hand-mapped UDIM table. Same AZIMUTHS = [0,45,90,135,180,225,270,315], same

ORTHO_MARGIN = 1.12, same CAM_DIST_FACTOR = 6.0, same three-area-light rig (energies 900000 /

380000 / 520000, size 110), same BLENDER_EEVEE, same world background (0.10,0.10,0.11,1), same

res*0.62 × res frame, same one-ortho-scale-for-all-eight rule, same TURNTABLE_TEX_<tag>.json

sidecar with per-frame sha256.

Three deltas, all forced and all recorded:

1. bpy.ops.import_scene.gltf(filepath=...) replaces bpy.ops.wm.obj_import(...);

2. the mesh is scaled to 166.804 cm — the graded turntable's own figure height

(TURNTABLE_TEX_A14_TEX_V3.json L6) — before framing, because TRELLIS.2 emits a unit-cube

normalized mesh (RUN_V3_TRELLIS2.json L54-58: bbox extents [0.2933, 0.171, 0.9994]) and

build/3d/finish_a14/v3/metrics/COND_A14V3_V3MV_s42.json shows the canonical conditioning is

axis-map + uniform scale to target_height_cm, nothing else;

3. the GLB's Y-up→Z-up conversion is applied at import (to_glb L312-315 already swapped Y/Z on

export), and the resulting axis frame is asserted against

build/3d/cage/render_turntable.py L18-21 (X = subject's right, Y = front, Z = up, cm) so

azimuth 0 is the face on both arms. A silent 180° flip here would read as a likeness failure.

The flat-slot positive control from render_textured.py L107-109 is kept in spirit: if the imported

GLB has no image texture on any material, the script exits rather than render a flat clay.

D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe --background --python ^
  C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/render_glb_turntable.py -- ^
  --glb C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/A14V3_TRELLIS2_NATIVE_s42.glb ^
  --out C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/renders/native_tt ^
  --tag A14V3_NATIVE --height-cm 166.804 --res 1100

5.4 sheet_native_vs_chain.py — the side-by-side

Modelled on build/3d/bakeoff/build_sheet.py (which already knows how to put arms, angles and

numbers on one face). Three rows, four columns:

rowsource
THE PLATE (the target)E:/art/boards/D1-09/CHAR_0001/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD{,_SIDE,_BACK}.png + the 3/4 bust, letterboxed to the render aspect
THE ASSEMBLED CHAIN (what Josh graded)build/3d/finish_a14/renders/textured_tt/A14_TEX_V3_az{000,090,180,270}.png
TRELLIS.2 NATIVE PBR (arm T-EVAL).../QUARANTINE_EVAL_ONLY/renders/native_tt/A14V3_NATIVE_az{000,090,180,270}.png

Footer, on the sheet's own face, in the register build_sheet.py L44-46 established:

(docs/LOOKDEV_STANDARD.md). Neither row is evidence for a polish claim.`

UNBAKED (TURNTABLE_TEX_A14_TEX_V3.json L99-107). The native row is one generator surface,

untouched, and is BALD and NUDE by construction — the plate it conditioned on is the bald V3

body sheet. Compare BODY READ and SKIN/FORM, not wardrobe.`

That last line is load-bearing and must be on the sheet: comparing a clothed assembly against a

nude generator output without saying so would be the sheet lying.

5.5 The comparison protocol — three readings, in this order

(a) THE EYE, FIRST AND DECIDING. The sheet goes to Josh. §8.14's question is *"why is this still

so far off"*, and the answer that matters is whether the native row reads closer to the plate row

than the chain row does. docs/LOOKDEV_STANDARD.md L11-14 already rules that his eye outranks every

number in it.

(b) THE HARNESS, ON BOTH ARMS. harness/asset_factory/thousand_point_eval.py, same age, same

plate dir, once per arm:

python harness/asset_factory/thousand_point_eval.py --mesh <arm mesh> --age 14 \
    --tag A14_NATIVE_PBR   --out-dir build/3d/eval/thousand_point
python harness/asset_factory/thousand_point_eval.py --mesh build/3d/finish_a14/uv/A14_ASSET.obj \
    --age 14 --tag A14_ASSET_TEXTURE_LEG --out-dir build/3d/eval/thousand_point   # already exists

This is the first run in the programme where L04_tone and L05_colour can score at all — they

have been armed and refusing since they were written

(EVAL_A14_ASSET_TEXTURE_LEG.jsonVERDICT.refused). The chain arm's current card is

`L01 GREEN / L02 RED / L03 RED / L04 REFUSED / L05 REFUSED / L06 AMBER / L07 GREEN / L08

READY_STATIC` over 16,288 measured points. **The native arm's L02_silhouette and L03_face_structure

are the two numbers that decide RC1/RC2 as well as RC3** — if a raw generator surface beats the

fitted-and-grafted assembly on silhouette, the "optimizes numbers not look" charge is proven from

the other direction.

Caveat to publish with the numbers, not after: the native arm is bald and nude, the chain arm wears

trousers and carries a grafted head. L03_face_structure is comparable (both bald at the face);

L02_silhouette is comparable on the plate's own bald-body silhouette, which is exactly what the V3

bald sheets are; L08_equip_swap_readiness is not comparable and should be marked N/A on the

native arm rather than scored.

(c) THE CONTROL THAT MAKES (b) MEAN ANYTHING. Run

build/3d/bakeoff/measure_mesh.py on the native arm's geometry and check `sym p95 / drift / w-asym

/ parts / lumpy against build/3d/finish_a14/v3/metrics/` for the run already on record at the same

seed. If the geometry moved, the texture is not the only variable and the comparison is void.

5.6 Expected outputs (the done-condition)

build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/
  A14V3_TRELLIS2_NATIVE_s42.glb                     # geometry + UVs + PBR material
  A14V3_TRELLIS2_NATIVE_s42_BASECOLOR.png           # 2048^2
  A14V3_TRELLIS2_NATIVE_s42_METALLICROUGHNESS.png   # 2048^2, B=metallic G=roughness
  RUN_NATIVE_PBR_A14V3_s42.json                     # LICENCE_TIER, pbr_volume, maps, timings
  renders/native_tt/A14V3_NATIVE_az{000..315}.png   # 8 frames
  renders/native_tt/TURNTABLE_TEX_A14V3_NATIVE.json
build/3d/bakeoff/native_tex/
  SHEET_NATIVE_VS_CHAIN_A14.png                     # the thing Josh looks at
build/3d/eval/thousand_point/
  EVAL_A14_NATIVE_PBR.json  POINTS_…  SHEET_…html   # L04/L05 finally scoring

Budget from the measured runs: load ~52 s, generation ~16-122 s, to_glb at 2048² a few minutes,

turntable ~1-2 min. **Under fifteen minutes of GPU for the answer to a question that has been open

since the bake-off.**

5.7 ARM T-MV — the multi-view question, second and separate

Only after T-EVAL returns. Requires authoring run_multi_image onto Trellis2ImageTo3DPipeline

the way TRELLIS-1 does it (D:/assetgen/TRELLIS/trellis/pipelines/trellis_image_to_3d.py L343-373:

get_cond(images), cond['neg_cond'] = cond['neg_cond'][:1], then

inject_sampler_multi_image(...) around both the sparse-structure and slat samplers, mode

stochastic). TRELLIS.2 has three sampler stages, not two — sparse structure, shape slat (cascade),

and tex slat — so the injection must cover the texture sampler too or the multi-view arm would

carry single-view colour on multi-view geometry and nobody would know.

Its positive control is non-negotiable: hand the authored multi-view path a list of ONE image

and assert it reproduces T-EVAL's mesh at the same seed. If it does not, the injection is wrong and

every multi-view result is uninterpretable.

Inputs: build/3d/finish_a14/v3/cond/A14V3_{front,back,side}_cut.png — already cut, already

registered, already the exact set Hi3DGen's V3MV arm used

(RUN_V3_HI3DGEN.json arm_table.V3MV). The 3/4 bust stays out

(CHAR_0001_V3_MULTIVIEW_READINESS.json L12).

---

6. WHAT THIS LANE DID NOT ESTABLISH

authored, not run, per lane law.

rather than asserted.

12.8-24.5 GB free after (RUN_V3_TRELLIS2.json L46-48, A14_TRELLIS2_RUN.json L46-48), so 2048²

is a safe first pass and 4096² is a second run, not a first.

docs/MODEL_STACK_AUDIT_AUG2026.md L342-343, confirmed on disk

(venv_trellis2_win/Lib/site-packages/cumesh/_cubvh.cp312-win_amd64.pyd), unresolved. It gates

T-SHIP, not T-EVAL.

7. ONE STALE ARTIFACT FOUND ON THE WAY (a finding, not an edit)

harness/asset_factory/should_exist_census.py L304 still reads:

`"DECLARED DEVIATION from PIPE_3D: TRELLIS.2 textured path BANNED (nvdiffrast non-commercial),
Hunyuan3D-2.1 licence read reserved for Josh."`

Both halves are now superseded and neither was updated:

over-broad about TRELLIS.2"* and ordered the row narrowed. It was not narrowed.

(docs/MODEL_STACK_AUDIT_AUG2026.md L358-373; build/3d/bakeoff/build_sheet.py L48-52).

This is contract item 6 territory — a finding for the director, not an edit by this lane. It

matters beyond tidiness: a census line saying a route is BANNED is exactly the kind of artifact a

later agent reads as authority and never re-tests. That is one mechanism by which the native

texture went unshown for as long as it did.

---

8. WHAT THIS LANE DID NOT ESTABLISH

authored, not run, per lane law.

rather than asserted.

12.8-24.5 GB free after (RUN_V3_TRELLIS2.json L46-48, A14_TRELLIS2_RUN.json L46-48), so 2048²

is a safe first pass and 4096² is a second run, not a first.

docs/MODEL_STACK_AUDIT_AUG2026.md L342-343, confirmed on disk

(venv_trellis2_win/Lib/site-packages/cumesh/_cubvh.cp312-win_amd64.pyd), unresolved. It gates

T-SHIP, not T-EVAL.

D:/assetgen/models/TRELLIS2/texturing_pipeline.json, weights present) could texture the

EXISTING assembled chain mesh rather than replace it. It imports nvdiffrast at

trellis2/pipelines/trellis2_texturing.py L13 and is a genuinely different question — a

"keep our geometry, take their surface" arm. Boarded, not evaluated.

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