pipelines/CORRECT_PIPELINE_AUDIT_2026-08-12.json
{
"audit": "the CORRECT pipeline for photoreal + identical, replicable across all asset classes",
"dated": "2026-08-12",
"prompted_by": "Josh rejected the director's 'structural ceiling' claim as user error. Five adversarial lanes were told to be blunt about the director's mistakes.",
"lanes": [
{
"lane": "THE HIGH-POLY BAKE GAP — is the 4.9M mesh persisted, what is the correct bake path with tools on disk, and does the 1536 grid physically resolve pores",
"my_error": "The director is wrong on the headline and wrong about which numbers matter, and the repo falsifies the claim three separate times.\n\n1. \"TRELLIS emits no normal map, so pores have nowhere to live\" is not a structural ceiling, it is a missing step we have already built, shipped, and run at scale. `C:/dev/humanity-forgotten/build/3d/characters/bl_char_bake_export.py:294-322` is a working headless Blender tangent-space high-to-low normal bake for CHARACTERS, already executed against a 120k high-poly. `D:/assetgen/bin/bl_finish2.py:250-278` is the production version and `C:/dev/humanity-forgotten/build/3d/texel_coverage.json` records 254 assets measured through it, each with `normal_baked: true`. `C:/dev/humanity-forgotten/build/3d/creature_normals/` holds before/after clay+texture photobooks proving the same path ran on creatures. We have a normal-map pipeline. It has simply never been pointed at the TRELLIS high-poly.\n\n2. We do not merely decimate the 4.9M mesh — we compute it into a numpy array, hash it, and then let it die. `run_trellis2_native_pbr_mv.py:280-283` builds `v_np`/`f_np`, `:288-289` sha256s them for the positive control, and `:316` hands the torch tensors straight to `to_glb`, which calls `mesh.simplify(decimation_target*3)` at `o_voxel/postprocess.py:136` and `mesh.simplify(decimation_target)` at `:149`. Nothing is written. The detail source of every AAA character pipeline is sitting in RAM and we `del` it. That is a six-line omission, not a ceiling.\n\n3. The director never did the arithmetic, so he named the wrong ceiling. Two ceilings exist and neither is \"no normal channel\":\n (a) GEOMETRY GRID. `voxel_size = 1/resolution` (`trellis2_image_to_3d.py:479`); every ladder record on disk carries `0.0006510416666` = 1/1536 exactly, and raw verts ≈ attr_voxels (A14 body: 1,967,879 verts / 1,963,877 surface voxels = 1.002), so dual contouring emits exactly ONE surface sample per voxel and the Nyquist relief wavelength is 2 voxels. On a FULL FIGURE the box maps to subject height, so at the canon 129 cm child the voxel is 1.29/1536 = 0.84 mm (Nyquist 1.68 mm) and at ~155 cm it is 1.01 mm (Nyquist 2.02 mm). Web-checked pore scale: pilosebaceous openings 40–80 µm, visible/enlarged pores 250–500 µm. So a full-figure run misses pores by 4–25x. TRUE — but it comfortably carries the nasolabial fold, philtrum, vermillion border, eyelid crease, tear trough and ear helix, which is the IDENTITY band, and decimating 4.9M→685k (sampling 1.6 mm) throws exactly that band away. The director called the band we are destroying a band that never existed.\n (b) TEXTURE ATLAS — completely unmeasured by anyone. `postprocess.py` packs the WHOLE mesh into ONE `texture_size²` atlas. A 4096 atlas over a ~1.7 m² figure at the ~0.57 UV utilisation this repo actually measures gives 9.56e6 usable texels = 0.42 mm/texel. Even a flawless bake cannot STORE a 250 µm pore there. Give the head its own 4096 (0.09 m²) and it is 0.095 mm/texel; at 8192 it is 0.047 mm/texel — one texel per follicle opening. The head having its own atlas is worth more than every parameter the director has touched.\n\n4. The free 4–5x nobody claimed. The `A14_face_bald` / `A11_face_bald` runs ALREADY EXIST in QUARANTINE_EVAL_ONLY. Their surface-voxel counts (A14 face: 2,113,523 voxels over a head-and-neck crop ≈ 0.09 m²) put the voxel at ~0.20 mm versus 0.84–1.01 mm for the body — a 4.2–5.0x linear detail multiplier already sitting on disk, obtained by cropping, and then thrown away by decimating to 200k into a 2048 atlas. Scale normalisation is the single biggest lever in this lane and it costs nothing.\n\n5. A factual error in our own tooling that the capability diff repeated: `run_trellis2_native_pbr_mv.py:102-105` asserts the 49152 token default \"DOWNGRADES resolution\" and the audit calls raising it MANDATORY. Every ladder record on disk ran at `max_num_tokens: 49152` and every one records voxel_size = 1/1536 exactly. Had the budget bound, `trellis2_image_to_3d.py:336-339` would have printed the downgrade and the record would read 1/1408. It did not. The 131072 raise is harmless and irrelevant; treating it as the fix is why five ladder ages got re-run for nothing.\n\n6. Where the director is actually right, and it is the small half: below ~0.40 mm nothing in the generation carries signal at ANY crop, so the final micro-octave (40–80 µm follicles, microrelief furrows) genuinely must be synthesised. But per-character monocular normal estimation (SKAP/Marigold/StableNormal) is the wrong shape for it. Nobody in AAA sculpts 40 µm pores per asset; they blend ONE tiling micro-detail normal+cavity set, masked by baked curvature. That is one texture reused by every character, NPC, creature and boss — the exact replicability the brief demands — not 84 assets of inference.\n\nMeshy at $100/mo buys none of this. It does not hand you a 4.9M high-poly to bake from and it does not fix atlas texel density, which is arithmetic, not a vendor.",
"correct_steps": [
"PERSIST THE HIGH-POLY (6 lines, lane-owned file, no vendor edit). In C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py after line 292 (v_np/f_np already exist), add --save-high and write: hv = v_np.copy(); y = hv[:,1].copy(); z = hv[:,2].copy(); hv[:,1] = z; hv[:,2] = -y; trimesh.Trimesh(vertices=hv, faces=f_np, process=False).export(out.replace('.glb','_HIGH.ply')). The explicit .copy() is required — postprocess.py:313 does the same swap via tuple aliasing and only works by accident. Record vertices/faces/sha256 in the JSON so the artifact is provable. Expected: ~4.9M verts / 9.85M faces, ~250 MB binary PLY.",
"CARRY ATTRIBUTES ONTO THE HIGH-POLY (optional but free). In the same block, call flex_gemm.ops.grid_sample.grid_sample_3d(attr_volume=mesh.attrs, coords=mesh.coords, shape=[1,6,*grid], grid=((v_np-aabb[0])/voxel_size).reshape(1,-1,3), mode='trilinear') — identical to postprocess.py:260-266 but evaluated at the RAW vertices instead of at low-poly texel positions. Write base_color as PLY vertex colors. This gives a basecolor bake source at full grid fidelity instead of the current path, which resamples the volume through a decimated surface.",
"MEASURE BEFORE TRUSTING THE BAKE — displacement. Load _HIGH.ply and the exported 685k GLB in pymeshlab, canon-scale both to metres, run get_hausdorff_distance point-to-surface high->low (the exact call this repo already uses; see the 'method' field in build/3d/candidates/flores_props/*/generation.json). Report max/mean/rms/p95 in MILLIMETRES. GATE: if p95 < 0.1 mm the decimation cost nothing and a bake is pointless; if p95 is 0.5-3 mm the meso band is real and must be recovered. Predicted p95 ~1-2 mm at body scale (685k verts = 1.6 mm sampling vs 0.84-1.01 mm voxel).",
"MEASURE — spectrum. Bake a height map high->low at 4096, radially-average its 2D FFT, plot power vs cycles/mm. Real relief shows power out to the grid Nyquist then a cliff. The frequency of that cliff IS the measured ceiling, stated as a number instead of an opinion. Twenty lines of numpy on top of one bake. This single plot settles the whole argument and should be the artifact handed to Josh.",
"MEASURE — curvature and eyes. pymeshlab compute_curvature_principal_directions_per_vertex on the high, histogram |H| in 1/mm restricted to face vertices; a smoothed diffusion surface is near-Gaussian at zero, real wrinkles give a heavy tail. Then render both meshes clay/matcap, no texture, identical rim light, face filling 1024 px, using the existing build/3d/bakeoff/native_tex/render_glb_turntable.py. Compare the FACE-crop run's face region against the BODY run's face region: same subject, 4-5x grid, so the tail MUST widen if the extra grid is signal and not noise.",
"REGENERATE THE LADDER AS HEAD-ONLY RUNS. Re-run each age as a separate head/bust generation at --pipeline-type 1536_cascade --no-low-vram, using the crops already catalogued in build/3d/characters/CHAR_0001_BODY_V3_HEAD_CROPS.json and CHAR_0001_EXEMPLAR_BUST_LADDER_V3_34_SPLIT.json. Measured payoff from the A14_face_bald / A11_face_bald records already on disk: ~0.20 mm/voxel versus 0.84-1.01 mm for full figures, a 4.2-5.0x linear detail multiplier for zero new tooling. Set --decimation 5000000 (both simplify() calls only reduce, so this makes them no-ops) so the exported mesh is not pre-destroyed, and --save-high regardless.",
"RETOPO A CLEAN LOW. Blender 5.2 headless: bpy.ops.object.quadriflow_remesh(target_faces=N, mode='FACES', use_preserve_sharp=True, use_preserve_boundary=True, use_mesh_symmetry=True, seed=0). Head 15-25k tris with loops around eyes and mouth; body 30-50k. The prop chain already records topology_route: 'quad_remesh', so this route is proven in-house. Then UV: head on its OWN atlas (separate object, separate material slot), body on another — bpy.ops.uv.smart_project(island_margin=0.003) or xatlas. Do NOT share one atlas; see the texel arithmetic in step 8.",
"HONOUR THE TEXEL ARITHMETIC WHEN SIZING MAPS. One 4096 atlas over a whole ~1.7 m2 figure at the ~0.57 UV utilisation this repo measures = 9.56e6 usable texels = 0.42 mm/texel, which physically cannot store a 250 um pore no matter how good the bake is. Head alone (~0.09 m2) on its own 4096 = 0.095 mm/texel; on 8192 = 0.047 mm/texel, roughly one texel per 40-80 um follicle opening. Therefore: head 8192, body 4096, hair/garment 4096. This is the highest-value change in the lane and it is a UV decision, not a model decision.",
"BAKE ON THE GPU. bl_char_bake_export.py:121 sets sc.cycles.device = 'CPU' with the comment that the card belongs to other lanes. Local compute is free and the box runs 24/7. Set bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'OPTIX', call get_devices(), enable the 5090, then scene.cycles.device = 'GPU'. Expect roughly an order of magnitude on the AO and curvature passes.",
"FIX THE CAGE — this is the difference between a face bake and mud. bl_char_bake_export.py:310-311 sets cage_extrusion = diag*0.02 and max_ray_distance = diag*0.04; for a 1.29 m figure (diag ~1.63 m) that is 32.6 mm extrusion and 65 mm ray. On a face, 32 mm of extrusion means rays from the nose bridge hit the eyeball and rays inside the ear hit the skull. Bake the HEAD as its own object with cage_extrusion 0.001-0.002 m and max_ray_distance 0.002-0.004 m; body keeps a larger cage. Per-region cages are the reason to split the objects in step 7.",
"RUN THE NORMAL BAKE WITH THE RIGHT SETTINGS. scene.render.bake.use_selected_to_active = True (high selected, low active), normal_space = 'TANGENT', margin = 32, margin_type = 'ADJACENT_FACES', cycles.samples = 1 (a normal bake is a deterministic ray query; the existing samples=4 at :313 is pure waste), image non-color with fill (0.5,0.5,1.0,1), then bpy.ops.object.bake(type='NORMAL').",
"SET THE GREEN CHANNEL FOR UE5 — silent ship-blocker. Blender bakes OpenGL convention (+Y up); UE5 expects DirectX (-Y). Set scene.render.bake.normal_g = 'NEG_Y' before the bake, OR tick Flip Green Channel on import. NEITHER bl_char_bake_export.py NOR bl_finish2.py does either, and no UE5 import manifest under build/3d/characters/ mentions it. Every normal map we have baked is therefore inverted in-engine: lit-from-above detail reads as lit-from-below, which looks exactly like 'flat plastic skin'. Verify with a lit sphere before re-baking 254 assets.",
"BAKE THE CHANNELS WE HAVE NEVER BAKED. Add: AO at cycles.samples=256 (existing default 64 is noisy at 8192); CAVITY/CURVATURE as a bake(type='EMIT') off ShaderNodeNewGeometry 'Pointiness' through a ColorRamp — this is the map the UE5 Substrate skin shader needs to mask micro-detail; and a HEIGHT/displacement pass. Pack AO/curvature/cavity into one RGB. Only NORMAL and AO exist today (bl_char_bake_export.py:318, :336).",
"SYNTHESISE ONLY THE LAST OCTAVE, ONCE, FOR EVERYTHING. Below ~0.40 mm no crop carries signal, so author ONE tiling skin micro-detail normal+cavity pair and blend it in the UE5 Substrate material masked by the baked curvature. Free route with zero licence risk: Blender procedural Voronoi F1 + wave distortion + noise warp, baked to a 2K tiling normal — 30 minutes, no download. Reuse the identical texture across every character, NPC, creature and boss, scaled per subject. Do this AFTER steps 3-5 report their numbers; if p95 displacement comes back thin, only then is a learned normal estimator worth evaluating."
],
"features_or_models_needed": [
"Blender 5.2 headless at D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe — ALREADY HAVE, already used headless. Provides Cycles selected-to-active bake (NORMAL/AO/EMIT/DISPLACEMENT), auto-cage via cage_extrusion+max_ray_distance, QuadriFlow remesh, smart_project UV, decimate, FBX/GLB export, and normal_r/g/b swizzle for the UE5 green flip. Cost: 0. This is the entire bake path.",
"bl_char_bake_export.py at C:/dev/humanity-forgotten/build/3d/characters/bl_char_bake_export.py — ALREADY HAVE AND ALREADY RUN. Working character high->low tangent-space normal + AO bake with LOD chain and FBX/GLB export. Needs four edits, not a rewrite: GPU device (:121), face-scale cage (:310-311), normal_g='NEG_Y', bake_res 4096/8192 (:53). Cost: 0.",
"bl_finish2.py / bake_only.py / rebake_fill.py at D:/assetgen/bin/ — ALREADY HAVE. The production baker with 254 assets measured through it (build/3d/texel_coverage.json), each carrying normal_baked: true. Same four defects. Cost: 0. Note D:/assetgen is declared read-only to the TRELLIS lane, so fork the logic into build/3d/characters/ rather than editing in place.",
"trimesh — ALREADY HAVE (imported by o_voxel/postprocess.py:7 and used by the runner at :326). Needed only to write the raw 4.9M mesh as binary PLY. Cost: 0. Critically, this export does NOT touch nvdiffrast, so the high-poly artifact is NOT under the EVALUATION_ONLY licence that quarantines the textured GLB.",
"flex_gemm.ops.grid_sample.grid_sample_3d — ALREADY HAVE (o_voxel/postprocess.py:9). Needed to sample base_color/metallic/roughness onto the raw high-poly vertices. Cost: 0.",
"pymeshlab — VERIFY INSTALLED; the prop records cite 'pymeshlab get_hausdorff_distance, point-to-surface' so it is almost certainly present in the finishing venv. Needed for the displacement gate and per-vertex curvature. Free: pip install pymeshlab. Cost: 0.",
"QuadriFlow — ALREADY HAVE, ships inside Blender as bpy.ops.object.quadriflow_remesh. The prop chain already records topology_route: 'quad_remesh'. Cost: 0. No need for Instant Meshes or ZRemesher.",
"Tiling skin micro-detail normal + cavity (the ONLY thing that must be synthesised, and only below ~0.40 mm). Free route: authored procedurally in Blender (Voronoi F1 + wave distortion), baked once to a 2K tiling pair, reused by every character/NPC/creature/boss. Cost: 0, no download, no licence. Paid alternatives exist (photogrammetric pore libraries) but are not required and are not per-asset.",
"Marmoset Toolbag 5 — NOT NEEDED. Web-checked 2026 pricing: $399 perpetual individual / $1,299 studio, or $18.99/mo individual. Its advantage over Blender is per-island cage painting and skew correction; step 7 (split head into its own object with its own small cage) achieves the same thing for free. Revisit only if a measured skew artifact survives the split.",
"xNormal — NOT NEEDED. Free, Windows, but it adds an OBJ round trip on a 4.9M mesh and does nothing Blender 5.2 cannot. Meshy at $100/mo — buys NOTHING in this lane: it does not emit a 4.9M-vertex high-poly you can bake from, and it cannot fix atlas texel density, which is arithmetic, not a vendor."
],
"blockers_that_are_real": [
"nvdiffrast EVALUATION_ONLY licence on o_voxel.postprocess.to_glb. run_trellis2_native_pbr_mv.py:10-13 and the hard refusal at :124-126 mean every textured artifact this runner has ever produced is quarantined and unshippable, at ANY resolution. This is a licensing fact and no parameter change touches it. It is also solved by the plan rather than blocked by it: the proposed raw-PLY export uses trimesh only and never calls nvdiffrast, and steps 7-13 replace the vendor UV+texture step with our own QuadriFlow/xatlas + Blender bake. But until that replacement lands, nothing textured from this lane can ship. Nobody should re-run the ladder at higher settings expecting a shippable result.",
"Every normal map baked to date is probably green-inverted for UE5. Blender bakes OpenGL (+Y); UE5 expects DirectX (-Y); neither bl_char_bake_export.py nor D:/assetgen/bin/bl_finish2.py sets scene.render.bake.normal_g, and no UE5 import manifest under build/3d/characters/ mentions Flip Green Channel. If confirmed on a lit sphere, 254 shipped assets carry inverted detail — which presents in-engine as exactly the flat, waxy, plastic read this whole investigation is chasing. Verify this FIRST; it is cheap and it may be a large fraction of the symptom.",
"Below ~0.40 mm the generation carries no signal at any crop, at any pipeline_type, at any token budget. Voxel is 1/1536 of the subject's longest real extent by construction (trellis2_image_to_3d.py:479), the mesh gets exactly one sample per voxel, and follicle openings are 40-80 um. This octave is genuinely not recoverable by baking and must come from a tiling overlay. It is a real physical limit — but it is the SMALL half of the problem, it is solved once with one texture for all asset classes, and the director wrongly generalised it to the entire detail budget."
],
"replicable_across_classes": "SHARED BY EVERY CLASS — characters, NPCs, creatures, bosses, props, weapons. (1) The --save-high change to run_trellis2_native_pbr_mv.py is subject-agnostic; every generation this box has ever made and will ever make gains a real detail source for six lines. (2) The bake path is already class-general and already proven on three classes: 254 props/weapons through D:/assetgen/bin/bl_finish2.py, creatures with before/after photobooks under build/3d/creature_normals/, and a character through build/3d/characters/bl_char_bake_export.py. The four fixes (GPU device, per-region cage, normal_g='NEG_Y', bake_res) are edits to shared code, so fixing them fixes every class at once — and re-baking the existing 254 assets through the corrected script is a batch job, not 254 decisions. (3) The tiling micro-detail normal+cavity is authored ONCE and blended by baked curvature in the UE5 Substrate material; a boss's hide, an NPC's cheek and the hero's nose all sample the same texture at different world scales. That is the answer to \"replicable, not a one-off for the hero\" — the expensive part is a shared asset, not a per-character inference run. (4) The texel-density rule generalises as a formula rather than a number: texel_edge = sqrt(surface_area_m2 / (res^2 * uv_utilisation)). Apply it per asset, give any region that must survive a close-up its own atlas, and the answer falls out. For a head that means 8192; for a distant NPC 2048 is correct and cheaper.\n\nPER-CLASS, AND ONLY THESE. (a) Crop scale, which is the free 4-5x: the box always maps to the subject's longest real extent, so anything that must hold up in a close-up gets generated in its own crop. Heads separately from bodies for humanoids; for a boss that means the face/head plate separately from the silhouette. (b) Cage extrusion, which must be a fraction of the LOCAL feature size, not of the object diagonal — the current diag*0.02 is right for a 40 cm prop and catastrophic for a face. (c) Retopo target and loop placement: 15-25k for a head with eye/mouth loops, 30-50k body, 5-15k props, and creature limbs need their own budget. (d) Roughness/metallic remain declared constants grounded per material class, exactly as the existing records already do it.\n\nWHAT THIS MEANS FOR THE FACE LADDER SPECIFICALLY. The seven ages are seven runs of the same subject-agnostic recipe. Identity consistency across the ladder is a conditioning problem, not a detail problem — and it is a different lane. This lane's contribution is that whatever identity the plates carry will actually SURVIVE into the mesh, because today 4.9M vertices of it are being deleted before anyone looks.",
"evidence_note": "Every claim below is read off disk or web-checked this session; nothing is recalled.\n\nDISCARD CONFIRMED. run_trellis2_native_pbr_mv.py:280-283 builds v_np/f_np; :288-289 sha256s them; :316 passes mesh.vertices/mesh.faces to to_glb; o_voxel/postprocess.py:136 and :149 call mesh.simplify. No write of the raw mesh exists anywhere in the file. The high-poly is computed, hashed, and dropped.\n\nBAKERS EXIST AND HAVE RUN. build/3d/characters/bl_char_bake_export.py:294-322 (character NORMAL bake, use_selected_to_active=True, normal_space='TANGENT', :310-311 cage) and :330-336 (AO). D:/assetgen/bin/bl_finish2.py:250-278 same. build/3d/texel_coverage.json \"measured\": 254. Prop record build/3d/candidates/flores_props/FL_0001/generation.json carries normal_bake {baked: true, space: TANGENT, high_poly: FL_0001_pre.obj, cage_extrusion_m: 0.011656} and honest_tier.normal \"REAL -- tangent-space bake from the dense repaired mesh\". build/3d/creature_normals/photobook/ holds before/after clay+tex sheets for SM_CR_0001..0003 and SK_CR_0001..0002.\n\nVOXEL ARITHMETIC. trellis2_image_to_3d.py:479 voxel_size = 1/resolution; :582 shows 1536_cascade calls sample_shape_slat_cascade(lr=512, resolution=1536) driving the shape_slat_flow_model_1024 on a 96^3 token grid. Nine ladder records under build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY/ all carry voxel_size 0.0006510416666 = 1/1536 exactly. One sample per surface voxel proven by A14_body_bald: attr_voxels 1,963,877 vs raw vertices 1,967,879 (ratio 1.002); A11_body_bald 2,444,672 vs 2,449,487. Full-figure bbox spans ~0.995 units in Z and equals stature (CHAR_0001_CHILD_proportions.json: stature_cm 129.0), giving 0.84 mm/voxel at 129 cm and 1.01 mm at 155 cm; Nyquist 1.68-2.02 mm. A14_face_bald: 2,113,523 surface voxels over a head-and-neck crop; solving N*s^2 = ~0.09 m2 gives s ~= 0.20 mm, Nyquist ~0.40 mm — 4.2-5.0x finer than the body runs, on runs that already exist.\n\nPORE SCALE, WEB-CHECKED 2026-08-12. Pilosebaceous follicle openings 40-80 um; visible/enlarged pores approximately 250-500 um (Dove Medical Press, \"Facial skin pores: a multiethnic study\"; dermcollective.com/large-pores). Conclusion: full-figure grid misses pores by 4-25x, head-crop grid touches only the coarse end, so the sub-0.40 mm octave must be synthesised at every crop scale — and only that octave.\n\nATLAS ARITHMETIC. postprocess.py takes a single texture_size and packs the whole mesh into one atlas (:233, :259). Measured UV utilisation in this repo is 0.5679 (FL_0001). 4096^2 * 0.57 = 9.56e6 texels over ~1.7 m2 = 0.42 mm/texel; head alone (~0.09 m2) on 4096 = 0.095 mm/texel, on 8192 = 0.047 mm/texel.\n\nTOKEN-BUDGET CLAIM FALSIFIED. trellis2_image_to_3d.py:327-339 steps hr_resolution down by 128 and prints \"Due to the limited number of tokens...\" when it binds. All nine ladder records ran at capacity.max_num_tokens 49152 and every one records voxel_size = 1/1536, so it never bound. The runner's own help text at :102-105 and the capability diff's \"MANDATORY\" recommendation are both wrong on our data.\n\nMARMOSET PRICING, WEB-CHECKED 2026-08-12: $399 perpetual individual, $1,299 studio, $18.99/mo individual (marmoset.co, cgchannel). xNormal free. Neither is needed.\n\nWHAT I COULD NOT VERIFY. (1) I did not open a GLB to confirm the 685k mesh's true metric vertex spacing — the displacement gate in step 3 is exactly the measurement that closes this, and I am predicting p95 1-2 mm, not asserting it. (2) I inferred the face-crop real-world scale from surface-voxel count and an assumed 0.09 m2 head area; the bbox of A14_face_bald (0.994 x 0.510 x 0.416 units) has its longest axis in X, which is unusual for a head and may reflect pointed elven ears, a hair plane, or an axis convention the model chose freely for a cropped subject. Measure the crop's real extent before quoting 0.20 mm as fact; the 4-5x RATIO versus the body is robust regardless of which absolute value is right. (3) I did not confirm the green-channel inversion empirically — I confirmed only that neither script sets normal_g and no import manifest mentions the flip. Test it on a lit sphere before re-baking anything. (4) I did not check whether pymeshlab is installed in the finishing venv, only that prop records cite its API."
},
{
"lane": "IDENTICAL — end-to-end identity chain (2D plate → MV views → TRELLIS geometry) for face AND body, measured against the approved 7-rung ladder",
"my_error": "The director is HALF RIGHT ON THE NUMBER AND WRONG ON EVERY CONCLUSION DRAWN FROM IT.\n\n1. VERIFIED, with a caveat that destroys the diagnosis. `TextEncodeQwenImageEditPlus` (D:/comfyui/ComfyUI/comfy_extras/nodes_qwen.py:80-88) resizes each reference to a FIXED TOTAL of 384*384 = 147,456 px, aspect-preserved. On our measured body plate (672x1568) that gives a 251x586 VL image, and a head at 1/7.3 of standing height = 80 px. So ~90px is real. BUT the budget is spent by FRAMING, not by resolution — feed it the 996x2728 SeedVR2 upres of a head-and-shoulders panel and the VL face is ~270px; feed it the 249x682 raw panel and it is ALSO ~270px. Identical. The director proposed a RESOLUTION fix (bigger plates, upres) for a FRAMING problem. SeedVR2's 4x buys exactly zero VL pixels. That is a user error, not a ceiling.\n\n2. THE VL BRANCH IS NOT THE MAIN IDENTITY PATH. The same node VAE-encodes each reference at 1024*1024 into `reference_latents` (nodes_qwen.py:89-95) — 7.1x the pixels, and that is what the DiT actually reconstructs from. Naming the VL branch as the cause of \"starved identity\" is an incomplete read of a node we wired ourselves. Also `TextEncodeQwenImageEdit` (non-Plus, :36-44) already runs VL at 1024x1024; we chose the 384 node.\n\n3. THE REAL STARVATION IS DOWNSTREAM AND THE DIRECTOR NEVER OPENED IT. TRELLIS.2 conditions geometry on DINOv3-ViT-L/16 ONLY (D:/assetgen/models/TRELLIS2/pipeline.json → facebook/dinov3-vitl16-pretrain-lvd1689m). `preprocess_image` (trellis2_image_to_3d.py:137-139, 152-157) clamps the max side to 1024 and then SQUARE-crops to the alpha bbox at side=max(bbox). A full-figure view therefore hands DINOv3 a head ~140px tall = ~8.75 patches at patch-16. A head-framed view hands it ~600px = ~37 patches. That is a 4.3x identity-information swing at the ONLY stage that decides face geometry, it is set purely by framing, and it is free.\n\n4. \"PHOTOREAL IS CEILINGED BECAUSE NO NORMAL MAP\" IS FALSE AND ALREADY SELF-REFUTED IN OUR OWN FILE. run_trellis2_native_pbr_mv.py:311-326 now SAVES the 4.9M-vert high-poly. A tangent-space normal/AO/cavity bake off that high-poly onto a retopo low-poly is a headless Blender step (Blender 5.2 is installed and already used headless). SKAP/Marigold/StableNormal are not needed to \"synthesise a missing channel\" — the channel's source geometry exists on disk. Missing STEP, not structural limit.\n\n5. THE IDENTITY LENS HAS NEVER PRODUCED A NUMBER, AND THE STATED REASON IS FALSE. L11 is not \"descriptive at n_c1=3\" — it is `REFUSED` because the SFace/YuNet weights were never downloaded (harness/asset_factory/likeness_lens.py:1553-1565). Its `why_not_arcface` field claims InsightFace-class weights are non-commercial; that is factually wrong for `onnxmodelzoo/arcfaceresnet100-8`, which is Apache-2.0. We have been unable to certify likeness for weeks because nobody fetched a ~250 MB file that needs no permission.\n\n6. WE HAVE NEVER TRAINED A LORA AND THE TRAINER IS ALREADY INSTALLED. ComfyUI ships `TrainLoraNode` (comfy_extras/nodes_train.py:955), `LoadImageTextDataSetFromFolder` (nodes_dataset.py:213), `SaveLoRA` (:1364), `LossGraphNode` (:1408). Zero custom nodes are installed, so nothing to add. Josh's instinct is correct and it is the single largest unused lever in this lane.\n\n7. FOUR MORE SELF-INFLICTED LOSSES: (a) mva_views.py:240 hardcodes stock SDXL base — the plastic-doll skin IS that checkpoint's prior; the vendor script exposes --base_model/--unet_model/--lora_model/--lora_scale (inference_i2mv_sdxl.py:186-194) and we expose none of it. (b) MV-Adapter's own script defaults height=768 (inference_i2mv_sdxl.py:254, README \"with 768 Resolution using SDXL\"); char_pipeline_from_ladder.py:250 runs it at --res 1024, off-distribution. (c) MV-Adapter's pipeline ALREADY accepts `ip_adapter_image`/`ip_adapter_image_embeds` (pipeline_mvadapter_i2mv_sdxl.py:600-608) and `mv_scale`; mva_views.py passes neither — a free IP-Adapter identity path sitting unused inside code we run daily. (d) `harness/asset_factory/body_plate_head_crop.py` already exists, already has a MediaPipe FaceMesh \"the bust contains a face\" control, and is NOT wired into the ladder pipeline.\n\n8. ONE MEASUREMENT THE DIRECTOR SHOULD HAVE LED WITH: the approved ladder panels are 249x682 px (measured), cropped from an 1774x887 sheet. In AGE_14_face.png the head is ~290 px crown-to-chin and interocular distance is ~55 px. That is the total real identity information in the project. Everything else is preservation or hallucination. Nobody wrote that number down before now.\n\n9. TRELLIS'S MULTI-VIEW INJECTION HAS NO CROSS-VIEW ATTENTION. trellis2_multiview.py:154-180 either cycles one view per step (`stochastic`) or averages per-view predictions (`multidiffusion`). Disagreement between views is therefore resolved by AVERAGING into mush. View-set self-consistency is a hard input requirement and we have never gated it.",
"correct_steps": [
"STEP 0 — ARM THE METRIC BEFORE TOUCHING THE PIPELINE. Download onnxmodelzoo/arcfaceresnet100-8 (Apache-2.0, ~250 MB, free, no permission needed) into D:/assetgen/models/arcface/. Run it in the pinned face312 venv with onnxruntime-gpu: detect+5-point-align with the MediaPipe FaceMesh already imported by harness/asset_factory/body_plate_head_crop.py, warp to 112x112, forward, L2-normalise the 512-d output, score cosine similarity. Concrete bars: cos>=0.50 = 'same person' (R100 IJB-C FAR 1e-4 operating point); cos>=0.60 = SHIP bar for a hero, because we control both sides of the pair. Report BOTH cosine and the yaw of each side.",
"STEP 0b — MAKE THE LENS ARMABLE, which it is not today. Build the corpus in one run: 7 panels x 7 panels = 21 same-person cross-age pairs, plus impostor pairs from the rejected exemplars in E:/art/boards/D1-09/CHAR_0001. n=21 clears the n>=8 floor likeness_lens.py enforces, so L11 can ARM rather than stay DESCRIPTIVE. Kill the yaw contamination the lens names by (a) measuring yaw with MediaPipe FaceMesh and only scoring pairs within ±10°, and (b) re-rendering the mesh at the plate's MEASURED yaw via likeness_lens.camera_from_known_azimuth. Delete the `why_not_arcface` licence claim — it is wrong for the Apache-2.0 ONNX weight.",
"STEP 1 — FIX FRAMING, NOT RESOLUTION, IN EVERY QWEN EDIT. Wire harness/asset_factory/body_plate_head_crop.py into char_pipeline_from_ladder.py and pass THREE references to TextEncodeQwenImageEditPlus: image1 = full plate, image2 = 1:1 head crop (LONG_SIDE 1344), image3 = age-matched approved panel. Each reference gets its OWN 384x384 VL budget and its OWN 1024x1024 reference_latent (nodes_qwen.py:80-95), so the face goes from ~80px to ~340px in VL and ~870px in the latent branch. For any single-reference edit, use TextEncodeQwenImageEdit instead of Plus (1024x1024 VL, nodes_qwen.py:36-44).",
"STEP 2 — BREAK THE 1.05 MP PLATE CEILING. In harness/asset_factory/stage_b_body_base.py, replace node 13 FluxKontextImageScale (:137) with ImageScaleToTotalPixels at ~1.7 MP in a body aspect (1056x1584 or 1104x1472), and delete the dead CFG['size']=1328 (:51, `n` at :129 is never used). Measured today: every body plate is 672x1568 = 1.05 MP because FluxKontextImageScale is the only resolution authority in the graph.",
"STEP 3 — BOOTSTRAP THE IDENTITY DATASET (this is what makes 7 panels enough). Per age, generate 15-25 Qwen-Image-Edit-2511 variations FROM the panel: yaw sweep -30°..+30° in 15° steps, two elevations, three lighting setups, neutral/slight-smile. Then GATE every generated image with Step 0's ArcFace score against its own age panel and KEEP ONLY cos>=0.55. Target ~120-200 surviving images across the ladder. Unfiltered bootstrapping is what makes identity LoRAs drift; the filter is the whole trick.",
"STEP 4 — TRAIN THE IDENTITY LoRA LOCALLY, ZERO DOWNLOADS. ComfyUI graph, all native: LoadImageTextDataSetFromFolder (nodes_dataset.py:213) -> ResolutionBucket -> VAEEncode -> TextEncodeQwenImageEditPlus -> TrainLoraNode (nodes_train.py:955) -> SaveLoRA (:1364) + LossGraphNode (:1408). Settings: rank 32, lr 1e-4, AdamW, MSE, ~2000 steps, training_dtype bf16, gradient_checkpointing True, offloading True (32 GB card + 128 GB RAM), bucket_mode True. Caption every image with a rare identity token PLUS an explicit age token so age stays a controllable axis instead of averaging into one face.",
"STEP 5 — TRAIN THE SAME DATASET AS AN SDXL LoRA for the view stage, and expose it. Add --base-model, --unet-model, --lora-model, --lora-scale, --ip-adapter-image, --mv-scale, --scheduler to harness/asset_factory/mva_views.py, mirroring inference_i2mv_sdxl.py:186-194 and pipeline_mvadapter_i2mv_sdxl.py:600-608 (both already support all of it; we pass none of it). Point --base-model at a photoreal SDXL finetune (README:194 ships the recipe: stablediffusionapi/real-dream-sdxl with --scheduler ddpm), stack the identity LoRA, and pass the head crop as ip_adapter_image.",
"STEP 6 — RUN MV-ADAPTER AT ITS TRAINED RESOLUTION. Change char_pipeline_from_ladder.py:250 from --res 1024 to --res 768. inference_i2mv_sdxl.py:254 defaults height=768 and the README titles the SDXL route '768 Resolution'. We have been generating every view off-distribution and reading the result as a model limit.",
"STEP 7 — BAKE OFF THE VIEW SOURCE INSTEAD OF ASSUMING MV-ADAPTER. Generate the SAME six azimuths a second way, with Qwen-Image-Edit-2511's own novel-view capability (the multi-angle LoRA is baked into the 2511 base per the model card) at 1.7 MP with all three references from Step 1. Score both sets with Step 0's ArcFace against the age panel and keep the winner per age. MV-Adapter is a Dec-2024 SDXL adapter; in Aug 2026 it must earn its place, not inherit it.",
"STEP 8 — GATE VIEW-SET CONSISTENCY BEFORE GEOMETRY. TRELLIS's injection never attends across views (trellis2_multiview.py:154-180: one view per step, or averaged), so any disagreement between views is resolved by averaging into mush. Refuse a view set unless (a) pairwise ArcFace cos between view_00 and every other face-bearing view >= 0.55, and (b) silhouette subject-height agreement across views is within the 7.66% band already measured in CHAR_0001_V3_MULTIVIEW_READINESS.json. Fail = regenerate, never proceed.",
"STEP 9 — SPLIT THE GEOMETRY SUBJECT SO THE FACE GETS ITS OWN DINOv3 BUDGET. TRELLIS square-crops the alpha bbox and clamps to 1024 (trellis2_image_to_3d.py:137-139, 152-157), so a full-figure view spends only ~140px / ~9 patches on the head. Run the HEAD as its own TRELLIS subject at head framing (~600px, ~37 patches) and the BODY as its own — the three-subject design already exists in char_pipeline_from_ladder.py, it is just never joined. Add the JOIN: headless Blender transfers the head mesh onto the body mesh at the measured neck ring, with a shrinkwrap/boolean weld. This decouples face resolution from body extent permanently.",
"STEP 10 — STOP OVERRIDING OUR OWN CAPACITY. char_pipeline_from_ladder.py:275 passes --texture-size 2048, overriding run_trellis2_native_pbr_mv.py's own 4096 default. Change to 4096 and keep --pipeline-type 1536_cascade --max-tokens 131072 --no-low-vram --decimation 1000000. Then bake normal/AO/cavity in headless Blender from the 4.9M-vert *_HIGHPOLY.ply the runner now writes (run_trellis2_native_pbr_mv.py:311-326) onto the retopo low-poly. That is the 'missing normal map' — it is a bake step, not a missing model.",
"STEP 11 — BODY STRUCTURE: MEASURE THE RATIO, DO NOT PROMPT IT. Head-to-height is currently only a sentence in stage_b_body_base.py:104-114. likeness_lens.py already ships the instrument: figure_profile (per-row silhouette width), head_subband_edges (crown/chin rows), camera_from_known_azimuth, turning_distance (contour shape). Run it on (a) each approved panel, (b) each generated body plate, (c) each rendered mesh at three azimuths, and gate |ratio_mesh − ratio_canon| against a band derived from the 7-age x 3-view corpus (n=21, clears the n>=8 floor). Add shoulder-width/head-width, hand-length/height (0.10-0.11 at all ages) and elbow-at-waist as sibling legs.",
"STEP 12 — MAKE THE AGE LADDER A DEFORMATION, NOT SEVEN INDEPENDENT GENERATIONS. Build ONE canonical mesh (age 14 or 18) at full quality, then derive the other six by a measured proportional deformation in Blender driven by the Step 11 ratio canon (cranial vault, jaw projection, limb-to-torso, shoulder width), and re-verify each derived age with ArcFace against its own panel. Seven independent generations is what makes the ages drift into seven different children; a deformation cannot drift because it shares one topology, one UV set and one skin weight set — which is also what makes the whole ladder riggable with a single UniRig stage-3 skin pass."
],
"features_or_models_needed": [
"ArcFace R100 ONNX — onnxmodelzoo/arcfaceresnet100-8 on HuggingFace. Apache-2.0 (verified this session), ~250 MB, FREE download, no permission needed. THE identity metric. Kills likeness_lens.py's false 'InsightFace is non-commercial' refusal.",
"MediaPipe FaceMesh — ALREADY HAVE IT LOCALLY, two ways: imported by harness/asset_factory/body_plate_head_crop.py, and native at D:/comfyui/ComfyUI/comfy_extras/nodes_mediapipe.py. Supplies the 5-point align for ArcFace and the yaw measurement that arms L11.",
"ComfyUI native LoRA trainer — ALREADY HAVE IT: comfy_extras/nodes_train.py:955 (TrainLoraNode), nodes_dataset.py:213 (LoadImageTextDataSetFromFolder), :1364 (SaveLoRA), :1408 (LossGraphNode). Zero custom nodes installed, so nothing to add. FREE, local, and never once used.",
"Qwen-Image-Edit-2511 bf16 — ALREADY HAVE IT. Its 2511 release folds multi-angle/novel-view and improved character consistency into the BASE model per the official card, so it is a second view generator we already own and have never pointed at this problem.",
"Photoreal SDXL base for MV-Adapter — e.g. stablediffusionapi/real-dream-sdxl, the recipe MV-Adapter's own README:194 ships verbatim. ~7 GB, FREE. Needed because mva_views.py:240 hardcodes stock SDXL, whose prior IS the plastic-doll skin.",
"MV-Adapter IP-Adapter path — ALREADY HAVE IT, unused: pipeline_mvadapter_i2mv_sdxl.py:600-608 accepts ip_adapter_image / ip_adapter_image_embeds. If we want the SDXL IP-Adapter weight itself: h94/IP-Adapter (Apache-2.0, ~700 MB, FREE).",
"onnxruntime-gpu in the pinned face312 venv — FREE pip install. Only new dependency in the entire plan.",
"Blender 5.2 headless — ALREADY HAVE IT at D:/assetgen/tools/blender-5.2.0-windows-x64/. Does three jobs here: normal/AO/cavity bake off the saved *_HIGHPOLY.ply, the head-to-body join at the neck ring, and the age-ladder proportional deformation. GPL tool, output unencumbered.",
"TRELLIS.2 saved high-poly — ALREADY HAVE IT as of 2026-08-12: run_trellis2_native_pbr_mv.py:311-326 writes <out>_HIGHPOLY.ply at ~4.9M verts. This is the normal-map source the director declared missing.",
"NOT NEEDED: Meshy at $100/month. Nothing in this plan requires it, and Meshy would not solve the two things that actually bind — reference framing and an armed identity metric — because both are OUR graph, not the generator."
],
"blockers_that_are_real": [
"nvdiffrast licence taint on the EXPORT path. o_voxel.postprocess.to_glb rasterises the UV layout with nvdiffrast, which is Nvidia Source Code License 1-Way Commercial ('research or evaluation purposes only'). Every GLB we have made is correctly quarantined EVALUATION_ONLY and CANNOT SHIP. This is the one genuine blocker in the lane and none of the identity work above touches it. Fix is a step, not a purchase: replace the to_glb UV rasterisation with a Blender + xatlas unwrap/bake (Blender is GPL, its output is unencumbered — the exact distinction the lane's own licence law already draws for bl_mv_stage.py).",
"Information ceiling on the SOURCE, and it is a real ceiling but a small one: the approved panels are 249x682 px with ~290px head and ~55px interocular. Nothing recovers detail that was never captured. This bounds MICRO-detail (pore-level skin), NOT identity or proportion — 55px interocular is above ArcFace's usable floor, and an identity LoRA trained on filtered bootstraps plus a normal bake from the 4.9M high-poly covers the micro-detail. If Josh wants the ceiling raised, the cheap fix is to re-render the approved ladder at 1344+ per panel from the same seed and re-approve, not to buy a subscription."
],
"replicable_across_classes": "[\"SHARED (write once, never per-character): the ArcFace measurement harness and its thresholds; body_plate_head_crop.py wired into the graph; the 3-reference Qwen conditioning pattern; the ImageScaleToTotalPixels resolution authority; the ComfyUI TrainLoraNode graph; the mva_views.py flag surface (--base-model/--lora-model/--ip-adapter-image/--mv-scale/--res 768); the view-set consistency gate; the head/body split-and-join in Blender; the high-poly normal bake; the likeness_lens silhouette instruments (figure_profile, head_subband_edges, turning_distance, camera_from_known_azimuth); the UniRig stage-3 skin pass against the UE5 mannequin. PER-SUBJECT (data only, no new code): N approved panels, one identity LoRA, one proportion-canon row, one reference ArcFace embedding.\", \"HERO/NPC: identical pipeline, smaller dataset. A hero gets 7 age rungs and ~200 filtered bootstraps; a named NPC gets 1-2 rungs and ~40; a crowd NPC gets no LoRA at all and rides the shared photoreal SDXL base plus a single reference — the ArcFace gate simply loosens from cos>=0.60 to cos>=0.50 by tier. Because the age ladder is a DEFORMATION of one canonical mesh (Step 12), an NPC family shares topology, UVs and skin weights with the hero, so one UniRig skin pass and 47 retargeted clips cover the whole cast.\", \"CREATURES AND BOSSES (no human face, so ArcFace does not apply): the metric degrades cleanly to legs the lens already has — L09/L10 silhouette scoring, turning_function contour distance, region_appearance_emd — PLUS a model-agnostic addition that costs nothing because the weights are already loaded: DINOv3 patch-token cosine between the approved concept plate and the rendered mesh view, using the exact same facebook/dinov3-vitl16 encoder TRELLIS conditions on. That gives one 'is this the approved design' number that works on a boy, a dragon or a siege engine, and it is measured in the same feature space the generator itself sees. Everything else — framing discipline, view-consistency gate, head/detail-region split, high-poly normal bake, proportion canon — is species-agnostic and transfers unchanged.\"]",
"evidence_note": "All verified this session by reading vendor source and measuring files on disk; nothing re-derived from the capability audit, which was read first as instructed.\n\nMEASURED ON DISK: approved panels = 249x682 px, sheet APPROVED_FACE_LADDER_JOSH.png = 1774x887; A14 head ~290px crown-to-chin, interocular ~55px (read the panel image directly). A14_face_7bsharp.png = 996x2728. A14_body_bald_front.png = 672x1568 (1.05 MP). A14_face_bald_square.png = 1344x1344. All MV views = 1024x1024. Only face_bald has a squared cond on disk — the square_pad fix reached one of three subjects.\n\nVENDOR CODE: D:/comfyui/ComfyUI/comfy_extras/nodes_qwen.py:80-88 (VL budget 384*384, aspect-preserved) and :89-95 (reference_latents at 1024*1024); :36-44 (non-Plus VL at 1024*1024). D:/assetgen/models/TRELLIS2/pipeline.json (image_cond_model = DinoV3FeatureExtractor / facebook/dinov3-vitl16-pretrain-lvd1689m; tex guidance_strength 1.0). D:/assetgen/TRELLIS2_code/trellis2/pipelines/trellis2_image_to_3d.py:137-139 (1024 clamp), :152-157 (square bbox crop), :174 + :539-540 (get_cond at 512 and 1024), :73-76 (PBR layout, no normal). /root/mv3d/MV-Adapter/mvadapter/pipelines/pipeline_mvadapter_i2mv_sdxl.py:600-608 (ip_adapter_image supported), :610-650 (reference is a UNet hidden-state cache at ref_scale, not CLIP). /root/mv3d/MV-Adapter/scripts/inference_i2mv_sdxl.py:254 (height=768), :186-194 (base/unet/lora/lora_scale flags), README:11 (\"with 768 Resolution using SDXL\"), README:194 (photoreal recipe). D:/comfyui/ComfyUI/comfy_extras/nodes_train.py:955/1364/1408 and nodes_dataset.py:213 (native trainer present; D:/comfyui/ComfyUI/custom_nodes is EMPTY).\n\nOUR CODE: harness/asset_factory/mva_views.py:240 (stock SDXL hardcoded), :270-291 (0.9 letterbox on grey), :344-361 (no ip_adapter_image, no mv_scale, text=\"high quality\"). harness/asset_factory/char_pipeline_from_ladder.py:250 (--res 1024), :275 (--texture-size 2048 overriding the runner's 4096). harness/asset_factory/stage_b_body_base.py:137 (FluxKontextImageScale as sole resolution authority), :51+:129 (size=1328 dead), :104-114 (head-to-height prompted, never measured). harness/asset_factory/likeness_lens.py:1553-1565 (L11 REFUSED, weights never fetched), :1534-1538 (the false ArcFace licence claim). build/3d/bakeoff/native_tex/trellis2_multiview.py:154-180 (no cross-view attention). build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py:311-326 (high-poly now saved). harness/asset_factory/body_plate_head_crop.py exists with a MediaPipe FaceMesh face-present control and is not referenced by the ladder pipeline.\n\nWEB-CHECKED 2026-08-12: onnxmodelzoo/arcfaceresnet100-8 is Apache-2.0 (https://huggingface.co/onnxmodelzoo/arcfaceresnet100-8). Qwen-Image-Edit-2511 model card states improved character consistency and that \"generating new viewpoints can now be done directly with the base model\" (https://huggingface.co/Qwen/Qwen-Image-Edit-2511). Current identity-adapter landscape confirmed as PuLID-FLUX / InstantID / InstantCharacter / DreamO / UNO, all ComfyUI-reachable; FLUX.2 takes up to 10 reference images with strong identity preservation (https://www.bentoml.com/blog/a-guide-to-open-source-image-generation-models). ArcFace cosine on L2-normalised 512-d embeddings is the standard verification metric with the threshold tuned to a target FAR (https://learnopencv.com/face-recognition-with-arcface/).\n\nREAD-ONLY: nothing was created, modified, moved or deleted."
},
{
"lane": "REPLICABLE ACROSS ALL ASSET CLASSES - characters, NPCs, creatures, bosses, props (system design, not hero one-off)",
"my_error": "Four things wrong, and from this lane they are all the same mistake: the director graded the ONE stage it was looking at and called the whole system ceilinged.\n\n1. \"No normal map, so pores have nowhere to live\" is FALSE AT THE PIPELINE LEVEL, not just arguable. MV-Adapter's own TexturePipeline (/root/mv3d/MV-Adapter/mvadapter/pipelines/pipeline_texture.py __call__ :136-172) takes base_color_path, orm_path AND normal_path + normal_strength and emits a PBR model at uv_size=4096. It is installed. It has never been called. TRELLIS.2's Trellis2TexturingPipeline (run_trellis2_texture_refine.py) has ZERO runs by the audit's own count. The director declared a structural ceiling while two installed surface-authoring pipelines sit at zero executions. That is not a ceiling, that is two stages we never ran.\n\n2. It missed that the character pipeline HAS NO SECOND HALF. char_pipeline_from_ladder.py ends at a .glb in build/3d/bakeoff/native_tex/QUARANTINE_EVAL_ONLY. There is no rig stage, no retopo stage, no export stage, no import stage, no staging manifest, no licence record. Meanwhile the PROP lane (D:/assetgen/bin: objref -> gen3d -> finish_mesh2 -> pbr_batch, driven by standing_batch.py) already has a battery-as-filter, a retry queue with named defect classes, a contact sheet, a staging manifest and emit_import_manifest_standing.py writing sha256 + ue_target_path into the engine. The highest-bar lane is the one missing the machinery the lowest-bar lane already shipped. Nobody merged them.\n\n3. It missed the single largest free capability on the box: UE 5.8's MetaHuman \"From Custom Mesh\". As of 5.8 it conforms BODIES as well as heads, accepts arbitrary topology, is explicitly documented for AI-generated meshes, preserves the source proportions, and is fully scriptable headless via MetaHumanCharacterEditorSubsystem.conform_to_target_meshes / build_meta_human / export_dna / export_dcc. We already ship on 5.8 with Substrate on, and we already drive UE headless (Tools/anim/ue_run.py:36-45). That one feature is retopo + UV + skin weights + face rig + LODs for every human row - 167 characters, 64 antagonists, the humanoid share of 284 bosses - and instead this project hand-built an 88-bone rig that landed \"DELIVERED WITH A NAMED BLOCKER\", 36 of 88 bones weighted, arm_pose_gap 32.93 deg (build/3d/best_a14/rig/RIG_RECORD.json).\n\n4. It repeated \"only 13 of 187 have a proven rig solver\" as a RIGGING gap. Read the census's own field: the solver it names is Tools/anim/limbless_solver.py - a MOTION solver (travelling wave, coil/uncoil, three-phase strike). Skeleton + skinning for arbitrary body plans is UniRig, installed at D:/assetgen/UniRig with 10.69 GB of weights and NEVER RUN, and now superseded by SkinTokens (MIT, 14 GB VRAM, one autoregressive pass, +98-133% skinning accuracy, --use_skeleton to reuse a template hierarchy). Conflating rig with motion turned an animation-authoring backlog into a fake tooling wall.\n\nAnd on gates the director understated its own case. It is not \"no gate on generated views\". It is NO GATE ON ANY STAGE: char_pipeline_from_ladder.py:280-286, step_done() returns all(have(o)) - file exists. A flat plane is a file. That one function is why corrupted views reached geometry twice, and grade_body_bases.py, topology_bar.py, normals_tooth.py, texel_density.py, contact_sheet.py and multiview_registration.py were all already written and none of them is wired to a step.\n\nOn the Meshy threat, honestly: Meshy replaces ONE stage of about ten. It has no registry query, no care-law filter, no gate battery, no archetype rig routing, no UE path contract and no 7-rung identity ladder. Buying it leaves nine stages still unbuilt. It is not the thing that is broken here.",
"correct_steps": [
"1. GATE FIRST, before any regeneration. In harness/asset_factory/char_pipeline_from_ladder.py replace step_done() (:280-286). Today: `return all(have(o) for o in outs)`. Required: a step is DONE iff every output exists AND a sibling `<out>.gate.json` carries verdict==PASS written by that step's declared gate script. Add a `gate` key to every dict in build_steps() (:206-277). Input: existing step dicts. Output: a ledger in which a flat plane is PENDING, not DONE. This is ~30 lines and it is the highest-value edit in the whole lane.",
"2. Build the missing G_VIEWS gate (harness/asset_factory/gate_views.py), run between C_views and D0_matte. Four numbers per view set, all cheap: (a) per-view alpha/subject coverage fraction inside a declared band (matte_plates.py already refuses implausible coverage - promote that refusal to a recorded verdict); (b) cross-view subject-height spread - multiview_registration.py ALREADY measures subject height in px per view and publishes the spread, so import it rather than rewrite it; (c) saturated/garbage pixel fraction per view (the red/black regions named in square_pad()'s docstring); (d) background-colour agreement across the 6 views. FAIL -> re-run C_views at seed+1, never pass through. Also emit contact_sheet.py over the 6 views for the visual-tier defects that have no numeric gate (GROUND_PLANE_INCLUDED, by that file's own measured finding).",
"3. Wire the gates that already exist to the stages they were written for, one line each: A_upres -> head_resolution_census.py (face-pixel floor); B_base -> grade_body_bases.py (head ratio vs the ladder trend, baldness scalp-vs-cheek, framing/crop) - this file was written for exactly this defect and has never been a gate; D0_matte -> matte_plates.py's own refusal, recorded; D_geom -> topology_bar.py (quad fraction, edge length vs feature FWHM) + normals_tooth.py (flat_triangle_fraction, vertex_inflation) + a one-number PLANARITY check (min bbox extent / max extent) which alone would have caught both flat-plane meshes; E_texture -> texel_density.py against the 2048 px/m hero band + delta_e_lens.py; F_rig -> bl_rom_render.py ROM battery + verts_weighted (weighted vertices, never vertex-group count); G_export -> normals_tooth flat band + FBX-twin existence; H_import -> sha256 + ue_target_path resolution.",
"4. Generalise the runner into a CLASS-DRIVEN stage machine without rewriting it. Lift AGES / SUBJECTS / WEIGHTS / BODY_PROMPT / PANELS / QUARANTINE (char_pipeline_from_ladder.py:49-80) into build/ops/asset_classes/<class>.json. build_steps(ages, subjects, seed) becomes build_steps(cfg, row, seed) and keeps its exact (key, done-check, gate, cmd) shape, so resume-by-set-difference and PIPELINE_STATE.json survive unchanged. Class key = the registry's EXISTING closed enum mesh_pipeline_type {metahuman, image_to_3d, special_case_by_case} plus an archetype field, so nothing new has to be invented or re-derived.",
"5. Concrete shared stage list, identical for every class (the machine): S0 QUERY (worklist row from registry) -> S1 COND (conditioning plate assembly: upres, square-pad, alpha-bbox crop, matte) -> S2 VIEWS (MV-Adapter i2mv) -> S3 GEOM (TRELLIS.2 1536_cascade) -> S4 SURFACE (Trellis2TexturingPipeline and/or MV-Adapter TexturePipeline for the normal/ORM channel) -> S5 RIG -> S6 MOTION -> S7 BAKE/EXPORT (Blender headless, GLB + FBX twin) -> S8 LAND (staging manifest + licence + generation record) -> S9 IMPORT (import manifest with sha256 + ue_target_path, UnrealEditor-Cmd headless). Each stage carries exactly one gate. Nine stages; we currently execute four of them for characters.",
"6. Concrete PER-CLASS parameter block in the class config (everything else is shared): source_registry + row_query + id_prefix; conditioning_recipe (7-rung age ladder | single approved plate | 3-view turnaround sheet); subjects[] (body_bald/face_bald/face_haired for the protagonist; a single subject for a creature or prop); generator params (pipeline_type, texture_size, decimation_target, tex sampler steps/guidance/rescale); rig_route (metahuman_conform | skintokens | unirig_skin_only | none); archetype (drives S6 motion solver); identity_requirement + which lens enforces it; lod_budget; care_policy; ue_target_path pattern + SM_/SK_ prefix; retry_policy (seeds, max attempts).",
"7. CHARACTER rig/retopo/LOD stage = MetaHuman From Custom Mesh, driven headless. Exact call chain inside UnrealEditor-Cmd -run=pythonscript (the mechanism Tools/anim/ue_run.py:36-45 already uses): sub = unreal.get_editor_subsystem(unreal.MetaHumanCharacterEditorSubsystem); sub.try_add_object_to_edit(character); sub.conform_to_target_meshes(character, target_mesh_key, params) with the stage-B BALD BARE A-pose body as the imported StaticMesh; commit_body_state / commit_face_state; sub.build_meta_human(character, params); then MetaHumanCharacterExportBlueprintLibrary.export_dna() / export_dcc() / export_geometry() / export_materials(). Input: our A-pose bald GLB/FBX. Output: MetaHuman-topology fully rigged body+face, LODs, MetaHuman skin shader, source proportions preserved. Feed it BALD and BARE - the docs state clothed meshes fit the garment silhouette, not the anatomy, and stage B already produces exactly the right input.",
"8. TEST THE ELVEN EAR THROUGH CONFORM BEFORE COMMITTING THE CHARACTER LANE. One run, one asset: conform A14, export, measure the ear tip against the source mesh with the existing silhouette comparator (likeness_lens L09 EAR_LINE sub-band, which already exists and already declares has_ears). If the MetaHuman face PCA space flattens the point, fall back to: conform the BODY only via MetaHuman, keep the TRELLIS head as a graft (build/3d/finish_a14/graft_a14.py is the precedent), or re-add the tip post-conform via translate_face_landmarks + a displacement pass. Decide this on a measurement, not an assumption, before 167 rows are queued behind it.",
"9. CREATURE/BOSS rig stage = SkinTokens primary, UniRig fallback, both category-agnostic and both single-mesh-in / rigged-mesh-out. Exact: `python demo.py --input <CR_xxxx.glb> --output results/CR_xxxx.glb --use_transfer` (add --use_skeleton once an archetype template hierarchy exists so every row in an archetype shares bone names). 14 GB VRAM, fits the 5090. Batch by pointing --input/--output at directories - the 187-row roster becomes one queue. UniRig stays as the fallback and for the skin-only path (generate_skin.sh against an existing FBX hierarchy) since its weights are already local.",
"10. SPLIT RIG FROM MOTION IN THE LEDGER - this is the fix for the fake 13/187 number. S5 RIG is class-agnostic and covers all 187 creatures + 284 bosses + 22 familiars in one queue. S6 MOTION is per-ARCHETYPE and is the real backlog, sized by the census: LIMBLESS 13 PROVEN; SPRAWLING_QUADRUPED 4 next (SK_CR_0001 already imported as a BRANCHING_TREE - one spine + four 3-bone limb chains); QUADRUPED 62 shares that solver with limb-splay and gait as PARAMETERS; WINGED 32; AQUATIC 20 (closest relative is the limbless wave solver); ARBOREAL_PRIMATE 11 (the only one needing real IK contact); MULTI_LEGGED 3. Six solvers cover 145 of 187 rows.",
"11. Merge the two half-pipelines instead of building a third. Point the character class config at the SAME upstream and downstream the prop lane already owns: emit_standing_worklist.py for row query + subject composition + the four exclusion classes (CARE_GATED, REFERENCE_BUILT_ONLY, NO_SUBJECT, ROUTE_NOT_PROP); standing_batch.py's battery-as-filter and retry-queue-with-defect-class posture; emit_import_manifest_standing.py for the engine handoff. Keep char_pipeline's generator stages and its resumable ledger. Net: one worklist emitter, one gate battery, one landing contract, N class configs.",
"12. Make the GENERATOR a per-class parameter and settle it with a measured bake-off, not history. Props and creatures currently go through TripoSG (D:/assetgen/bin/gen3d.py) while TRELLIS.2-4B is used only by the character lane - that split is an accident of which lane was built when, not a decision. Run the same 5 subjects through both at the audit's corrected settings and let the gate battery pick per class.",
"13. Enforce the UE landing contract in the class config, because it has already been got wrong once. HumanityGeneratedMesh.cpp resolves statics at /Game/Meshes/<AssetClass>/SM_<id> and skeletals at /Game/Creatures/<id>/SK_<id>; WQ_0077 imported the rebuilt 38-bone serpent to /Game/Meshes/Creatures/SK_CR_0002 and the runtime could not see it. ue_target_path goes in the class config, the H_IMPORT gate asserts it against the C++ convention, and every class must declare an FBX twin because there is no GLB importer targeting Unreal anywhere in the game repo.",
"14. Register the new gates in the machinery that already exists rather than inventing a parallel one: harness/gates_config.json + harness/gatelib.py + harness/run_gates.py, which already give per-gate scorecards, PASS-on-empty declared-not-armed reporting, must-fire/must-not-fire fixtures and a --self-test convention. Every asset gate ships with a MUST-FIRE fixture built from a known-bad artifact we already own (a flat-plane GLB, the haired 'bald' A7 mesh, MN_0006's plinth) - a gate that has never failed a known-bad subject is not a gate."
],
"features_or_models_needed": [
"MetaHuman From Custom Mesh + MetaHuman Creator Python API (conform_to_target_meshes, build_meta_human, export_dna/export_dcc/export_geometry/export_materials, MetaHumanCharacterEditorSubsystem) - ALREADY HAVE, zero cost, zero download: it is in the UE 5.8 install at C:/dev/Humanity/Humanity. 5.8 is the release that added BODY conforming. Docs: https://dev.epicgames.com/documentation/metahuman/metahuman-creator-from-custom-mesh-tool-in-unreal-engine and https://dev.epicgames.com/documentation/metahuman/metahuman-creator-python-scripting-in-unreal-engine",
"SkinTokens (VAST-AI-Research) - FREE local download, MIT licence, 14 GB VRAM inference, .glb in / rigged .glb out, --use_skeleton and --use_transfer, batch by directory. Successor to UniRig from the same lab: +98-133% skinning accuracy, +17-22% bone prediction. https://github.com/VAST-AI-Research/SkinTokens and https://huggingface.co/VAST-AI/SkinTokens (weights: articulation_xl_quantization_256_token_4, skin_vae_2_10_32768)",
"UniRig - ALREADY HAVE at D:/assetgen/UniRig + D:/assetgen/models/UniRig (10.69 GB), NEVER RUN. Keep as fallback and for the skin-only path (launch/inference/generate_skin.sh against an existing FBX hierarchy, which preserves UE5 Mannequin bone names). https://github.com/VAST-AI-Research/UniRig",
"MV-Adapter TexturePipeline - ALREADY HAVE in WSL at /root/mv3d/MV-Adapter/mvadapter/pipelines/pipeline_texture.py, never called. This is the installed source of the NORMAL MAP the director said we lack: __call__ :136-172 exposes base_color_path / orm_path / normal_path / normal_strength at uv_size=4096 and returns TexturePipelineOutput.pbr_model_save_path. Zero cost.",
"TRELLIS.2 Trellis2TexturingPipeline via run_trellis2_texture_refine.py - ALREADY HAVE, ZERO RUNS. Mesh-conditioned re-texture that PRESERVES existing UVs (trellis2_texturing.py:299-302), so it is the only installed way to re-texture a MetaHuman-conformed or UniRig-retopo'd mesh without regenerating the character. Zero cost.",
"Photoreal SDXL base checkpoint + a skin/pore LoRA for MV-Adapter - FREE HF download. MV-Adapter is base-agnostic and its own README:194 ships the recipe (--base_model stablediffusionapi/real-dream-sdxl --scheduler ddpm). mva_views.py:240 currently hardcodes stock stabilityai/stable-diffusion-xl-base-1.0, whose prior IS the plastic-doll skin. ~7 GB.",
"Marigold IID (prs-eth/marigold-iid-lighting-v1-1 and prs-eth/marigold-iid-appearance-v1-1) - FREE HF download, for the de-light + roughness stage that our SH-2 delight.py declares it cannot do. Runs at ensemble_size>=5 with output_uncertainty=True, which gives the gate a per-pixel confidence to threshold on.",
"BiRefNet (ZhengPeng7/BiRefNet) - ALREADY HAVE / auto-downloaded by matte_plates.py. Not gated (unlike TRELLIS.2's briaai/RMBG-2.0), and it is also the vendor matte MV-Adapter expects via --remove_bg, which mva_views.py never uses. Zero cost.",
"The existing gate instruments - ALREADY HAVE, all unwired: grade_body_bases.py, multiview_registration.py, topology_bar.py, normals_tooth.py, texel_density.py, texel_coverage_tooth.py, delta_e_lens.py, likeness_lens.py, thousand_point_eval.py, contact_sheet.py, visual_judge.py, iteration_engine.py, anatomy_bar.py, harness/gatelib.py + run_gates.py. Zero cost, zero download - the entire quality-gate layer is already written and simply not called.",
"Blender 5.2 headless (D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe) - ALREADY HAVE, already used headless. It is the class-agnostic S7 BAKE/EXPORT stage and the only place the FBX twin gets produced (there is no GLB route into Unreal at all)."
],
"blockers_that_are_real": [
"42 creature rows are NEEDS_CANON_BODY_PLAN (22.5% of the roster). The census refuses to guess what a folklore being's body IS, and it is right to: no model resolves a design decision. These rows are blocked on Josh / the creature canon, not on tooling, and must sit on a canon worklist rather than counting as pipeline failures. 7 more are PROVISIONAL (name proposes a plan: 5 naga, 1 thunderbird, 1 great serpent) and need a one-line ruling each, not a run.",
"MetaHuman From Custom Mesh is HUMANOID-ONLY by Epic's own documentation - non-humanoid or heavily stylised meshes show volume loss and pinching during animation. So the single best rig/retopo/LOD tool we own cannot serve the 187 creatures or the non-humanoid bosses. That is a genuine permanent CLASS FORK in the system (metahuman path vs skintokens path), and it is why rig_route has to be a per-class parameter rather than one shared implementation.",
"The pointed elven ear through MetaHuman's face conform is UNVERIFIED and is the one thing that could invalidate the character path. The conform solves into a PCA space learned over human scans; an ear tip may be outside it. This is testable in a single run and MUST be tested before 167 rows queue behind it - if it flattens, the fallback is body-only conform plus a head graft, which costs a stage.",
"Care-gated and REFERENCE_BUILT_ONLY rows can never be raw-generated at all - that is standing law (docs/art_bibles/flores_island.md s3/s17.1: real cultural architecture and protected sacred markers are reference-built). These rows are a permanent alternate route, not a stage that can be made to work. emit_standing_worklist.py already filters them; the character class config must apply the same filter, which it currently does not."
],
"replicable_across_classes": "\"SHARED - one implementation, every class, no forks: the worklist emitter (registry row query + subject composition + the four exclusion classes, already built in emit_standing_worklist.py); conditioning prep (SeedVR2 upres, square-pad, vendor alpha-bbox crop, BiRefNet matte); multi-view generation (MV-Adapter i2mv, same base/LoRA/mv_scale/control_conditioning_factor); geometry (TRELLIS.2 1536_cascade at the corrected max-tokens); surface (Trellis2TexturingPipeline + MV-Adapter TexturePipeline for the ORM/normal channel); bake and export (Blender headless, GLB + mandatory FBX twin); the gate machinery itself (gatelib scorecard shape, must-fire fixtures, run_gates aggregation); the resumable step ledger (stable key = row_id/stage/subject, resume = set difference plus gate verdict); the staging manifest + licence + generation record; the import manifest with sha256 + ue_target_path; and the headless UE import. That is 8 of the 10 stages shared verbatim.\\n\\nPER-CLASS - config only, never a second runner: (a) SOURCE - which registry and which row query, plus the id prefix (CHAR_/CR_/BE_/FAM_/AN_/prop). (b) CONDITIONING RECIPE - the protagonist runs the 7-rung age ladder x 3 subjects (body_bald, face_bald, face_haired); an NPC runs one approved plate; a creature runs its bestiary sheet plate; a prop runs a 3-view turnaround. Same code, different plate count. (c) RIG ROUTE - metahuman_conform for humans (167 characters, 64 antagonists, humanoid bosses), skintokens/unirig for creatures and non-humanoid bosses (187 + 22 familiars + the rest of 284), none for props. This is the ONE genuine hard fork and it is a single enum in the config. (d) MOTION SOLVER - keyed on the census archetype, and this is a different axis from rigging: 8 archetypes, 1 proven (LIMBLESS/13), 6 buildable (145 rows), 1 blocked on canon (42 rows). (e) IDENTITY REQUIREMENT and the lens that enforces it - face-identity embeddings only mean something for the ladder protagonist; a creature is scored on anatomy_bar plus bestiary-sheet MARK PRESENCE (the four marks bl_creature_elevation.py builds, one of which is a combat telegraph and therefore a gameplay requirement, not a look one); a prop is scored on delta-E and silhouette. (f) LOD BUDGET and texel band - hero 2048 px/m vs gameplay 1024 vs background 512, straight off the craft doc's existing table. (g) CARE POLICY - already a filter, must now also apply to characters. (h) UE TARGET PATH and SM_/SK_ prefix. (i) RETRY POLICY.\\n\\nCREATURE PATH vs CHARACTER PATH, in practice: identical from S0 through S4, then diverges at S5 and rejoins at S7. The character branch hands its bald A-pose body to MetaHuman conform and gets back retopo + UVs + skin weights + face rig + LODs + the MetaHuman skin shader in one call - which deletes the entire hand-built retopo/wrap/rig sub-project that landed with a named blocker. The creature branch hands its GLB to SkinTokens and gets back a skeleton and dense weights for any body plan, then picks up an archetype motion solver at S6. Bosses split by body plan across those same two branches - a humanoid boss is a character with a bigger LOD budget and a hitzone_parts requirement, not a new pipeline. Props take the same S0-S4 and S7-S9 and simply skip S5 and S6.\\n\\nThe honest summary: about 80% of the work is already class-agnostic and already written; what is missing is one config layer, one gate contract on step_done(), and two rig calls - one of which ships free inside the engine we already run on.\"",
"evidence_note": "Read directly, not recalled. Repo files: harness/asset_factory/char_pipeline_from_ladder.py in full (step_done at :280-286 is the file-exists done-check; build_steps :206-277; square_pad :166-203 documents the flat-plane failure and its cause); harness/asset_factory/mva_views.py (argparse block :432-457, --res default 768 while char_pipeline:250 passes 1024; record written at :405, no gate); matte_plates.py, grade_body_bases.py, multiview_registration.py, contact_sheet.py, topology_bar.py, normals_tooth.py, texel_density.py, likeness_lens.py, thousand_point_eval.py, visual_judge.py, iteration_engine.py, weights.py, standing_batch.py, emit_standing_worklist.py, emit_import_manifest_standing.py (headers/docstrings); harness/gatelib.py, harness/run_gates.py, harness/gates_config.json; harness/apply_mesh_routing.py (the 724-row / 5-registry count and the closed mesh_pipeline_type enum). Registries parsed live: T0_Creature_Roster [DRAFT v0.1]/Sheet1.csv = 187 rows, all mesh_pipeline_type=image_to_3d, 181 pending / 6 complete, morphology mammal 82 / folklore 47 / bird 22 / reptile 15 / fish 9 / insect 6; T0_Boss_Encounter_Registry 284 rows; T0_Familiar_Registry 22 rows. Game repo: C:/dev/Humanity/Humanity/QA/bench/creature_archetype_census.json (8 archetypes, coverage.rows_served_by_a_proven_solver = 13/187 = 0.0695, solver field = Tools/anim/limbless_solver.py which is a MOTION solver; 42 NEEDS_CANON_BODY_PLAN; 7 provisional) and build/ops/GAME_INTEGRATION_SURVEY_2026-08-12.json (no generic GLB->rigged-character function, bl_rig.py has no biped mode and self-declares PREVIZ, RIG_RECORD.json verdict 'DELIVERED WITH A NAMED BLOCKER' with 36/88 bones weighted and arm_pose_gap_deg 32.93, WQ_0077 landed SK_CR_0002 at the wrong UE path, no GLB importer targets Unreal); HumanityGeneratedMesh.cpp read directly for the /Game/Meshes/<class>/SM_<id> and /Game/Creatures/<id>/SK_<id> conventions. Vendor install checked on disk: D:/assetgen/UniRig/launch/inference/{extract,generate_skeleton,generate_skin,merge}.sh and D:/assetgen/models/UniRig/{skeleton,skin,data}; D:/assetgen/bin/gen3d.py confirms the prop lane runs TripoSG, not TRELLIS.2. The 84-feature audit at build/ops/PHOTOREAL_CAPABILITY_DIFF_2026-08-12.json was read, not re-derived (all six lanes, features_UNUSED + misconfigurations + newer_alternatives + top_recommendation). WEB-CHECKED 2026-08-12: MetaHuman 5.8 From Custom Mesh (body conforming added in 5.8, arbitrary topology, AI-generated meshes named explicitly, humanoid-only limitation, no LOD/texture output stated in that doc) and the MetaHuman Creator Python API (conform_to_target_meshes / build_meta_human / export_dna / export_dcc, headless-capable); SkinTokens (MIT, 14 GB VRAM, --use_skeleton, --use_transfer, HF VAST-AI/SkinTokens) confirmed as UniRig's successor. NOT VERIFIED, and I am naming it rather than assuming it: whether the pointed elven ear survives MetaHuman's face conform, and whether MetaHuman's Python conform path is fully driveable in -run=pythonscript without an interactive editor session - both are one test run each and both are listed as steps."
},
{
"lane": "MESHY AND COMMERCIAL ALTERNATIVES — honest buy/no-buy on a $100/mo Meshy subscription vs the local stack",
"my_error": "Three errors, and the first one is embarrassing because the fix landed in our own file today.\n\n(1) \"TRELLIS emits no normal map, so pores have nowhere to live\" is a MISSING STEP dressed as a structural ceiling. TRELLIS.2 was computing a 4,915,787-vert / 9,853,672-face mesh and the runner was handing it straight to to_glb, which decimated it to ~954k and BINNED the high-poly — 38 assets deep. The high-poly IS the pore source; baking high-to-low normal/AO/cavity is standard character work and has been since roughly 2005. The director declared a ceiling while deleting the solution on every run. run_trellis2_native_pbr_mv.py:299-310 now says this in its own comment. A channel that Blender bakes for free, that Marigold/StableNormal/SKAP synthesise for free, and that a $20/mo Meshy plan emits as a checkbox, is not a structural limit of anything.\n\n(2) The director pointed at Meshy as if it addressed the identity problem. It does not, and this is the central category error of the whole debate. Meshy, Rodin, Tripo are all image-to-mesh: they consume the plate you hand them. If the Qwen vision branch starved the face to ~90px, Meshy returns that same starved face, now in 3D and harder to fix. Identity is decided upstream at the 2D plate and multiview stage — nodes_qwen.py's 384x384 VL clamp and the 1.05MP plate ceiling — all of which are local, free, and ours. Buying a mesh generator to fix an identity problem is buying the wrong layer of the stack.\n\n(3) The audit the director commissioned says Trellis2TexturingPipeline has NEVER ONCE EXECUTED, and UniRig (10.69GB, installed) has never been run. You cannot report a ceiling on photorealism while an entire texturing pipeline and the whole rigging stage have never been switched on. Under standing law that is our error, not a limit.\n\nAnd on the purchase itself: the \"$100/month Meshy\" framing is wrong on Meshy's own pricing page. Every capability being bought — API access, normal maps, quad topology, 4K/8K textures, free rigging, full commercial ownership — is on Meshy Pro at $20/mo. Ultra at $100 buys credit VOLUME (10,000 vs 1,000 credits, i.e. 500 vs 50 full models a month) and queue priority. Nothing else. Josh is being quoted $100 for a $20 feature set. Separately, if the actual goal is photoreal CHARACTERS, Meshy is not even the right vendor — Rodin/Hyper3D is.",
"correct_steps": [
"SPEND NOTHING THIS WEEK. Open free tiers on all three real contenders and settle this with images instead of argument: Tripo gives 2,000 free credits on signup (developers.tripo3d.ai, ~40-50 credits/task = ~40 free assets), Rodin/Hyper3D free tier is pay-by-result with a preview BEFORE credits are confirmed, Meshy Free gives 100 credits = 5 full Meshy-6 generations. Total cost $0.",
"FIX THE SHOOTOUT INPUT FIRST, or the test is worthless. Every vendor consumes the plate you give it. Produce ONE approved age-14 head-crop plate at full resolution — not the measured 672x1568 full-body plate whose face lands at ~90px. This single file is the fixed input for all four arms of the test.",
"MESHY ORACLE ARM: POST https://api.meshy.ai/openapi/v1/image-to-3d with bearer auth and body {image_url: <data URI of the plate>, ai_model: \"meshy-7\", ultra_mode: true, should_remesh: true, topology: \"quad\", target_polycount: 300000, texture_resolution: \"4k\", enable_pbr: true, pose_mode: \"a-pose\"}. Poll GET on the task id until SUCCEEDED. Expect GLB plus basecolor/metallic/roughness/NORMAL (+emission on meshy-6). Cost: 20 credits. Note the hard cap — target_polycount maxes at 300,000 (and only 15,000 on smart-topology meshy-t2).",
"RODIN ARM — this is the one that matters for characters: Gen-2.5 accepts UP TO 5 MULTI-VIEW IMAGES of one subject, which means our existing MV-Adapter output feeds it directly rather than being thrown away. Set mesh_mode Quad, quality High, add the HighPack addon (+1 credit) for 4K textures and ~16x face count, or quality_override up to 2,000,000. Returns basecolor + metallic + NORMAL + roughness. Preview before confirming spend.",
"TRIPO ARM: v3.0 Ultra, up to 2M polygons, then run its auto-rig (humanoid skeleton, 100+ preset motions, FBX export). Full generate-through-rig runs about $0.20-0.25 per asset on free signup credits. This arm exists mainly to benchmark our unrun UniRig against a working rig button.",
"LOCAL ARM — bake the normal map from the source we were already computing and throwing away. The runner now writes <out>_HIGHPOLY.ply by default (run_trellis2_native_pbr_mv.py:311-322, 4.9M verts / 9.85M faces). Retopo in Blender 5.2 headless with QuadriFlow, then bake high-to-low normal + AO + cavity onto it. This alone retires the director's 'structural ceiling' claim, at zero cost, with no subscription.",
"RUN THE TEXTURING PIPELINE THAT HAS NEVER EXECUTED: Trellis2TexturingPipeline.run(mesh, image, seed, tex_slat_sampler_params, preprocess_image, resolution, texture_size) at trellis2_texturing.py:375-384. It re-textures an existing mesh and reuses that mesh's own UVs, so it runs AFTER the retopo in step 6, not before.",
"ADD THE SKIN DETAIL CLASS NO VENDOR SELLS: SKAP (Texturing.XYZ, skap.texturing.xyz) takes exactly what step 6 produces — an OBJ head plus a 2K-8K diffuse — and returns 16K displacement, cavity, and multi-frequency displacement split across RGB, derived from real skin scans. Meshy's normal map is inferred from your one image at 2-4K; this is a different category of detail, not a better version of the same thing.",
"DELIGHT PROPERLY with MarigoldIntrinsicsPipeline: prs-eth/marigold-iid-lighting-v1-1 for albedo/shading separation and prs-eth/marigold-iid-appearance-v1-1 for roughness and metallicity, ensemble_size>=5, output_uncertainty=True. Free, local, sub-second per image.",
"JUDGE ON THREE CRITERIA THAT ACTUALLY DECIDE THIS PROJECT, not on which single render looks nicest: (a) does the pointed elven ear survive the vendor's retopo, (b) do child proportions at age 7 survive, (c) run the SAME vendor twice on age-7 and age-18 plates and check whether it is recognisably the SAME PERSON. Criterion (c) is where every commercial engine is expected to fail — none of them expose identity conditioning.",
"IF AND ONLY IF a vendor wins, buy the RIGHT one at the RIGHT tier: for photoreal characters that is Hyper3D Business at $120/mo ($96/mo annual, ~416 models, and the only tier that unlocks API access + 4K textures + High-Poly Quad), NOT Meshy Ultra at $100. If Meshy somehow wins the shootout, buy Meshy PRO at $20 — it has identical features to Ultra (API, quad topology, normal maps, 4K/8K textures, free rigging, full commercial ownership); the extra $80 buys only credits and queue priority.",
"EVALUATE ChatAvatar (Deemos/Hyper3D, bundled under the same Business tier) separately for HEADS ONLY. It is the single tool in this field purpose-built for photoreal faces: unified topology, displacement AND normals optimised via SDS, 4K, expression blendshapes, and direct MetaHuman export. If any commercial purchase is justified for this project, this is the strongest candidate — but test the pointed ears against its unified human topology before believing it.",
"FIX IDENTITY LOCALLY REGARDLESS OF THE OUTCOME, because no vendor sells it: raise the ComfyUI Qwen 384x384 VL clamp and 1024x1024 ref-latent clamp in nodes_qwen.py, break the 1.05MP plate ceiling, and stop running MV-Adapter at 1024 on a 768-trained adapter against stock SDXL base (mva_views.py:240) — that checkpoint's prior IS the plastic-doll skin.",
"BEFORE ANY STEAM SUBMISSION, resolve the training-data rights attestation. Valve requires affirmative confirmation that you own the rights to the IP in the dataset that trained any generator producing shipped assets. This applies to the LOCAL stack too — verify TRELLIS.2-4B's actual licence text, do not assume. This is the one place a commercial vendor is genuinely EASIER to defend than our local checkpoints."
],
"features_or_models_needed": [
"Meshy Pro — $20/month ($240/yr). 1,000 credits = 50 full Meshy-6 generations (20 cr each; remesh, rigging and animation all cost 0). API access, 10 concurrent tasks, quad OR triangle topology, texture_resolution 2k/4k/8k, enable_pbr returns metallic + roughness + NORMAL + emission, pose_mode a-pose/t-pose, ultra_mode on meshy-7, exports glb/obj/fbx/usdz/stl/3mf. Full commercial ownership, sell anywhere, no attribution. HARD CAP: target_polycount 300,000. NOT owned — but note the Free tier's 100 credits tests it for $0 first.",
"Meshy Ultra — $100/month, 10,000 credits (~500 models), top queue priority. DO NOT BUY: feature-identical to the $20 Pro tier. The $80 delta is credits and priority only.",
"Rodin / Hyper3D Business — $120/month, $96/month annual (~416 models). The only tier with API access, 4K textures and High-Poly Quads. Gen-2.5 takes up to 5 multi-view images, quality High = 1M faces raw / 50k quad, quality_override to 2,000,000, Quad+HighPack ~16x face count, 4K via HighPack (+1 credit), PBR = basecolor/metallic/NORMAL/roughness. Vendor explicitly markets pores and skin detail. Includes ChatAvatar commercial licence. NOT owned. BEST commercial candidate for photoreal characters — better than Meshy for our use case.",
"Rodin Creator — $30/month ($24 annual, ~60 models). Multi-image to 3D and HD texture, but NO API and NO 4K/High-Poly Quad. Insufficient for a driven pipeline.",
"ChatAvatar (Deemos, bundled in Hyper3D Business) — photoreal facial assets with unified topology, SDS-optimised displacement and normals, 4K, expression blendshapes, MetaHuman export. The closest commercial match to this project's actual requirement. NOT owned; free to generate, paid to export.",
"Tripo v3.0 Ultra — API credits $0.01 each, 2,000 FREE on signup, 40-50 credits/task, generate-through-rig ~$0.20-0.25/asset; web subs from $11.94/mo. Up to 2M polygons, auto-rig with FBX skeleton and 100+ preset motions. FREE TO TEST TODAY, no card.",
"CSM.ai / Cube — DEAD. Shutting down 5 January 2026. Do not evaluate, do not buy.",
"Sloyd — $11-15/mo Plus, $50/mo Pro. Game-ready topology, UVs, LODs, AI rigging. Vendor's own comparison concedes it is NOT for photoreal cinematic or film-quality character work. Wrong tool for this lane.",
"ALREADY OWNED AND UNUSED, all free, all local: the 9.85M-face high-poly now saved by default at run_trellis2_native_pbr_mv.py:311-322; Trellis2TexturingPipeline (never once executed); UniRig at D:/assetgen/UniRig (10.69GB, never run); Blender 5.2 QuadriFlow retopo + normal/AO/cavity bake at D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe; Substrate already enabled in DefaultEngine.ini:31.",
"FREE LOCAL DOWNLOADS that beat the commercial normal maps: SKAP (skap.texturing.xyz) 16K displacement + cavity + multi-frequency split from real skin scans; Marigold Intrinsics prs-eth/marigold-iid-appearance-v1-1 and -lighting-v1-1 via diffusers; StableNormal and StableDelight (github.com/Stable-X, torch.hub loadable). No permission needed, no cost."
],
"evidence_note": "Live web checks on 2026-08-12 against vendor primary sources, plus local source read on this box.\n\nVENDOR PRIMARY: meshy.ai/pricing (Free/Pro $20/Premium $40/Ultra $100/Studio $70/Enterprise; Pro = 1,000 cr, 10 concurrent, API, full ownership); docs.meshy.ai/en/webapp/pricing (Meshy 6 text/image-to-3D = 20 cr covering mesh AND texture; Meshy 5 = 10; AI texturing 10; remesh/rigging/animation = 0; failed generations refunded; Free = CC BY 4.0 attribution, Pro+ = full ownership); docs.meshy.ai/en/api/image-to-3d (POST /openapi/v1/image-to-3d, bearer auth, ai_model meshy-5/6/7/latest + smart-topology meshy-t1/t2, target_polycount 100-300,000 remesh / 100-15,000 smart-topology, topology quad|triangle, texture_resolution 2k/4k/8k, enable_pbr -> metallic+roughness+NORMAL+emission, ultra_mode, pose_mode, auto_size, glb/obj/fbx/usdz/stl/3mf). hyper3d.ai/pricing (Free/Creator $30-$24annual ~60 models/Business $120-$96annual ~416 models with API+4K+High-Poly Quad/Enterprise). developer.hyper3d.ai/api-specification/rodin-gen2.5 (up to 5 multi-view images, High 1M raw / 50k quad, quality_override 1,000-2,000,000, Quad+HighPack ~16x, 4K via HighPack +1 credit, basecolor/metallic/normal/roughness). developers.tripo3d.ai (2,000 free credits, $0.01/credit, 40-50 cr/task). Meshy help centre on commercial rights and third-party resale. Valve/Steam AI-asset rights-attestation requirement.\n\nLOCAL SOURCE READ, NOT RECALLED: C:/dev/humanity-forgotten/build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py lines 294-327 read directly — the _HIGHPOLY.ply export now defaults ON, and the in-file comment at :299-310 states that until today the ~4.9M-vert mesh was measured, sha256'd and discarded on every run for 38 assets, and names the director's 'structural ceiling' claim as the accountability failure Josh identified. Also read :88 (--decimation default 1000000) and :350-364 (to_glb call site).\n\nCAPABILITY AUDIT read, not re-derived: build/ops/PHOTOREAL_CAPABILITY_DIFF_2026-08-12.json, all 6 lanes enumerated. Load-bearing items quoted from it: Trellis2TexturingPipeline 'has never once executed'; UniRig verdict NOT_USED; TRELLIS.2 PBR layout basecolor/metallic/roughness/alpha with no normal channel (trellis2_image_to_3d.py:73-76); zero tangent-space normals anywhere in build/3d; mva_views.py:240 hardcodes stock SDXL base; ComfyUI nodes_qwen.py 384x384 VL clamp and 1024x1024 ref-latent clamp; 672x1568 measured body plate.\n\nNOT VERIFIED, flagged honestly: exact Meshy Premium/Ultra credit allocations came from a third-party aggregator (meshyiai.com) that self-contradicts — it lists 10,000 credits for BOTH tiers while estimating 150 vs 500 models. Meshy's own pricing page does not publish Premium/Ultra credit counts in fetchable text. The $100 = ~500 models figure should be confirmed on the live checkout page before purchase. I did not test any vendor output myself — no shootout has been run, so all quality claims about Meshy/Rodin/Tripo are vendor marketing and third-party review, NOT measured. That is precisely why step 1 is 'spend nothing, run the free tiers'.",
"blockers_that_are_real": [
"IDENTITY ACROSS THE AGE LADDER IS UNSOLVED BY EVERY VENDOR IN THIS FIELD. Meshy, Rodin, Tripo and Sloyd are all image-to-mesh with no identity conditioning, no seed-locked likeness, no cross-generation consistency guarantee. Nothing on the market lets you assert that the age-7 and age-18 meshes are the same person. This is the project's core requirement and it is genuinely hard everywhere — it is NOT a reason the local stack is ceilinged, it is a reason no purchase rescues it. It must be solved upstream in the 2D plate and multiview stage.",
"POINTED ELVEN EARS VS UNIFIED/TEMPLATE TOPOLOGY. ChatAvatar's unified head topology, MetaHuman 'From Custom Mesh' (Epic's own docs warn pointed ears and child proportions 'solve with varying quality', and Creator has no child presets), and vendor auto-retopo all fight a non-human ear silhouette. Must be tested on one head before any path is committed — this is a real risk on both the commercial and the local-retopo route.",
"STEAM / VALVE TRAINING-DATA ATTESTATION. Valve requires affirmative confirmation of rights to the IP in the training dataset for AI-generated assets. This binds the LOCAL stack too, and TRELLIS.2-4B's licence terms have not been verified on disk. Honest concession: a paid commercial vendor with an explicit ownership grant is EASIER to attest for than a research checkpoint, so this is one point genuinely in the commercial column.",
"MESHY'S 300,000-TRIANGLE HARD CAP (15,000 on smart-topology) means Meshy can never supply a high-poly bake source. If the chosen path is retopo-plus-detail-transfer — which the capability audit concludes it must be — Meshy structurally cannot participate in that step. Rodin (2M, High-Poly Quad) and our own 9.85M-face local mesh can."
],
"replicable_across_classes": "\"Replicable for characters, NPCs, creatures and bosses is exactly where the commercial subscription argument collapses, and it is worth being precise rather than loyal about why.\\n\\nSHARED ACROSS ALL CLASSES (build once, reuse forever, all local and free): the high-poly save now defaulting on in the runner; the Blender QuadriFlow retopo plus high-to-low normal/AO/cavity bake; the Marigold delight and roughness recovery; the UE5 Substrate slab plus Subsurface Profile material template; the UniRig skinning stage. None of these are per-character. Every one of them is a script that runs unattended on a 5090 that is already paid for and already runs 24/7. Once built, asset number 900 costs the same as asset number 1: nothing.\\n\\nPER-CLASS, NOT PER-ASSET: skin/scale/fur/carapace material presets; skeleton templates (biped, quadruped, boss-scale); and for creatures specifically, a different displacement source than SKAP, which is a human-skin prior.\\n\\nPER-ASSET AND GENUINELY IRREDUCIBLE: the approved 2D plate ladder and the identity lock. This is the real bottleneck for the hero and it stays the bottleneck for every named NPC. No vendor sells a fix.\\n\\nWHY THE SUBSCRIPTION MATHS FAILS AT SCALE: Meshy Ultra's 500 models a month sounds generous until you count iterations honestly — a hero at 7 ages, rejected and re-rolled five times, is 35 accepted plus perhaps 200 discarded generations for ONE character. Multiply by a cast, plus creatures, plus bosses, plus every re-roll after an art-direction note, and a metered pipeline turns every creative revision into a purchasing decision. That is the wrong incentive on a solo indie project. Local compute is free and the card is idle at night; that asymmetry only widens as the asset count grows.\\n\\nWHERE COMMERCIAL GENUINELY WINS AND SHOULD BE USED: as an ORACLE, not a pipeline. Background props, greybox creatures, and blockout NPCs where identity does not matter are legitimately faster and cheaper through Meshy or Tripo than through our chain — and Meshy's free auto-rig plus guaranteed quad topology at a chosen polycount are things we have not built at all yet. Buy the $20 tier for that and for settling arguments with measurements. Reserve the identity-critical hero ladder for the local chain, because that is the only place identity can be controlled.\\n\\nTHE TRADE, PLAINLY: cancelling Claude to buy Meshy swaps the layer that reads vendor source, found 84 unused features across 6 lanes, and caught a 9.85M-face bake source being deleted on every run, for a single node that generates a mesh from whatever image you hand it. Meshy replaces roughly one and a half nodes of a twelve-node pipeline, and not the two nodes where our measured failures actually live. That is a bad trade — but the honest way to prove it is $0 of free tiers and, at most, $20 for one month, not a defence of the local stack.\""
},
{
"lane": "ADVERSARIAL REVIEW OF THE DIRECTOR'S PLAN — method-level attack, read-only, nothing changed",
"my_error": "The director's headline claim is false, and it is false in the most expensive way: it calls a MISSING STEP a STRUCTURAL CEILING.\n\n1. \"TRELLIS emits no normal map so pores have nowhere to live\" — wrong. Pores do not live in a channel the generator emits; they live in a channel you BAKE. The generator produced 4,915,787 verts / 9,853,672 faces this session. In every professional pipeline that high-poly IS the normal/AO/cavity/displacement source, baked onto a retopologised low-poly. Blender 5.2 is installed and already runs headless in this repo. There is no bake script anywhere in the tree (grep for bpy.ops.object.bake returns nothing; the only normal work is object-space, deliberately unwired, per build/3d/best_a14/records/ROUTE_A_RESIDUALS.md OPEN-9). The runner at build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py now even writes *_HIGHPOLY.ply by default with a comment naming exactly this (\"bake source for normal/AO/cavity onto a retopo low-poly\"). The director diagnosed a model limitation while the bake source sat on disk and the bake stage was simply never written. Josh is right and the accountability read is correct.\n\n2. Proposing SKAP/Marigold/StableNormal to \"synthesise the missing channel\" is proposing a purchase to replace a free local step. Worse, it breaks the stated mandate: SKAP is ~$40-55 per head, credit-based. Seven ages x every companion, NPC, creature and boss is not a pipeline, it is a subscription with extra steps. The same charge Josh is levelling at Meshy applies to the director's own step 6.\n\n3. \"Identity is starved because Qwen sees a ~90px face\" is a true 2D finding (the audit measures it: 384x384 VL budget, ~90px face, plates at 672x1568) but it is the wrong lever for the stated goal. You do not make a HERO identical to an approved reference by feeding a better picture to a stochastic generator. You conform a fixed-topology cage to plate-derived landmarks. This repo already ran that bake-off and picked it: S2 numpy landmark-NRICP, p95 0.01749 vs do-nothing 0.05232 (build/3d/cage/CAGE_PICK.json), the wraps exist (WRAP_A*_V3Q.obj under solver_nricp/), and the stage tooling exists (build/3d/best_a14/retopo_s6/nricp_body.py). The plan does not mention it once.\n\n4. The plan's step 1 (\"generate at capacity\") DESTROYS the deliverable. docs/CHARACTER_FACTORY_PLAYBOOK.md is explicit: every age rung is \"a morph target on one cage with identical topology and UVs\" (~L1373), and \"two age states generated independently CANNOT be blended — there is no vertex correspondence to blend along\" (~L693). Regenerating geometry per age gives seven unrelated assets with seven unrelated auto-atlases. The age ladder Josh approved becomes unbuildable at step 1.\n\n5. The plan ignores the asset that already exists. There is a landed ROUTE_A A14 character with UDIM UVs (uv_v32/A14_PARTITIONED.obj), a de-lit basecolor tile (A14_BASECOLOR_DELIT.1001_PAD.png), a strand groom (build/3d/best_a14/groom/CHAR_0001_A14_GROOM.abc), an eye stage, a rig, and a numbered defect list. The real open defects are OPEN-15 (ship mesh 167,468 triangles, ZERO quads, no edge loop follows elbow/knee/shoulder), OPEN-9 (no LOD chain, no tangent-space normal, no ORM), OPEN-12 (primary limb joints hold zero weight), OPEN-17 (no correctives/PSD). The director's eight steps touch none of them. It is a parameter plan wearing a pipeline plan's clothes.\n\n6. Finally, the plan's own doctrine contradiction: docs/CHARACTER_FACTORY_PLAYBOOK.md ~L814 states, already ratified, \"GENERATIVE 3D IS A BASE SCULPT AND A BLOCKOUT, NOT A SHIPPABLE ASSET\". The plan treats TRELLIS output as the deliverable. The repo already decided otherwise and the director is arguing against a ruling it did not read.",
"correct_steps": [
"FREEZE TOPOLOGY BEFORE ANYTHING ELSE. Run the named-but-unrun cage bake-off (playbook Stage 5; Anny 13,380v/13,378 quads Apache-2.0+CC0 vs the MetaHuman archetype body already exported by build/3d/cage/body_stage/export_metahuman_bare_body.py). Output: ONE cage per body plan, authored quad topology with edge loops at elbow/knee/shoulder/eyelid/mouth, a hand-authored UDIM UV layout with the FACE on its own tile at its own texel density, ear preserved. Acceptance: blind A/B plus child-proportion read against canon 6.567 head-units, plus a do-nothing floor row (repo law).",
"DEMOTE TRELLIS.2 TO SHAPE TARGET. Run build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py --pipeline-type 1536_cascade --max-tokens 131072 --no-low-vram --texture-size 4096 --decimation 1000000, and KEEP the _HIGHPOLY.ply (default on). Fix the recorder first: run_trellis2_native_pbr_mv.py:258 writes the REQUESTED pipeline_type, never the ACHIEVED hr_resolution that sample_shape_slat_cascade may silently step down (trellis2_image_to_3d.py:328-339). Without that, 'generated at capacity' is unfalsifiable. Expected out: ~4.9M-vert PLY + a record naming achieved resolution.",
"CONFORM, DO NOT ACCEPT. Wrap the frozen cage onto the TRELLIS high-poly plus 2D-plate landmarks using the already-ratified S2 numpy landmark-NRICP (build/3d/best_a14/retopo_s6/nricp_body.py; CAGE_PICK.json). Acceptance, all three: identical vertex count and order to the cage; per-vertex deviation heat-map on a fixed colour scale under a stated tolerance; POINTED EAR retained (measure it, do not eyeball it). This is the step that makes the character identical to the reference — not any sampler parameter.",
"BAKE HIGH-TO-LOW IN BLENDER 5.2 HEADLESS. blender.exe -b --python <new bl_bake_character.py>: Cycles bake of TANGENT-SPACE NORMAL, AO, CAVITY, HEIGHT/DISPLACEMENT and POSITION from *_HIGHPOLY.ply onto the conformed low-poly, into the frozen UDIM set, 4K-8K per tile, 16-bit, cage/ray distance driven by the step-3 deviation map. THIS STEP IS THE ENTIRE ANSWER TO 'TRELLIS EMITS NO NORMAL MAP'. It does not exist today and it is free.",
"DE-LIGHT IN IMAGE SPACE, NOT ATLAS SPACE. Run MarigoldIntrinsicsPipeline (prs-eth/marigold-iid-lighting-v1-1, ensemble_size>=5, output_uncertainty=True) on the multi-view PLATES/RENDERS before projection — never on a UV atlas, which is charts, seams and black void, not a photograph the model was trained on. Then project. Gate on the repo's own dielectric band (harness/check_lookdev_albedo.py:45-46; current basecolor FAILS at p02 = 23,24,24) and emit a DELIGHT record, which harness/asset_factory/delta_e_lens.py:746-747 already requires and zero of 31 GLBs carry.",
"TEXTURE INTO OUR UV LAYOUT, ONCE. Trellis2TexturingPipeline.from_pretrained(config_file='texturing_pipeline.json').run(conformed_mesh_with_uvs, plate, tex_slat_sampler_params={'steps':50,'guidance_strength':2.5,'guidance_rescale':0.3,'rescale_t':3.0}, resolution=1024, texture_size=4096) via build/3d/bakeoff/native_tex/run_trellis2_texture_refine.py (written, ZERO executions). It reuses the incoming mesh's UVs (trellis2_texturing.py:299-302), so our authored layout survives. This single call SUPERSEDES the director's steps 2, 3 and 4.",
"AUTHOR MICRO-DETAIL AT THE MATERIAL LEVEL, NOT PER ASSET. One shared tiling skin micro-normal + cavity + pore-mask set, authored once (the central-difference height-to-tangent-normal code already exists at harness/asset_factory/author_fabric_set.py:400 and its encoding is documented at :953), blended over the baked normal by a region mask with a UV multiplier. This is how AAA puts pores on 400 NPCs, and it is what makes SKAP an optional hero-only luxury instead of a per-head invoice.",
"SPLIT THE ASSET THE WAY PRODUCTION DOES. Separate meshes: body, eyes (sclera + refractive cornea shell, not a painted disc), teeth/gums/tongue, eyelashes, brows, and hair as a STRAND GROOM — build/3d/best_a14/groom/CHAR_0001_A14_GROOM.abc already exists and the card cap was already retired as a dead end (ROUTE_A_RESIDUALS OPEN-8a). Never let a generative model emit fused hair geometry again; condition on the bald plate (CHAR_0001_KEYSTONE_BARE_14_BALD.png) which the repo already proved is the correct conditioning source.",
"RIG AND SKIN WITH THE TOOL WE OWN AND NEVER RAN. UniRig stage 3 ONLY, against the UE5 mannequin armature: bash launch/inference/generate_skin.sh --input <mannequin_bound.fbx> --output <skin.fbx> --data_name raw_data.npz, then python -m src.inference.merge --source <skin.fbx> --target <hi_poly.fbx> --output <rigged.fbx> --add_root true. Raise faces_target_count off 50000, group_per_vertex 4->8 (src/system/skin.py:300), voxel_skin grid off 196, reskin iter_steps off 1. Close OPEN-12 (primary limb joints currently hold ZERO weight). A/B against bl_skin_to_mannequin.py, which self-declares 'PREVIZ-AUTOSKIN'.",
"BUILD THE AGE LADDER AS MORPH TARGETS. Re-run step 3 (conform only) against each age's approved plates using the SAME cage, preserving vertex order. Output: 7 blendshapes on one asset, not 7 assets. Add the expression/FACS set and correctives in the same space (OPEN-17: no corrective, no smoothing filter, no PSD exists anywhere today).",
"EMIT THE LOD CHAIN. build/3d/characters/bl_lod_tooth.py exists and OPEN-9 says no LOD chain has ever been built. Playbook budget: 4/4/3 steps, A1_vertex_tri_ratio_max 2.0, ear retention measured per LOD (a prior run recorded 99.20% ear retention across 119,994/59,996/29,998/11,998 tri).",
"SHADE IT IN UE 5.8 AGAINST SUBSTRATE, WHICH IS ALREADY ON (C:/dev/Humanity/Humanity/Config/DefaultEngine.ini:31). Substrate slab with a Subsurface Profile at the bottom of the material topology (profiles are not blendable): Burley Mean Free Path Color + Distance, World Unit Scale, and DUAL SPECULAR Roughness 0 / Roughness 1 / Lobe Mix. Separate materials for eye (cornea IOR + refraction, limbus, iris parallax) and hair (strand groom + hair BSDF, root-tip gradient, depth-sorted alpha). One material graph, reused by every character.",
"PROVE IDENTITY, NOT LIGHTING. The goal is 'identical to the approved 2D ladder', so the gate must be a likeness delta: orthographic front/side render at the plate's own camera, landmark-ratio comparison against build/3d/best_a14/face_pass/FACE_LANDMARK_RATIOS_A14_V3.json, silhouette overlay (build/3d/best_a14/silhouette_overlay.py), and delta_e_lens.py on the face crop — every arm rendered by the SAME instrument, with a do-nothing floor row. The director's step 8 'relight to prove' proves de-lighting only, and proves nothing without two HDRIs and a floor.",
"ONLY NOW consider paid detail services, hero-only, as a top-up on a pipeline that already works without them — and re-read SKAP's licence for game shipping before any purchase (the audit flags the licence as unverified, only the pricing confirmed)."
],
"features_or_models_needed": [
"Blender 5.2 headless — ALREADY HAVE at D:/assetgen/tools/blender-5.2.0-windows-x64/blender.exe, already used headless. Provides the entire missing bake stage (Cycles normal/AO/cavity/displacement bake), QuadriFlow, Shrinkwrap, Multires Reshape, symmetrize. FREE. No bake script exists yet — that is authoring work, not a download.",
"TRELLIS.2 4B — ALREADY HAVE (D:/assetgen/TRELLIS2_code + D:/assetgen/models/TRELLIS2). Reclassified as blockout + high-poly bake source. Trellis2TexturingPipeline weights (texturing_pipeline.json, shape_enc_next_dc_f16c32_fp16) are on disk and have ZERO executions.",
"Marigold Intrinsics — prs-eth/marigold-iid-lighting-v1-1 and prs-eth/marigold-iid-appearance-v1-1, free HF download via diffusers, fp16, sub-second per image. NOT confirmed on disk. Applied to PLATES/VIEWS only, never to a UV atlas.",
"UniRig — ALREADY HAVE (D:/assetgen/UniRig + 10.69GB weights), NEVER RUN, zero callers in the repo. Free. Use stage-3 skin-only against the UE5 mannequin.",
"SkinTokens (VAST-AI, MIT, ~14GB VRAM, HF VAST-AI/SkinTokens) — free download, UniRig's own README now points at it as the successor; --use_skeleton does the same mannequin-skin job with a claimed 98-133% accuracy lead. Run head-to-head with UniRig.",
"MetaHuman / MetaHuman From Custom Mesh — ALREADY HAVE via UE 5.8; probes already written at C:/dev/Humanity/Humanity/Tools/char/probe_metahuman_*.py and a capability record emitter at build/3d/characters/emit_metahuman_capability_record.py. Free under the UE licence. RISK, from Epic's own docs: pointed ears and fantasy features 'solve with varying quality' and there are no child presets — must be measured on one head before adoption.",
"MPFB2 / MakeHuman, or Anny (13,380v/13,378 quads/163 bones, Apache-2.0 + CC0, WHO-calibrated infant-to-elder) — FREE download, NOT INSTALLED. Needed to settle the cage fork the playbook itself calls 'the highest-value thing this brief can point at'.",
"Tileable scan-based skin micro-detail (pore/cavity) source — one set, authored once, reused by every character. Can be generated locally from the existing height-to-normal code path; a paid Texturing.XYZ multi-channel tile is the premium option. This replaces per-head SKAP for all non-hero classes.",
"SKAP (skap.texturing.xyz) — PAID, ~$40-55 per head, credit-based. OPTIONAL and HERO-ONLY. Licence for game shipping is UNVERIFIED. It is a top-up, never a pipeline stage, because it does not scale to NPCs/creatures/bosses.",
"Meshy $100/month — DOES NOT FIX THIS. It emits the same class of fused single-blob generated mesh with auto-retopo that, per Tripo's own engineering blog cited in our playbook, 'has no concept of where an elbow bends', and Meshy's own docs concede fused fingers and asymmetric faces and call DCC repair standard practice. Buying it swaps one blockout generator for another and leaves retopo, bake, UV authoring, mesh separation, blendshapes, LODs, skinning and the skin shader exactly as missing as they are today."
],
"evidence_note": "READ, NOT RECALLED, this session: build/ops/PHOTOREAL_CAPABILITY_DIFF_2026-08-12.json in full (all six lanes, 317 lines); build/3d/bakeoff/native_tex/run_trellis2_native_pbr_mv.py lines 270-340 (confirms raw_geometry is hashed AND that a _HIGHPOLY.ply export was added 2026-08-12, default ON, with a comment naming the bake use — so the brief's 'high-poly appears to be discarded' is now stale in code but the BAKE that consumes it still does not exist); docs/CHARACTER_FACTORY_PLAYBOOK.md L620-720 (cage/conform doctrine, S2 NRICP pick, MetaHuman archetype base in hand, MPFB2 not installed, Anny bake-off not run, 'two age states generated independently CANNOT be blended') and L790-855 ('GENERATIVE 3D IS A BASE SCULPT AND A BLOCKOUT, NOT A SHIPPABLE ASSET'; Hunyuan struck on licence; shell cleanup and symmetrize chartered-and-unbuilt) plus grep hits at L984 (LOD budget 4/4/3), L1373 (age rungs are morph targets on one cage with identical topology and UVs), L1644 (ear retention 99.20% across a 4-step LOD chain), L1774-1776 (187-row creature census into 8 body plans); build/3d/best_a14/records/ROUTE_A_RESIDUALS.md OPEN-8a (hair is ~1,100 flat ribbon cards, retired as a dead end, groom rebuild ruled), OPEN-9 (no LOD chain, no tangent-space normal, no ORM), OPEN-12, OPEN-15 (167,468 tris / ZERO quads / no loop at elbow, knee or shoulder), OPEN-16, OPEN-17; harness/asset_factory/author_fabric_set.py:400, :953, :1032-1036 ('skin_today tangent_space_normal: NONE'), :1176-1198 (rank-8 owed: 'skin still has no subsurface profile, no dual-lobe specular, no tangent normal, no micro-normal and no cavity'); docs/EYE_STAGE_SPEC.md head. DIRECTORY EVIDENCE by ls/find: build/3d/best_a14/{retopo_s6,groom,uv_v32,face_pass,likeness_v3,rig,silhouette} exist with real artifacts (CHAR_0001_A14_GROOM.abc, A14_PARTITIONED.obj, FACE_LANDMARK_RATIOS_A14_V3.json, nricp_body.py); build/3d/characters/bl_lod_tooth.py and emit_metahuman_capability_record.py exist; D:/assetgen/UniRig present. NEGATIVE FINDING, load-bearing: a repo-wide grep for bpy.ops.object.bake / bake_normal / multires bake over all *.py returns NO character bake script — the only tangent-related code is GLB tangent passthrough in bl_smooth_repair.py. NOT VERIFIED, stated plainly: I ran NOTHING (read-only), so every quality claim here is a mechanism argument from source and landed records, not a measurement; I did not open the Marigold, SKAP or MetaHuman licence texts this session and inherit the parent audit's flags on all three; I did not verify VRAM headroom for 1536_cascade at 131072 tokens (the audit records a 41,539 MiB peak already, implying host spill on a 32GB card); and I did not confirm whether Marigold weights are present on disk.",
"replicable_across_classes": "SHARED, AUTHORED ONCE, USED BY EVERYTHING: the METHOD (cage -> conform -> high-to-low bake -> frozen UV -> texture in our layout -> shared material), the Blender bake script, the tiling skin/hide/scale micro-detail library, the UE Substrate skin/eye/hair material graphs, the LOD tooth chain (bl_lod_tooth.py), the identity gate (ortho render + landmark ratios + silhouette overlay + delta_e_lens), and the de-light stage. None of these are per-character.\n\nPER BODY PLAN, NOT PER CHARACTER: the cage. The repo has already censused 187 creature rows into 8 body plans (QUADRUPED 62, NEEDS_CANON_BODY_PLAN 42, WINGED 32, AQUATIC 20, LIMBLESS 13, ARBOREAL_PRIMATE 11, SPRAWLING_QUADRUPED 4, MULTI_LEGGED 3), and the playbook already states a quadruped and a serpent cannot share a vertex order. So the correct unit of investment is roughly 9 cages (1 humanoid + 8 creature plans), each authored once with loop-correct quads and a UDIM layout. Every human character, companion and NPC in the game is then the SAME cage with different conform targets and different textures — which is precisely what makes 200 NPCs affordable and what regenerating per character makes impossible.\n\nPER CHARACTER, CHEAP: approved 2D plates, one TRELLIS blockout + high-poly, one conform, one bake, one texture set, one skin-weight solve. Per AGE: a conform only, emitted as a morph target on the frozen topology.\n\nWHERE IT DIVERGES BY CLASS: hero gets the premium top-ups (hand sculpt pass, optional SKAP, strand groom with physics, full FACS set, correctives). Companions get the same cage and material with fewer blendshapes. NPCs get cage + parametric variation in the morph space + palette swaps, no per-character generation at all. Creatures and bosses run the identical order against their body-plan cage — generative 3D stays the blockout for all of them, and the bake is what carries their scale/hide/feather micro-relief exactly as it carries human pores. Bosses additionally get their own micro-detail tile if their surface is not in the shared library.\n\nWHAT KILLS REPLICABILITY: any step priced per head (SKAP at $40-55), any step that regenerates topology per asset (the director's step 1), and any step that depends on a per-run auto-atlas (the director's step 4). All three are in the plan under review.",
"blockers_that_are_real": [
"MetaHuman conform on POINTED ELVEN EARS and on child proportions. Epic's own documentation says fantasy features such as pointed ears 'solve with varying quality' and calls out oversized-head/small-body child proportions as suboptimal, and MetaHuman Creator still ships no child presets. Our ladder is ages 7-18 with pointed ears — two direct hits. This is a genuine risk that must be settled by a measured test on one head (A11) with an ear-retention metric, not assumed either way. Fallback is already identified: authored cage + NRICP conform, or QuadriFlow + Shrinkwrap + Multires Reshape.",
"1536_cascade VRAM on a 32GB 5090 at max_num_tokens 131072 is untested. A14_face_haired already recorded a 41,539 MiB peak allocation, which exceeds 32GB and implies host-memory spill. Raising both resolution and token budget may not fit; it needs a staged test, and the recorder must be fixed first to write the ACHIEVED hr_resolution or a silent downgrade will look like 1536 failing when it never ran.",
"Licence bans that are real and already proven by direct reads: Hunyuan3D-2.x (Tencent Community Licence, Territory excludes EU/UK/South Korea, Output includes generated meshes), CHORD (Ubisoft ML Licence, Research-Only Copyleft), RigAnything (Adobe Research, non-commercial), and nvdiffrast on the render/preview side. These constrain WHICH tools, not whether the method works — the ratified Blender-headless substitution already exists for the tainted exporter.",
"NOT a blocker, listed to close it: the missing tangent-space normal map. It is a bake, the source geometry is now saved by default, and Blender 5.2 is installed. Anyone who reports this as a ceiling again is reporting a step nobody wrote.",
"NOT a blocker: identity match to the approved 2D ladder. It is a conform onto a fixed-topology cage plus a quantitative likeness gate, both of which this repo has already picked, tooled and measured (CAGE_PICK.json, nricp_body.py, FACE_LANDMARK_RATIOS_A14_V3.json, delta_e_lens.py). It has never been run end to end, which is a schedule fact, not a limit."
]
}
]
}