Written 2026-07-08 (authoring session), last updated 2026-07-10 through §10z-clod
(.clod / NelExportLodCharacter headless — last surface format; 105/105 produced, 99 FLOATEQ + 6 residual after the §10z-clod-bis audit round),
§10z-quinze (interface weld, mapext verification, sky-dome hemisphere, Edit-Mesh clone
decode, decompAffine gold revert, Physique rigid one-link, Morpher bsList), §10z-seize
(Map Extender drop-in plugin, 118/118 live Max 2010) and §10m-quater (linked-COM attach).
Prior rounds: §10z-douze/treize/quatorze, §10s-*, §10k-bis, §10z-onze.
Pick-up map for the next few
implementation sessions. Read pipeline_max_design FIRST — especially
§12 (rules for the implementing session) — and the per-format sections referenced below. The
goal state: build_gamedata's 1_export.py steps can run the headless pipeline_max_* tools
instead of 3ds Max, for every asset type, with corpus-proven fidelity.
nel/tools/build_gamedata/processes/*/1_export.py + their maxscript/*.ms scripts. Every Max-side export goes through one of these NelExport* MAXScript entry points (implemented in nel/tools/3d/plugin_max/nel_export/nel_export_script.cpp → nel_mesh_lib/): NelExportShapeEx, NelExportAnimation (used by BOTH the anim and shape processes), NelExportSkeleton, NelExportSkeletonWeight, NelExportInstanceGroup (ig + ligo), NelExportCollision (rbank + ligo), NelExportPACSPrimitives, NelExportVegetable, NelExportLodCharacter, plus ExportRykolZone (zone) and LigoExportZone (ligo). The ps process is a plain file copy (no Max involvement) despite its log line. Everything else under processes/ (rbank build, ig_light, zone_light, banks, maps, sheets, …) is post-export C++ tooling that already runs headless.
Reference outputs live in ~/pipeline_export (continents/ecosystems/common trees, mirroring the workspace config under ~/ryzomcore_leveldesign/workspace); sources in ~/ryzomcore_graphics.
| Entry point | Process(es) | Output | Refs in ~/pipeline_export | Headless tool | Status |
|---|---|---|---|---|---|
| ExportRykolZone | zone | .zone | 6306 (incl. ligo) | pipeline_max_export_zone | GREEN — corpus 1068 exact + 130 x87-tier + 3 budget (§10h) |
| LigoExportZone | ligo | .zone/.ligozone bricks | (in above) | pipeline_max_export_zone --ligo | GREEN — .ligozone 100% byte-identical; .zone same tiers as ExportRykolZone above (§10h) |
| NelExportSkeleton | skel | .skel | 203 | pipeline_max_export_skel | GREEN — legacy 99.4% drot, era classes documented (§10, §10e) |
| NelExportSkeletonWeight | swt | .swt | 1 (+ corpus) | pipeline_max_export_swt | GREEN (§10f) |
| NelExportInstanceGroup (standalone ig) | ig | .ig | 54 direct (→ 18497 final, downstream via land_exporter, out of scope) | pipeline_max_export_ig | GREEN — T1/T2 116/116; 54/54 direct-ref field-exact under uninit-mask (--max-direct-diff budget retired to 0, gate strict); processed-tier 40/40 match. 0 truly byte-identical remains a legitimate tolerance class (reference exporter's SunContribution/Light[]/STL-light-tie-order uninit heap). §10w closed the last open diff via the Edit Mesh 0x0130 created-verts decode |
| NelExportInstanceGroup (ligo bricks) | ligo | .ig | 537 direct | pipeline_max_export_ig --ligo | MOSTLY (near green) — T1/T2 1201/1201; 529/537 (98.5%) match under uninit-mask (71 byte-identical + 458 uninit-only under x64/SSE; under VS2008/x87 shifts to 130 byte-identical + 399 uninit-only — same 8 open, x87 codegen zeros the uninit heap x64 leaves random). 8 open, budgeted DIFF_BUDGET=8 (§10w, was 89 → 28 → 8 in one session): ~5–6 PS-instance clusterize AABBox borderline containment (extra cluster links on rotated FX bboxes, LOW real-content risk — extra links harden culling rather than drop content) + 2–3 unclassified singleton files (taverne palme_type_C3 Scale ≈3e-5, jungle-2 ju_water_jungle_coude Pos ≈15cm, brandon fortress_int_03/04 Clusters + one Scale) |
| NelExportAnimation | anim, shape | .anim | 9146 | pipeline_max_export_anim (+ shape --anim-out) |
MOSTLY — whole corpus DIRECT-gated since the §10m-ter audit (2026-07-09): all 4524 anim sources have a fresh direct reference (fauna/sky anim_export tiers wired — fauna was informational-optimizer-only before); non-biped 25 byte-identical + 46 floateq / 71 (ULP scale-value class, codegen-independent); biped 7 byte-identical + 4445 structural-exact, 0 structural fails; the audit closed Bezier Scale outTan [14..16], nested-COM re-entry (mektoub rider subtree) and the linked-COM rigid figure attach (kitin Bip02 5.5 m class → 2e-4); shape-process anims 110/110 byte-identical with not-produced accounting; in-between IK bounded (chars median ~0.068, §10r/§10s; arm-pin default ON §10s-quat/cinq/sept; pole [22..24] §10s-six); §10m-quater decoded the linked-COM attach EXACTLY (L = 0x0112 × the corrected current-position frame — position AND rotation float-exact; the stun-trilogy "era class" was our decode error, withdrawn; mektoub pair 1.449 → ~1.32, its residual is the mount's 4-link HorseLink/Toe IK tier); remaining: FK-floor plants, in-plant foot rot, blend ramps |
| NelExportShapeEx | shape | .shape | 11251 | pipeline_max_export_shape | MOSTLY (coverage 99.97% — 3589 exported, 1 not produced) — the §10z-douze/treize/quinze rounds closed every skip class except skinned-maxverts=1 (the gauntlet authoring case, defective_max_files §1.7): spline caps + UVW planar + PB2 prims (§10z-douze), skinned WORLD-space VBs + Physique link-parent rule → weight-exact skinning (§10z-treize), interface border weld + Morpher bsList + Physique rigid one-link (§10z-quinze). Corpus (2026-07-10 session-final, x64): 141 byte-identical + 1330 float-eq + 736 differ + 113 mapext-verified + 3 mapext-diff + 983 lightmap-verified + 282 lightmap-diff + 1 no-ref; VS2008/x87 same binaries: 158 + 1354 + 695 (reference codegen closes a tier of borderline files, §10z-treize pattern). Map Extender fully closed: cache decode §10z-quatorze, hemisphere fix → 118/118 applies, --compare-mapext-mask verification tier, per-node validation matrix in defective_max_files §4.4 (2 byte-identical + 8 FLOATEQ-full + 105 verified + 3 diff), and a drop-in plugin (§10z-seize) validated 118/118 in live Max 2010. Remaining open: interface-weld normals residual (~75 files, 0.002–0.2 border corners), ~550-file CMeshMRMSkinned MRM-structural class (float-noise input divergence, informational tier), CMeshMultiLod slave-material merge, UVW non-planar / Unwrap UVW / FFD tail |
| NelExportCollision (standalone/rbank direct) | rbank | .cmb | 16 direct (+ 429 placed-instance copies land_exporter — a separate, already-headless C++ tool — writes into RBankCmbExportDirectory from the ligo-brick Max exports below, same mechanism as its .ig placement copy; porting that copy step is out of scope for pipeline_max, but it is real and load-bearing: it's what rbank's "Build rbank indoor" step reads to build the shipped .rbank/.gr/.lr) |
pipeline_max_export_cmb | MOSTLY (near green) — direct tier (gated): 16/16 files export, 12/16 byte-identical + 3/16 x87-tier + 1/16 differ (DIRECT_DIFF_BUDGET=1, was 3): §10z-quinze's Edit-Mesh created-vert CLONE decode (0x0130 srcTag != -1 = clone of input vertex + offset from its PRE-move position) made FY_hall_reunion and Zo_bt_hall_Reunion_vitrine byte-identical; the single remaining differ is Zo_bt_Hall_Conseil's rotation-about-Y class (open on its own terms since §10v, precision ruled out). |
| NelExportCollision (ligo bricks) | ligo | .cmb | 221 measured igs (subset of the 537 ligo bricks that contain collision geometry) | pipeline_max_export_cmb --ligo | MOSTLY — genuine Max export (nel_ligo_export.ms calls NelExportCollision per brick exactly like NelExportInstanceGroup, landing brick-level .cmb in LigoEcosystemCmbExportDirectory which is land_exporter's RefCMBDir input for the placement copy in the row above) and is now GATED in the corpus test (LIGO_DIFF_BUDGET=95 since §10z-quinze, was 105 at §10z-ter's gating; --gate-t3 fails on regression past that count — same shape as ligo_ig_corpus.py's DIFF_BUDGET). §10z-ter closed the majority of §10x's "base object is not a GeomObject" gap via EditablePoly + parametric-primitive extraction paths on cmb (previously shape-only): ~230 skipped node warnings dropped to 47. Ligo T3 (2026-07-10): 34 byte-identical + 95 float-eq + 92 differ + 0 export failures across 221 measured igs (LIGO_DIFF_BUDGET tightened 105 → 95; the clone decode moved several bricks) — up from 11 + 15 + 22 + 6 = 54 (§10v baseline). |
| NelExportPACSPrimitives | pacs_prim | .pacs_prim | 493 | pipeline_max_export_pacs_prim | GREEN — 493/493 byte-identical (§10v) |
| NelExportVegetable | veget | .veget | 126 | pipeline_max_export_veget | MOSTLY — T1/T2 126/126 clean; T3 (corrected §10z-douze — the earlier "505 exported" record was a -j shared-outdir race artifact, fixed with per-file outdirs + a pipeline_max_veget_corpus ctest gate): 126 exported: 25 byte-identical + 97 size-eq (float-noise byte-diff) + 4 size-diff (Ju_DebrisFeuillesA class, unclassified), 0 export failures, 0 references not produced. Shared code with the shape exporter. §10z-onze/§10z-douze. |
| NelExportLodCharacter | clodbank | .clod | 105 | pipeline_max_export_clod | MOSTLY — 105/105 refs produced, 0 export failures; T1/T2 106/106; T3 99 float-eq (2 mapext-UV-masked) + 6 residual (§10z-clod-bis closed the bone-set class via the Physique link-tree boundary decode, the doubled-face normals via float-Point3 deltas + (1,0,0) fallback, and phytopsy via the mapext UV-mask policy; the 6 left are the shared RVertex-merge/chaotic-sign tails). Shared PHYSIQUESKIN + CMeshMRM NLods=1/Divisor=1 → CLodCharacterShapeBuild. Gates: pipeline_max_clod_corpus + pipeline_max_clod_corpus_vs2008 (x87 buckets identical) |
Cross-cutting infrastructure that is DONE and reusable: the storage/scene layer with full T1/T2 byte-identity over every corpus (max files parse→build byte-identical), the OLE backend (§2a), the VS2008/Wine x87 reference build (§2b), Edit Mesh / Editable Poly / Edit Patch geometry decode (ig/zone/shape sessions), the shared pipeline_max_export_common library — EDITMESH (Edit Mesh decode+apply, §10x, now driving ig+cmb+shape), XREFRESOLVE (§10x, ig+cmb), PRIMMESH (Box/Cylinder/Sphere/Plane topology + Box/Plane UVs, §10y/§10z-sept), PHYSIQUESKIN (Physique vertex weights → CMeshBuild skinning, §10z-sept — reused by .clod §10z-clod), SPLINESHAPE (BezierShape/Spline3D compact knot decode, §10z-dix — remanence shipping; tessellation pending for cmb XRef / mesh-eval), TRACKBUILD (shared Max keyframer → NL3D ITrack builders incl. typeColor, §10k-bis), MAPEXT (Map Extender UV cache, §10z-quatorze), SPLINEMESH (spline tessellation + the corpus-reversed morph-cap rule, §10z-douze — the spline-cap shapes), UVWMAP (planar projection default-ON §10z-douze; non-planar gated), IFACEBUILD (interface-mesh border weld, §10z-quinze — shape-side), max_scene/max_math/OLDPBLOCK/db_path/appdata_util — materials + ParamBlock2 (§10j), typed Bezier Point3 (0x200A) / Bezier Color (0x2011) / TCB Point3 (0x442314) controllers, the biped system incl. typed keytrack editing (§10t/§10u), AppData flag reading, and the corpus-test harness patterns (*_corpus.py, fork-per-file, tolerance tiers, --compare-lightmap-mask / --compare-mapext-mask). Plus, outside the headless tools proper: the Map Extender drop-in Max plugin (plugin_max/map_extender → mapext198m3.dlm, §10z-seize — in the quick_start redist set for Max 9..2023, validated 118/118 in live Max 2010) and the resource-compiled VC90 SxS manifest mechanism (NL_EMBED_SXS_MANIFEST_RC, nel.cmake — mt.exe page-faults under Wine; without it, Wine-built plugins fail on real Windows with LoadLibrary error 126). Standing warning (§10z-quinze): max_math's decompAffine adaptation is corpus-arbitrated — a "gold alignment" against an offline Max 2010 probe was landed and REVERTED after it broke the ig gates structurally (the probe validated reassembly, which is convention-invariant); never change pipeline_max_export_common math without re-gating ig/ligo-ig/shape/skel/zone together.
.pacs_prim and .cmb landed 2026-07-08 (§10v) — .veget and .clod close the rest of the SURFACE.
.cmb — NelExportCollisionpipeline_max_export_cmb. §10z-ter landings: (a) the face-index remap chunk the §10x handoff flagged was 0x0210 itself — misclassified in §L.2 as "created map-1 texture-vertex faces", corpus-validated across 445 files / 113 chunks / 2881 entries as the modern equivalent of legacy TOPO_FACEMAP_CHUNK 0x2780 (per-face per-corner index rewrites, (uint32 faceIdx, uint32 applyMask, uint32 v[3])), applied in the shared EDITMESH::applyEdits between moves and face-deletes; FY_hall_reunion face-index match 247/248 → 248/248, Zo_bt_hall_Reunion_vitrine face diffs 221 → 0 (25 vertex offsets remain); (b) EditablePoly + parametric-primitive (Box/Cylinder/Sphere/Plane) extraction paths added to cmb (shape had them, cmb didn't) — ligo brick warnings ~230 → 47, and the 16 ligo bricks that produced zero output all now succeed (0 export failures). (c) Ligo tier is now GATED (LIGO_DIFF_BUDGET=105) instead of informational — nel_ligo_export.ms calls NelExportCollision per brick exactly like NelExportInstanceGroup, so its fidelity matters exactly as much as the standalone tier's. srcTag != -1 is a CLONE of input vertex srcTag with the Point3 as an OFFSET from the source's PRE-move position (the modern merge of legacy TOPO_CVERTS_CHUNK + the clone's move; "srcTag ignored" in the earlier decode was wrong). Remaining open: Zo_bt_Hall_Conseil vertex offset (still open on its own separate terms since §10v — rotation-about-Y suspected, precision ruled out); 35 XRef sources resolving to Max SplineShape (superclass 0x40 — SPLINEMESH tessellation now exists for the shape-cap path, wiring it into cmb is the follow-up); 12 Boolean compound objects on Matis buildings (class (0x51db4f2f, 0x1c596b1a), union/intersection/subtraction of two operands — no quick path without SDK)..pacs_prim — NelExportPACSPrimitivespipeline_max_export_pacs_prim, 493/493 byte-identical..veget — NelExportVegetablepipeline_max_export_veget, 126 exported: 25 byte-identical + 97 size-eq + 4 size-diff (Ju_DebrisFeuillesA class open)..clod — NelExportLodCharacterpipeline_max_export_clod, 105/105 refs produced, 99 FLOATEQ + 6 residual (the shape-shared RVertex-merge/chaotic-sign tails; codegen-stable under x87). Reuses PHYSIQUESKIN + shape mesh path.NelExportShapeEx beyond plain CMesh, in rough dependency order (post-§10z state):
CMeshMRM / CMeshMRMSkinned)skinned-physique skip class closed; quality still DIFF). Shared PHYSIQUESKIN in pipeline_max_export_common: bone table = modifier refs, boneRef=~index, top-4+normalize into SkinWeights, skeleton walk for BonesNames, root-weight fallback for ConvertToRigid residues. Remaining quality work: full ConvertToRigid replay (differential dataset), MRM era tolerance tiers, interface border-weld. Skin modifier still unimplemented (0 corpus hits). §10z-quat/six history (detection, Part M characterization, bone table location) remains the decode foundation — see design §10z-quat/six/sept. Part M open items reduced to cross-link boneRefs + ConvertToRigid fidelity: (i) boneRef residual positive values (~4 %) — deformable cross-links, needs a differential dataset authoring TWO variants of a minimal skinned mesh with one vertex's bone reassigned to pin), (ii) weight-to-.shape normalization (Physique's stored rigidity blend vs the .shape's top-4 normalized weights — likely needs the reference's own SDK rigidity→final-weight math replayed on the stored bytes; if not derivable from stored payload alone, byte-identity is closed and placeholder root-bone weights become the pragmatic fallback for coverage), (iii) Skin modifier fully uncharacterized (same PMB_SKIN_DUMP diagnostic applies when a Skin-carrying corpus file appears — none in the Max 3-era corpus, but Max 4+ authored assets would), (iv) bone-name array source (per-modifier bone list location on the Physique scene class not yet pinned; may live on getReference() slots or in a container chunk on the modifier itself, separate from the mod-app). The Session-3 plan below is unchanged; §10z-quat gives it a written baseline instead of a cold start. Update 2026-07-09 (§10z-six): two of Part M's four blockers closed WITHOUT a differential dataset — the per-modifier bone table is the modifier's OWN reference array (87 INode refs on fy_hof_armor01_pantabottes, named, 16 NULL slots counted), and the primary boneRef is ~index (one's-complement) into it, validated end-to-end (boneRef −77→idx 76→Bip01 L Thigh etc., anatomically exact). The bone-name array source is therefore solved too. Remaining: ~4 % deformable cross-link boneRefs (small positive, not ~index) and the Bip01 root-link reassignment from ConvertToRigid(TRUE) — both still need the differential dataset. PMB_SKIN_DUMP now also dumps the modifier's references; --dump-skin <shape> validates a decode against a built reference shape. Update 2026-07-10 (§10z-douze/treize/quinze) — the Physique decode is COMPLETE, all without the differential dataset: positive cross-link boneRefs are 1-BASED link indices (neg k=~stored, pos k=stored−1; every 0x0989 record is uniformly 4+20·numBones bytes — the "different record structure" hypothesis was wrong); the deforming bone is the INode PARENT of ref[k+1] (equals ref[k] mid-chain, differs at chain boundaries); skinned meshes export WORLD-space VBs (GetObjectTM — why no skinned vertex ever position-matched before); a single-link record is RIGID (weight 1.0 regardless of the stored float, which is Physique's rigidity factor — the c03/monster family stores 0.0 there and 620+ verts/mesh silently fell to root). Result: position-matched vertices are weight-exact against the references and whole CMeshMRMSkinned files compare byte-identical (~122+ corpus-wide). Remaining quality is the ~550-file MRM-STRUCTURAL class (the MRM builder takes different simplification decisions on float-noise-divergent inputs — the byte-identical files prove exactness on bit-equal inputs; informational tier, same shape as the anim optimizer tier) plus the interface-weld normals residual below.common/construction/gen_mo_charognemammal 'materials: 3 vs 4') — likely the reference plugin merges slave materials into the base list; not yet root-caused. Coarse-mesh routing (with-coarse-mesh output dir) was already handled by M1's NEL3D_APPDATA_LOD_COARSE_MESH scan.CWaterShape) (221 files)WATERBUILD::buildWaterShape replicates CExportNel::buildWaterShape: mesh eval + node-local transform + Z=0 projection + convex hull → CPolygon2D::setShape, PB2 reads for pool id / wave height / slot enables + texmaps, textures via a lean BitmapTex + NeL-Multi-Bitmap loader; default transformation from decompMatrix(getLocalMatrix). Same-session byte-identity rounds 1+2 landed on top of the coverage path: (r1) CTextureBlend day/night envmap pairing when slot 2's TEXMAP is present + CWaterShape stream v7→v4 downgrade (patch version byte + truncate trailing 14 bytes) → file size matches reference exactly (698 vs 698 on tr_water_00), byte diffs 111 → 35; (r2) correct v14-water-material NLB_MAIN param IDs pinned empirically via PMB_WATER_DUMP — fBumpU/VScale/Speed=0x09..0x0c, fDisplaceU/VScale/Speed=0x0d..0x10, fWaterHeightFactor=0x11, iWaterPoolID=0x12 — plus tail bool defaults (splash=true from ctor, UsesSceneWaterEnvMap[0/1]=false explicit) → byte diffs 35 → 3. 220 of 221 water files close (the 1 residual is a genuine missing-required-map case matching reference behavior). §10z-bis added a compareWaterShape field-walk in compareShapesFields (dispatched off dynamic_cast alongside the CMeshBase branch; walks the default transform double-cover-aware + water-specific fields + textures via a new compareTexture helper handling CTextureFile/CTextureMultiFile/CTextureBlend recursively) that promotes the ~219 water-differ files from DIFF to FLOATEQ. Open items on the tail: the 3-byte DefaultRotQuat MSB residual (ref has sign bit set on x/y/z; ours has 0.0 — likely the same double-cover boundary class as CMesh's rot-quat sign flip §10i already documents); the water script's bEnableWaterSplash + bWaterUseSceneEnvMap{Above,Under} name-to-id mapping still unmapped in-code (initial guesses at 0x19..0x1b turned out to be the standard material's iBlendSrcFunc/iBlendDestFunc); setColorMapMat (2x3 affine, only fires when diffuse slot 7 is enabled — most corpus files don't use it).FLAREBUILD::buildFlareShape replicates CExportNel::buildFlare: nel_flare scripted-plugin PB2 (ClassId (0x4e913532, 0x3c2f2307), extends Sphere), 57 params declared without explicit id: tags → IDs equal MAXScript declaration order (same convention as pacs_prim / max_geometry_formats §K.1), corpus-verified via python PB2 dump against zo_cn_mairie.max. Color conversion follows the reference verbatim ((uint)(255.f*col.x), plain truncation — NOT the material_build path's rounded +0.5 convertColor). DefaultPos from decompMatrix(getLocalMatrix) translation row; rotation/scale not exposed on CFlareShape. Also caught in the same landing: main.cpp was calling shape->setDistMax(LOD_DIST_MAX appdata) UNIFORMLY on every returned shape, but the reference gates it inside the mesh branch AFTER the special-shape early returns (buildFlare/buildWaterShape/buildRemanence/buildWaveMakerShape/buildParticleSystem all return before line 466 of export_mesh.cpp) — flares/water/remanence keep the ctor default. Moved into the mesh/multi-lod branch of buildShapeForNode. Byte-identical on flare_preoder_item, zo_flare, zo_bt_mon_flare_couloir, gen_autel_flare04. Full sweep: 12/13 byte-identical; the 13th (gen_autel_flare04) is a reference-era class — byte-identical against the newer ecosystems/desert reference, differs by 1 byte from the older common/construction reference; not a decode bug.SPLINESHAPE + REMANENCEBUILD; 82/82 produced (trail subset 78/78 FLOATEQ-or-better). Interface border-weld LANDED §10z-quinze (IFACEBUILD::applyInterfaceToMeshBuild — MRM bookkeeping + packed-vertex align + both normal-correction variants, scene-normals dominant): byte-identical 122 → 139 at its landing; a ~75-file normals residual remains (~0.002–0.2 on border corners; suspected biped-box candidate-set gap in the scene-normals sweep).mod:02df2e3a, 25 uses). Triage after (1)–(3) close.NL3D::CMeshLightmapper (faithful calc_lm/calc_lm_plane/calc_lm_rt port) + CLightmapScene versioned-serial intermediate + versioned serial() on CMeshBuild/CMeshBaseBuild; producer pipeline_max_export_shape --lm-scene (light decode incl. the 0x2800/0x0a32 exclusion-list handles, MGF Part Q); consumer nel/tools/3d/shape_lightmapper (2_build tool); harness lightmap_corpus.py gated as pipeline_max_lightmap_corpus. Landing sweep: 108 scenes / 1012 single-mesh receivers — TGAs 445 byte-identical + 97 near + 615 repacked / 865 diff (median quantile 2.75 tail), logs 837/33, shapes 7+309 floateq + 93 repacked. First file validated byte-exact (TGA) / FLOATEQ (shape) vs Max 2010. DEFERRED: multi-lod receivers (~274, recipes already in the files), in-Max plugin + assimp producers. Previous architecture note: calc_lm/calc_lm_rt as a library on pure NeL types (pre-build CMeshBuild receivers, occluders + transparency, Max-fidelity lights, UV2 unwrap inside); a NeL versioned-serial scene-graph intermediate written at 1_export by all three producers (headless shape exporter, the in-Max plugin route updated to STOP running calc_lm, assimp mesh_export); the lightmapper itself is a pure 2_build step reading scene files + project options, feeding the existing lightmap_optimizer. Scene data in the file, process.py options stay build config. New engine surface: versioned serial() for CMeshBuild/CMeshBaseBuild.UVW Map (planar DONE §10z-douze; non-planar gated), Unwrap UVW (36 uses), FFD(box) (20 uses), Nel VertexTreePaintmapext198m3.dlm, 118 export nodes)MAPEXT library), the 4 sky-dome "stale cache" refusals were our Sphere-hemisphere defect (fixed → 118/118 applies), --compare-mapext-mask verification tier splits the bucket (113 verified + 3 diff; per-node matrix in defective_max_files §4.4 — 8 references even carry VALID UVs matching the cache), and the drop-in Max plugin (§10z-seize) makes legacy scenes view/collapse/export correctly in Max itself, validated 118/118 in live Max 2010. Also landed §10z-quinze: Morpher bsList (visage class-mismatch closed — non-empty bsList forces the CMeshMRM branch like the reference). Unwrap UVW / FFD(box) / UVW non-planar still open.PRIMMESH::buildParametricMesh's uvVerts output. Corpus-validated against ~/shape_export_dataset primuv_{box,cyl,sphere,plane}. Cylinder/Sphere seam handling uses a within-tri shift rule: consecutive corner u values are kept within 0.5 by adding/subtracting 1 (reproduces Max's u=1.0 or u=-0.125 duplicate UV verts at cap-fan seam-crossing tris without special-casing). Cylinder uses u = fract(0.75 + k/N) (0.75 offset places seam at +Y direction). Sphere uses u = k/segs, v = 1 - i/rows (INDEX-based, not by latitude z — Max maps UV rows uniformly regardless of the actual cos-spaced z coord).Shape-process anim surface closed §10k-bis (2026-07-09). Prior state only emitted texture-matrix tracks; a full NelExportAnimation for AUTOMATIC_ANIMATION nodes is now a separate pass over all scene objects (matching shape_export.ms for node in objects), covering:
NEL3D_APPDATA_LM_ANIMATED + LM_ANIMATED_LIGHT name → LightmapController.<name> BezierRGBA color tracks. Source: stuff/animated_light/fyros_city_animated_lights.max. 11/11 byte-identical vs ~/pipeline_export/common/objects/shape_anim and core4_data/objects.conerotor.anim / rotor.anim from taverne constructions; byte-identical.Biped IK: legs default (§10r); arm-pin default ON (§10s-quat small-D + §10s-cinq large-path dual-fold + §10s-sept moderate-miss FK hold when wrist miss < 6.5 cm — coup_fort_03 residual plant 0.51→0.23; static 2-knot / 3-knot large / course stay FK). §10s-six: limb pole [22..24] channel built (planted-only); apply behind PMB_BIPED_IK_POLE=1 after default-on regressions; offline GetIKActive confirms both-keys plant gate. Parked: FK-floor residual on some plants, foot-worst a1m coups, in-plant foot rotation (PODA hold rejected), blend-ramp — structural-exact with bounded in-between error is the shipping bar.
Standalone (§10g) is GREEN as of §10w (2026-07-08): budget retired to 0, 54/54 direct-ref field-exact under uninit-mask. Ligo (§10g-bis) is MOSTLY near-green with 8 open diffs (DIFF_BUDGET=8, down from 89 → 28 → 8 in one session); the two large open classes that occupied this section (cluster-containment defect + selection-order divergence) both closed via §10w. The remaining 8 are documented as a punch list for the next session — small and mostly precision-tier, not structural. Historical §10g/§10g-bis budgets and their un-root-caused states are kept in the design doc; this section is now the current open list.
TR_hall_reu_vitrine diff AND ~65 of the ligo path's 89 was the Edit Mesh 0x0130 created-verts chunk never being read (16-byte srcTag+Point3, mis-decoded as 0x0210 in the §10g decode note); any node whose base mesh got fully deleted and rebuilt in Edit Mesh evaluated to an empty world mesh, missing all cluster containment. Both cases green after the fix. The gen_decors_probe.ms Max 9 dump the user contributed (~/decors_probe/manifest.txt) also refuted the earlier "Mirror plane two stories down" hypothesis — the mirror offset=-24.9164, axis=Z, gizmo tz=1.78 produce exactly our current world-Z range.buildTreeOrder from ssc->scene()->rootNode(), matches MaxScript's $geometry/$lights/$helpers), which closed the sibling-parent-interleave cases (desert nb01..nb05 etc.); (b) the ligo XRef pre-pass now filters exactly like the maxscript — single-step source resolution (resolveXRefObject() recurses, so peel one step and stop at node->getReference(1)), reject nested-XRef sources, gate on {Geom, Helper, Light} superclass — which closed the 23 village-bundle-file class (fy_module_village_nb_01..05, zo_imm_village_nb_01..05, tr_village_nb_01..05, jungle foret-18..21_village_a..d, etc.). Kaetemi's Max 9 gen_ig_selorder_probe.ms run pinned the maxscript rule that made the fix possible.SunContribution/Light[] uninit-heap + STL-light-tie-order class is a legitimate tolerance not a bug; ligo is 71/537. Under VS2008/x87 the same code produces 130/537 byte-identical (59 files gain full byte-identity, x87 codegen zeros the uninit heap x64 leaves random). Standalone doesn't shift because its uninit fields are per-instance (every ig has some), whereas ligo has many small igs where the uninit fields don't get exercised. Byte-identity is a stretch goal, not the gating bar (matches the zone/skel precedents).~5-6 files, PS-instance clusterize AABBox borderline containment (fy_sheriff inst 23/26, fy_warschool inst 48/49/79, street inst 103/113/117, oasis_kami_auberge, ma_serre_village_a): the pss->getAABBox() corners re-boxed in world space (via CAABBox::transformAABBox — the reference plugin's own behavior, corpus-validated) land within CLUSTERPRECISION = 5mm of a cluster plane on rotated FX-instance bboxes; ours attach the PS to 2/3 clusters where reference attaches to 1/2. Confirmed VS2008/x87 build produces the SAME diffs → this isn't x87 codegen precision, it's a genuine borderline case. LOW real-content risk (extra links harden visibility culling rather than drop content), acceptable tolerance-tier candidate. Would need a Max-side dump of the shape's getAABBox() bytes to fully pin whether the reference used a smaller/different bbox or the CLUSTERPRECISION test itself.3 singleton unclassified files: taverne.ig inst 53 palme_type_C3_taverne Scale ≈3e-5 (may be a tighter epsilon needed on Scale — same magnitude class as the standalone POS_EPS); jungle-2.ig inst 0 ju_water_jungle_coude Pos differs by ≈15cm in X, ≈1.4cm in Y (real content difference — likely a modifier/controller decode gap for this water node); fy_cn_fortress_a_brandon.ig inst 38 Scale ≈1e-5 + inst 97/99 Clusters (1 vs 3) / (0 vs 2) — this one is the opposite direction from the PS borderline class (we find FEWER clusters, not more), suggesting a mesh evaluation issue on FY_CN_fortress_int_03/04 specifically. Not yet triaged individually.Whoever picks this up: the punch list is small enough now to close each item on its own. Retire by fixing diffs and re-tightening the budget — don't raise. When it hits 0, tighten to 0 in ligo_ig_corpus.py and re-grade the table row from MOSTLY to GREEN.
Session 1 (Sonnet-scale): .pacs_prim + .cmb. DONE 2026-07-08 (§10v/§10x) — pipeline_max_export_pacs_prim (493/493 byte-identical) and pipeline_max_export_cmb (16/16 direct files T1/T2, 13/16 T3 exact-or-tolerance, 3/16 documented open gaps). Extended 2026-07-09 (§10z-ter): 0x0210 correctly decoded as face-vertex remap (was misclassified as map-1 texture-vertex faces), EditablePoly + parametric-primitive extraction paths added to cmb, ligo tier gated (LIGO_DIFF_BUDGET=105, was informational — same "load-bearing Max export step" as its ig counterpart, feeding land_exporter → the shipped rbank). pacs_prim_corpus.py/cmb_corpus.py wired into ctest. Ligo T3 state: 32 byte-identical + 84 float-eq + 105 differ + 0 export failures across 221 measured igs. Open follow-ups listed in item A.1 above.
Session 2 (Opus-scale): shape M2 coverage frontier. DONE 2026-07-08 (§10y) — the M2 breadth landing that added CMeshMultiLod (+418 files), parametric-prim shapes (+37), Edit Mesh shared-library migration (defect fix + code consolidation), and --compare-lightmap-mask mode (1263 lightmap-bucketed → 982 verified + 281 diff). Corpus: exported 2141 → 2596 (+21% coverage). See §10y for the full session summary + open items handoff.
Session 3 (Opus-scale): skinned/MRM shapes. DONE across §10z-sept → §10z-quinze — coverage landed §10z-sept (653-file class closed), and the decode reached weight-exact + whole-file byte-identity through §10z-douze (1-based cross-links) / §10z-treize (world-space VBs + link-parent rule) / §10z-quinze (rigid one-link) — the planned differential dataset was never needed; corpus data + the reference shapes pinned every rule. The success metric was met: every skinned outcome is in an explained tier (byte-identical / FLOATEQ / MRM-structural — the last being the measured 2004-vs-now MRM builder divergence on float-noise inputs, ~550 files, informational).
Session 3b (Sonnet-scale, after Session 3): the M2 tail — CWaterShape CFlareShape CSegRemanence, interface meshes. Water DONE 2026-07-09 (§10z, FLOATEQ §10z-bis). Flare DONE 2026-07-09 (§10z-bis). Remanence DONE 2026-07-09 (§10z-dix) — shared SPLINESHAPE (max_geometry_formats Part N) + REMANENCEBUILD; 82/82 produced, trail subset 78/78 FLOATEQ-or-better. Interface border-weld DONE §10z-quinze (residual: the ~75-file border-normals class). Residual 14 mesh-eval CLOSED §10z-douze — the "reference yields no mesh .shape for plain splines" assumption was wrong: real references existed for all of them (open splines → EMPTY CMesh, closed splines → the corpus-reversed min-interior-angle XY ear-clip cap, shared SPLINEMESH, MGF §N.5); shape not-produced is down to 1 (the gauntlet). Full spline tessellation for cmb XRef Shape sources (~35) remains the shared follow-up.
Session 4 (Sonnet-scale, anytime): shape-process modifier tail — UVW Map non-planar, Unwrap UVW (36), FFD(box) (20). Nel VertexTreePaint DONE §10z-quat; Map Extender DONE §10z-quatorze (cache decode); UVW Map planar DONE §10z-douze. Remaining: non-planar UVW types (gated), Unwrap UVW, FFD(box).
Session 5 (Sonnet-scale): DONE (§10z-onze/douze), .veget DONE (§10z-clod). Whole Max-side export surface now has a headless tool. .clod.clod residual quality closed to 6 files in §10z-clod-bis (the shape-shared RVertex-merge/chaotic-sign tails) — not coverage.
Waterfall texture-matrix anims DONE §10k. Shape-process light-group + transform anims DONE §10k-bis (11/11 light + transform samples byte-identical).
pipeline-max-session-baseline memory note / §12).~/pipeline_export references with explicit tolerance tiers; per-file forked runs (parse→clean→build→disown; COFile, not CMemStream)..ms scripts + workspace config (~/ryzomcore_leveldesign/workspace) are the ground truth for WHAT gets exported from each source file and with which options — read them before the C++.