Skip to content

pipeline_max_coverage

Jan Boon edited this page Jul 9, 2026 · 34 revisions

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-09T00:00:00.000Z tags: editor: markdown dateCreated: 2026-07-08T00:00:00.000Z

Written 2026-07-08 (authoring session), updated 2026-07-09 across shape-coverage rounds through §10s-cinq (arm large-path weapon plants: stored-pA gate + dual-fold; full A/B 23↑/3↓ max reg +0.05, coup_fort_03 0.93→0.51) and §10k-bis (shape-process full animation). Prior same-day: §10s-quat static-plant, §10s-ter midFlip, §10z-onze Cylinder/Sphere UVs, Physique, remanence, veget. 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.

How the surface was enumerated

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.cppnel_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.

Coverage table

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 — non-biped byte-identical; biped structural-exact, in-between IK bounded (median ~0.068, §10r/§10s-cinq); shape-process anims closed §10k-bis; arm-pin default ON (§10s-quat small-D + §10s-cinq large-path dual-fold; full A/B 23↑/3↓ max reg +0.05, mean −0.0012, over-tol 594→588; coup_fort_03 0.93→0.51); remaining gap: residual strike ~0.4–0.6, many a1m/a2m coups still FK-gated, in-plant foot rot, blend ramps
NelExportShapeEx shape .shape 11251 pipeline_max_export_shape MOSTLY (coverage ~99.6%) — M1–M2 + Physique (§10z-sept) + CSegRemanence (§10z-dix): shared SPLINESHAPE decodes BezierShape compact knots (Part N); remanence builds piece endpoints + appdata + single material → CSegRemanenceShape. Corpus 3577 exported: 14 byte-identical + 1144 float-eq + 1035 differ + 118 mapext + 982 lightmap-VERIFIED + 283 lightmap-diff + 1 no-ref. 13 refs not produced — skip classes: mesh-eval=14, skinned-maxverts=1, water=1 (remanence gone). Trail-only: 78/78 remanence FLOATEQ-or-better. Remaining open: skinned DIFF quality, UVW Map (272) / Unwrap (38) / FFD (20), Cylinder/Sphere UV, residual mesh-eval, .veget/.clod
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 — direct tier (gated): 16/16 files export, 10/16 igs exact + 3/16 x87-tier + 3/16 differ (DIRECT_DIFF_BUDGET=3); FY_hall_reunion now 248/248 face-index match after §10z-ter's face-vertex-remap decode + exact material distribution vs the reference, remaining residual is 16 created-vert positional offset alone.
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=105, --gate-t3 fails on regression past that count — same shape as ligo_ig_corpus.py's DIFF_BUDGET, per §10z-ter). §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: 32 byte-identical + 84 float-eq + 105 differ + 0 export failures across 221 measured igs — up from 11 + 15 + 22 + 6 = 54 (§10v baseline), 30 + 83 + 41 + 16 = 170 (§10x).
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 corpus roundtrip clean; T3 505 exported: 92 byte-identical + 397 size-eq (float-noise byte-diff, same structure) + 16 size-diff (1 class: Ju_DebrisFeuillesA 544 vs 424, unclassified), 0 export failures, 0 references not produced. Shared code with the shape exporter (scene_lib/mesh_eval/material_build/mesh_build compiled into both). §10z-onze.
NelExportLodCharacter clodbank .clod 105 MISSING

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 — ready for .clod reuse), 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), 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).

The gaps, sized

A. The remaining small formats (breadth completion)

