pipelines/RC3_NATIVE_TEXTURE_ADJUDICATION.md
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.
---
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):
docs/HANDOFF_2026-08-05_ACCOUNT_SWITCH.md §8.14 (L2811-2838) — the charter and Josh's verbatimgrade; item 3 is this lane's assignment.
build/3d/bakeoff/build_sheet.py (L1-145) — the bake-off sheet's own definition of what itcompared and which routes were struck.
build/3d/bakeoff/metrics/*.json (7 files) — every metric emitted by the bake-off.build/3d/hi3dgen/run_trellis2_a14.py (L1-253) — the TRELLIS.2 arm, in full.build/3d/hi3dgen/a14/logs/A14_TRELLIS2_RUN.json (L1-61) and build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json (L1-61) — both TRELLIS.2 run records.
build/3d/finish_a14/v3/metrics/RUN_V3_HI3DGEN.json — the V3 conditioning A/B (front vs MV).build/3d/characters/CHAR_0001_V3_MULTIVIEW_READINESS.json (L1-537) — the multi-view plate set.build/3d/finish_a14/render_textured.py (L1-194) and build/3d/finish_a14/renders/textured_tt/TURNTABLE_TEX_A14_TEX_V3.json (L1-161) — the graded
turntable and the instrument that made it.
build/3d/cage/render_turntable.py (L1-45) — the acceptance turntable's frame/scale law.build/3d/eval/thousand_point/EVAL_A14_ASSET_TEXTURE_LEG.json (VERDICT, LENSES, POINT_COUNTS, subject) — the likeness harness and its two refusing lenses.
docs/MODEL_STACK_AUDIT_AUG2026.md L59-66, L99-118, L326-356 — the nvdiffrast/TRELLIS.2 read.docs/licence_records/nvdiffrast_EXCLUDED/LICENSE.txt §3.2-3.3 (L45-59) — the use limitation.docs/LOOKDEV_STANDARD.md L1-60 — the honest-tier law for a look claim.D:/: TRELLIS2_code/example.py, example_texturing.py, 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.
---
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.**
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).
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.
The gate opened and the arm ran. build/3d/hi3dgen/run_trellis2_a14.py L1-7 says so explicitly.
Two run records exist:
| record | conditioning | out | geometry time |
|---|---|---|---|
build/3d/hi3dgen/a14/logs/A14_TRELLIS2_RUN.json L11, L44, L49-51 | build/3d/hi3dgen/a14/cond/A14_full_cut.png (single view) | .../meshes/A14_TRELLIS2_s42.obj, 894,379 v / 1,803,286 f | 121.7 s |
build/3d/finish_a14/v3/metrics/RUN_V3_TRELLIS2.json L11, L44, L49-51 | build/3d/finish_a14/v3/cond/A14V3_front_cut.png (single view) | .../v3/meshes/A14V3_TRELLIS2_s42.obj, 940,424 v / 1,895,540 f | 15.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.
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.**
.gen.json records (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").
"(shape only)" by its own record (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.**
The thousand-point harness already caught this from the other end.
build/3d/eval/thousand_point/EVAL_A14_ASSET_TEXTURE_LEG.json → VERDICT.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.
---
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/A14_PARTITIONED.obj + garment build/3d/finish_a14/uv/TROUSERS_A14_MID_UV.obj
build/3d/finish_a14/texture/A14_BASECOLOR.{1001,1002,1003}.pngBLENDER_EEVEE, ORTHO, one scale for all eight azimuths, 868×1400M_GARMENT_UNDERWEAR_TROUSERS (tile 1021) — `"FLAT_BECAUSE": "nobaked 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.
---
It is fully installed and the native-texture path is physically runnable today.
| item | state | evidence |
|---|---|---|
| code | D:/assetgen/TRELLIS2_code/ (git checkout, MIT LICENSE) | dir listing; app_texturing.py, example_texturing.py present |
| weights | D:/assetgen/models/TRELLIS2/ckpts/ — 16.0 GB, 10 safetensors | listing; 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 configs | pipeline.json (image→3D, texture models included) and texturing_pipeline.json (mesh+image→texture) both on disk | both read in full |
| venv | D:/assetgen/venv_trellis2_win/ — 158 packages, cp312 | listing |
| torch | 2.8.0+cu128, sm_120 confirmed at runtime | A14_TRELLIS2_RUN.json L4-9 |
| CUDA ext | cumesh (with _C, _cubvh, _xatlas .pyd), flex_gemm, o_voxel (_C.cp312-win_amd64.pyd) — all built | site-packages listing |
| nvdiffrast | 0.4.0 INSTALLED AND COMPILED (nvdiffrast/, _nvdiffrast_c.cp312-win_amd64.pyd, nvdiffrast-0.4.0.dist-info) | site-packages listing |
| DINOv3 encoder | gate ACCEPTED; runs via a transformers-5.x shim | run_trellis2_a14.py L147-174; A14_TRELLIS2_RUN.json L32-35 |
| RMBG-2.0 | gated AND non-commercial — correctly routed around by feeding pre-cut RGBA | run_trellis2_a14.py L73-81, L176-199 |
| attention | dense sdpa; sparse xformers pinned to the CUTLASS kernel (Hopper flash kernel fails on sm_120) | run_trellis2_a14.py L126-145 |
| Blender | D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe | verified present |
| measured cost | load 50-52 s; geometry 15.6 s (V3) / 121.7 s (A14 1024-cascade); peak VRAM alloc 2.74 GB | both 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.
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.
---
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):
| view | path | framing |
|---|---|---|
| FRONT | E:/art/boards/D1-09/CHAR_0001/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD.png | full figure, 705 px subject |
| BACK | …/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD_BACK.png | full figure, 651 px |
| SIDE | …/CHAR_0001_EXEMPLAR_BODY_14_V3_BALD_SIDE.png | full figure, 677 px |
| 3/4 BUST | …/CHAR_0001_EXEMPLAR_BUST_14_V3_BALD_34.png | head-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.
Trellis2ImageTo3DPipeline exposes run(self, image: Image.Image, …) only — there is **no 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).
run() L536-537 hard-wires a single image: cond_512 = self.get_cond([image], 512).get_cond accepts a list, but DinoV3FeatureExtractor.__call__ (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.
---
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.
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:
to_glb as upstream ships it, nvdiffrast included. Output lands under 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.
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
run_trellis2_native_pbr.py — the armDerived 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()
render_glb_turntable.py — the straight render, on the SAME instrumentThe 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
sheet_native_vs_chain.py — the side-by-sideModelled on build/3d/bakeoff/build_sheet.py (which already knows how to put arms, angles and
numbers on one face). Three rows, four columns:
| row | source |
|---|---|
| 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:
LICENCE TIER: the native row is nvdiffrast-derived, EVALUATION ONLY, and never ships as-is.(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.
(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.json → VERDICT.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.
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.**
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).
---
authored, not run, per lane law.
trimesh 5.0.0 accepts extension_webp=True. Handled by a recorded fallback in §5.2rather than asserted.
to_glb at 4096². The geometry runs peaked at 2.74 GB allocated and the box had 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.
cumesh-vendors-cubvh residual for a ship-tier build. Named in 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.
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:
docs/MODEL_STACK_AUDIT_AUG2026.md L345-348 ruled this line *"correct about nvdiffrast andover-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.
---
authored, not run, per lane law.
trimesh 5.0.0 accepts extension_webp=True. Handled by a recorded fallback in §5.2rather than asserted.
to_glb at 4096². The geometry runs peaked at 2.74 GB allocated and the box had 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.
cumesh-vendors-cubvh residual for a ship-tier build. Named in 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.
Trellis2TexturingPipeline, 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.