-
Notifications
You must be signed in to change notification settings - Fork 113
pipeline_max_coverage
title: pipeline_max coverage vs the build_gamedata Max export surface description: Gap analysis of the headless .max exporters against every Max-side export build_gamedata needs, with sized work packages for follow-up sessions published: true date: 2026-07-10T00:00:00.000Z tags: editor: markdown dateCreated: 2026-07-08T00:00:00.000Z
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.
-
DONE, mostly (§10v landed, §10x closed most follow-ups, §10z-ter closed the face-vertex remap + ligo extraction gaps)..cmb— NelExportCollisionpipeline_max_export_cmb. §10z-ter landings: (a) the face-index remap chunk the §10x handoff flagged was0x0210itself — 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 legacyTOPO_FACEMAP_CHUNK0x2780 (per-face per-corner index rewrites,(uint32 faceIdx, uint32 applyMask, uint32 v[3])), applied in the sharedEDITMESH::applyEditsbetween moves and face-deletes;FY_hall_reunionface-index match 247/248 → 248/248,Zo_bt_hall_Reunion_vitrineface 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.mscallsNelExportCollisionper brick exactly likeNelExportInstanceGroup, so its fidelity matters exactly as much as the standalone tier's.FY_hall_reunion 16 created-vert positions + Zo_bt_hall_Reunion_vitrine 25 vert offsetsCLOSED §10z-quinze — both byte-identical: the 0x0130 created-vert record withsrcTag != -1is 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 —SPLINEMESHtessellation 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). -
DONE (§10v) —.pacs_prim— NelExportPACSPrimitivespipeline_max_export_pacs_prim, 493/493 byte-identical. -
DONE, mostly (§10z-onze, counts corrected + gated §10z-douze) —.veget— NelExportVegetablepipeline_max_export_veget, 126 exported: 25 byte-identical + 97 size-eq + 4 size-diff (Ju_DebrisFeuillesA class open). -
DONE (§10z-clod + §10z-clod-bis) —.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):
-
Skinned + MRM meshes (COVERAGE DONE §10z-sept (653-fileCMeshMRM/CMeshMRMSkinned)skinned-physiqueskip class closed; quality still DIFF). SharedPHYSIQUESKINinpipeline_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 ongetReference()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 (87INoderefs onfy_hof_armor01_pantabottes, named, 16 NULL slots counted), and the primaryboneRefis~index(one's-complement) into it, validated end-to-end (boneRef −77→idx 76→Bip01 L Thighetc., anatomically exact). The bone-name array source is therefore solved too. Remaining: ~4 % deformable cross-link boneRefs (small positive, not~index) and theBip01root-link reassignment fromConvertToRigid(TRUE)— both still need the differential dataset.PMB_SKIN_DUMPnow 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 (negk=~stored, posk=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. -
CMeshMultiLod / coarse meshesDONE §10y — LOD_NAME_COUNT-driven build with per-slot CMeshGeom or CMeshMRMGeom, per-slot DistMax/BlendLength/BlendIn/BlendOut/CoarseMesh flags from slave appdata; 418 files added to the exported set (+63 float-eq, +274 lightmap-verified, +79 differ). One residual class: multi-lod slaves whose materials aren't all in the parent's list produce ref-larger material counts (e.g.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'sNEL3D_APPDATA_LOD_COARSE_MESHscan. -
Water shapes (DONE §10z, promoted to FLOATEQ §10z-bis —CWaterShape) (221 files)WATERBUILD::buildWaterShapereplicatesCExportNel::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 fromdecompMatrix(getLocalMatrix). Same-session byte-identity rounds 1+2 landed on top of the coverage path: (r1)CTextureBlendday/night envmap pairing when slot 2's TEXMAP is present +CWaterShapestream 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 viaPMB_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 acompareWaterShapefield-walk incompareShapesFields(dispatched off dynamic_cast alongside the CMeshBase branch; walks the default transform double-cover-aware + water-specific fields + textures via a newcompareTexturehelper handling CTextureFile/CTextureMultiFile/CTextureBlend recursively) that promotes the ~219 water-differ files from DIFF to FLOATEQ. Open items on the tail: the 3-byteDefaultRotQuatMSB 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'sbEnableWaterSplash+bWaterUseSceneEnvMap{Above,Under}name-to-id mapping still unmapped in-code (initial guesses at 0x19..0x1b turned out to be the standard material'siBlendSrcFunc/iBlendDestFunc);setColorMapMat(2x3 affine, only fires when diffuse slot 7 is enabled — most corpus files don't use it). -
CFlareShape (13 files)DONE §10z-bis —FLAREBUILD::buildFlareShapereplicatesCExportNel::buildFlare: nel_flare scripted-plugin PB2 (ClassId (0x4e913532, 0x3c2f2307), extends Sphere), 57 params declared without explicitid: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.5convertColor). DefaultPos fromdecompMatrix(getLocalMatrix)translation row; rotation/scale not exposed on CFlareShape. Also caught in the same landing:main.cppwas callingshape->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 ofbuildShapeForNode. 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 newerecosystems/desertreference, differs by 1 byte from the oldercommon/constructionreference; not a decode bug. -
CSegRemanence (82)DONE §10z-dix — sharedSPLINESHAPE+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). -
Waterfall texture-matrix material animsDONE §10k — landed pre-shape-session; 642 byte-identical .anim outputs in the M2 shape sweep. -
Lightmap-diff tail (282 files) — the calc-lm-mask compare says these have structural divergence beyond just the lightmap-added fields. Candidates: multi-lod material merging (above), missing UV generation on parametric prims, Unwrap UVW modifier effects (§10y's
mod:02df2e3a, 25 uses). Triage after (1)–(3) close. -
Standalone lightmapper — IMPLEMENTED for the headless route (PMD §11-lm-bis, 2026-07-10): engine library
NL3D::CMeshLightmapper(faithful calc_lm/calc_lm_plane/calc_lm_rt port) +CLightmapSceneversioned-serial intermediate + versionedserial()on CMeshBuild/CMeshBaseBuild; producerpipeline_max_export_shape --lm-scene(light decode incl. the 0x2800/0x0a32 exclusion-list handles, MGF Part Q); consumernel/tools/3d/shape_lightmapper(2_build tool); harnesslightmap_corpus.pygated aspipeline_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. -
Modifier tail —
UVW Map(planar DONE §10z-douze; non-planar gated),Unwrap UVW(36 uses),FFD(box)(20 uses),DONE §10z-quat, andNel VertexTreePaintMap Extender (FULLY CLOSED §10z-quatorze/quinze/seize — cache decode (sharedmapext198m3.dlm, 118 export nodes)MAPEXTlibrary), the 4 sky-dome "stale cache" refusals were our Sphere-hemisphere defect (fixed → 118/118 applies),--compare-mapext-maskverification 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. -
UV mapping-coord generation for parametric primitivesDONE §10z-sept (Box/Plane) + §10z-onze (Cylinder + Sphere). All four primitive UVs now emit per-corner UV verts throughPRIMMESH::buildParametricMesh'suvVertsoutput. Corpus-validated against~/shape_export_datasetprimuv_{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:
-
Animated light groups — Omni lights with
NEL3D_APPDATA_LM_ANIMATED+LM_ANIMATED_LIGHTname →LightmapController.<name>BezierRGBA color tracks. Source:stuff/animated_light/fyros_city_animated_lights.max. 11/11 byte-identical vs~/pipeline_export/common/objects/shape_animandcore4_data/objects. -
Node transform tracks (scale/rotquat/pos) — e.g.
conerotor.anim/rotor.animfrom taverne constructions; byte-identical. - Material texture-matrix tracks — already §10k; re-validated 18/18 waterfall sample.
- Morph tracks — path wired (same as anim exporter); corpus signal is 1 shape_anim file.
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.
-
Cluster-membership / vertex-in-volume containment.CLOSED §10w — the shared defect that surfaced as the standalone'sTR_hall_reu_vitrinediff 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 as0x0210in 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. Thegen_decors_probe.msMax 9 dump the user contributed (~/decors_probe/manifest.txt) also refuted the earlier "Mirror plane two stories down" hypothesis — the mirroroffset=-24.9164,axis=Z,gizmo tz=1.78produce exactly our current world-Z range. -
Ligo selection-order divergence.CLOSED §10w — closed by two orthogonal fixes: (a) depth-first scene-tree walk for the per-category iteration (buildTreeOrderfromssc->scene()->rootNode(), matches MaxScript's$geometry/$lights/$helpers), which closed the sibling-parent-interleave cases (desertnb01..nb05etc.); (b) the ligo XRef pre-pass now filters exactly like the maxscript — single-step source resolution (resolveXRefObject()recurses, so peel one step and stop atnode->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, jungleforet-18..21_village_a..d, etc.). Kaetemi's Max 9gen_ig_selorder_probe.msrun pinned the maxscript rule that made the fix possible. -
Byte-identity backlog. Under x64/SSE the standalone remains 0/54 truly byte-identical — the reference exporter's
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). -
Remaining 8 ligo diffs (the current open list).
-
~5-6 files, PS-instance clusterize AABBox borderline containment(fy_sheriffinst 23/26,fy_warschoolinst 48/49/79,streetinst 103/113/117,oasis_kami_auberge,ma_serre_village_a): thepss->getAABBox()corners re-boxed in world space (viaCAABBox::transformAABBox— the reference plugin's own behavior, corpus-validated) land withinCLUSTERPRECISION = 5mmof 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'sgetAABBox()bytes to fully pin whether the reference used a smaller/different bbox or the CLUSTERPRECISION test itself. -
3 singleton unclassified files:taverne.iginst 53palme_type_C3_taverneScale ≈3e-5 (may be a tighter epsilon needed on Scale — same magnitude class as the standalone POS_EPS);jungle-2.iginst 0ju_water_jungle_coudePos 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.iginst 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 onFY_CN_fortress_int_03/04specifically. 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).
- Load the session baseline first: all pipeline_max sources + the design doc (see the
pipeline-max-session-baselinememory note / §12). - Corpus-gate EVERYTHING: T1/T2 byte-identity must stay green over the full .max corpus for any parser change; new exporters get their own T3 vs
~/pipeline_exportreferences with explicit tolerance tiers; per-file forked runs (parse→clean→build→disown; COFile, not CMemStream). - The
.msscripts + 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++. - Emission-form preservation, typed-chunk order preservation, and the §11 hazard list apply unchanged.
- Don't rebuild binaries mid-sweep; don't trust per-file probes over full-corpus A/B (§10r method notes).