.pacs_prim and .cmb landed 2026-07-08 (§10v) — .veget and .clod close the rest of the SURFACE.

  1. .cmb — NelExportCollision DONE, mostly (§10v landed, §10x closed most follow-ups, §10z-ter closed the face-vertex remap + ligo extraction gaps). pipeline_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. Remaining open items: FY_hall_reunion 16 created-vert positions (mod-context TM confirmed near-identity via PMB_DEBUG_MESH — 0x2510 is NOT the cause; probable reference-era class where the max source was edited post-reference and the base verts alone match); Zo_bt_hall_Reunion_vitrine 25 vert offsets (same class at larger scale); Zo_bt_Hall_Conseil vertex offset (still open on its own separate terms since §10v); 35 XRef sources resolving to Max SplineShape (class (0x0000000a, 0) superclass 0x40 = Shape — would need spline tessellation); 12 Boolean compound objects on Matis buildings (class (0x51db4f2f, 0x1c596b1a) superclass 0x10 = GeomObject, named "Boolean" — computes mesh from two operand objects via union/intersection/subtraction, no obvious quick path without SDK).
  2. .pacs_prim — NelExportPACSPrimitives DONE (§10v) — pipeline_max_export_pacs_prim, 493/493 byte-identical.
  3. .veget — NelExportVegetable (126 refs; 817 veget-related source .max). Serializer: NL3D::CVegetableShape (nel/3d/vegetable_shape.h) — mesh + bend-weight painting (vertex colors/UV1?) + appdata params (bend mode, freq…). Reference impl: nel_mesh_lib/export_vegetable.cpp (buildVegetableShape). Plain-mesh scale; the vertex-paint channel decode is the only likely new ground.
  4. .clod — NelExportLodCharacter (105 refs; 111 *_lod.max sources under stuff/lod_actors/). Serializer: NL3D::CLodCharacterShapeBuild (nel/3d/lod_character_shape.h) — positions, tris, UVs, normals + per-vertex skin weights to a bone-name table. Reference impl: nel_mesh_lib/export_lod_character.cpp. NOTE: this needs the SKIN modifier decode (bone weights) — the same decode the shape-MRM work needs; whichever session lands first should put the skin-weight reader in pipeline_max_export_common for the other to reuse. (The .swt exporter already reads skeleton-weight data — check what it shares before writing anything new.)

B. Shape depth (the flagship remaining work — Opus-sized)

NelExportShapeEx beyond plain CMesh, in rough dependency order (post-§10z state):

  1. Skinned + MRM meshes (CMeshMRM / CMeshMRMSkinned) COVERAGE DONE §10z-sept (653-file 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.
  2. CMeshMultiLod / coarse meshes DONE §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's NEL3D_APPDATA_LOD_COARSE_MESH scan.
  3. Water shapes (CWaterShape) (221 files) DONE §10z, promoted to FLOATEQ §10z-bisWATERBUILD::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_DUMPfBumpU/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).
  4. CFlareShape (13 files) DONE §10z-bisFLAREBUILD::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.
  5. CSegRemanence (82) DONE §10z-dix — shared SPLINESHAPE + REMANENCEBUILD; 82/82 produced (trail subset 78/78 FLOATEQ-or-better). Interface (9) was demoted from hard-skip in §10z-sept (border-weld still missing as quality).
  6. Waterfall texture-matrix material anims DONE §10k — landed pre-shape-session; 642 byte-identical .anim outputs in the M2 shape sweep.
  7. Lightmap-diff tail (280 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.
  8. Modifier tailUVW Map (272 uses in the current corpus warn breakdown, standard-plugin planar/cylindrical/spherical/box projection, §10i handoff's biggest UV-differ class), Unwrap UVW (36 uses, user-authored overlays), FFD(box) (20 uses, Free-Form Deformer lattice), and Nel VertexTreePaint (110 uses, our own plugin) DONE §10z-quat — the mod-app payload is 0x2500 → 0x2510 (mod-context TM 52 B) + 0x2511 (ctx bbox 24 B) + 0x2512 container { 0x0100 int32 version + 0x0120 (int32 numColors + numColors × COLORREF) } + 0x2513 (trailing 4 B), format sourced from plugin_max/nel_vertex_tree_paint/vertex_tree_paint.cpp:569-620 (SaveLocalData) and corpus-verified via PMB_SKIN_DUMP against ge_mission_maduk.max; applied as map channel 0 (vertex color) on the current SEvalMesh — Nel VertexTreePaint is a REPLACING modifier (not additive) matching the reference's Paint.cpp semantics, mod:40c7005e warn class gone from corpus reports with no regression. UVW Map / Unwrap UVW / FFD(box) still open — class ids catalogued in §10y.
  9. UV mapping-coord generation for parametric primitives DONE §10z-sept (Box/Plane) + §10z-onze (Cylinder + Sphere). All four primitive UVs now emit per-corner UV verts through 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).

C. Anim depth

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:

  1. Animated light groups — Omni lights with 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.
  2. Node transform tracks (scale/rotquat/pos) — e.g. conerotor.anim / rotor.anim from taverne constructions; byte-identical.
  3. Material texture-matrix tracks — already §10k; re-validated 18/18 waterfall sample.
  4. 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: multi-key stored-pA weapon plants dual-fold; static 2-knot / 3-knot large / course stay FK). Parked: residual strike accuracy, a1m/a2m coup variants still FK-gated, in-plant foot rotation (PODA hold rejected), blend-ramp — structural-exact with bounded in-between error is the shipping bar.

D. Ig export — open correctness items

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.

  1. Cluster-membership / vertex-in-volume containment. CLOSED §10w — the shared defect that surfaced as the standalone's 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.
  2. Ligo selection-order divergence. CLOSED §10w — closed by two orthogonal fixes: (a) depth-first scene-tree walk for the per-category iteration (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.
  3. 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).
  4. Remaining 8 ligo diffs (the current open list).
    • ~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.

Suggested session plans

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. The big one, still open (654 files, the biggest remaining not-produced class). Approach exactly like §10i M1: replicate CExportNel::buildShape's MRM branch against the real NeL builders, corpus-bucket the outcomes (byte-exact / float-eq / era-tier / differ), and expect several probe rounds. Reuse the skin decode from session 2 if it landed. Success metric: the character/fauna shape corpus classified into explained tiers with zero unexplained structural fails — byte-identity is the stretch goal, not the bar (see the zone precedent). The exporter-side dispatch (CMeshMRMSkinned::isCompatible + LOD_MRM path in buildShapeForNode) is already wired; this session is entirely (a) the Physique/Skin modifier decode and (b) measuring the MRM builder's 2004-vs-now divergence.

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 demoted from hard-skip §10z-sept (border-weld still quality). EditablePoly extraction §10z closed most residual mesh-eval; remaining 14 are non-mesh Shape-superclass objects (SplineShape/Line/Rectangle without USE_REMANENCE — reference also yields no mesh .shape) plus a few unidentified classes. Full spline tessellation (not just endpoints) is the shared follow-up for cmb XRef Shape sources (~35) if those need mesh output.

Session 4 (Sonnet-scale, anytime): shape-process modifier tail — UVW Map (272 uses), Unwrap UVW (36), FFD(box) (20). Each is a modifier-app decode + a UV/geometry rewrite pass; all are self-contained. Nel VertexTreePaint DONE §10z-quat — the largest single unknown-modifier count closed via the in-tree plugin's own format (see B.8 above). UVW Map is now the biggest remaining unhandled modifier by usage; expect its bbox "fit" to be baked into the 0x2510 mod-context TM at authoring time (unlike Mirror's identity default, but VERIFY against a corpus case per §10i handoff). UV mapping-coord generation for parametric prims (Box/Cylinder/Sphere/Plane) rides along here.

Session 5 (Sonnet-scale): .veget, then .clod if the skin decode from Session 3 is available. .veget first (no skinning). For .clod, put the skin-weight decode in pipeline_max_export_common — Session 3 will have already done this.

Waterfall texture-matrix anims DONE §10k. Shape-process light-group + transform anims DONE §10k-bis (11/11 light + transform samples byte-identical).

Non-negotiables for every session (from §12, restated)

  • Load the session baseline first: all pipeline_max sources + the design doc (see the pipeline-max-session-baseline memory 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_export references with explicit tolerance tiers; per-file forked runs (parse→clean→build→disown; COFile, not CMemStream).
  • The .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++.
  • 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).

Clone this wiki locally