"Per-game audit of Fallout 4 compatibility — BA2, half-float verts, BGSM materials, M49 precombines (CSG)"
Install
npx skillscat add matiaszanolli/byroredux/claude-commands-audit-fo4 Install via the SkillsCat registry.
Fallout 4 Compatibility Audit
Deep audit of ByroRedux readiness for Fallout 4 content.
Architecture: Orchestrator. Each dimension runs as a Task agent (max 3 concurrent).
Read .claude/commands/_audit-common.md (layout, game data, dedup, finding format) and .claude/commands/_audit-severity.md. This file carries only FO4-specific context.
Scope: this skill owns FO4's data through the shared mechanisms. Defects in the mechanism itself go to the owner: BA2/CSG/BGSM/.uvd reader discipline → /audit-parsers; NIF block parsing → /audit-nif; ESM walker → /audit-esm; canonical-material invariants → /audit-nifal; Scaleform HUD (--hud, byroredux/src/scaleform_hud.rs, docs/smoke-tests/m48-7-fo4-hud.sh, AVM2/BGSCodeObj) → /audit-ui; runtime telemetry (.claude/audit-baselines/runtime/fo4-InstituteBioScience.tsv) → /audit-runtime — run it after any precombine / SCOL / spawn change (#5484's skip-set fix, 777351ec1, moved its draw / entity / BLAS rows; that recapture is still owed).
Everything below is shipped — audit as regression guard, never re-propose as pending: NIF BSVER 130+ incl. Next-Gen, BA2 v1/v7/v8, the BGSM/BGEM parser, SCOL/PKIN expansion, M49 CSG precombines, BSConnectPoint attach graph.
Game Context
| Aspect | State |
|---|---|
| NIF | BSVER 130 + Next-Gen patches (FALLOUT4..FO4_DLC_UPPER = 130..=139 carries the DLC trailing fields, crates/nif/src/version.rs) |
| Shader flags | typed u32 pair at BSVER 130 only; BSVER 131 (FO4_SHADER_GAP) has neither; BSVER ≥ 132 uses CRC arrays. Guard: bsver_shader_flag_band_tests (version.rs), gates carries_typed_shader_flags / carries_crc_shader_flags |
| Materials | PBR params live in external .bgsm/.bgem; the NIF carries a path stub. crates/bgsm/ parses; merge_external_material folds into ImportedMesh.material before translate_material |
| Precombines | crates/bsa/src/csg.rs → crates/nif/src/import/precombine.rs → byroredux/src/cell_loader/precombined.rs; per-REFR skip set = the CELL XCRI pair tail, never XPRI (#5484). Spec docs/engine/fo4-csg-format.md |
| Data | /mnt/data/SteamLibrary/steamapps/common/Fallout 4/Data/; bench --cell MedTekResearch01 (ROADMAP) |
Parameters / Setup
--focus <dimensions> (comma-separated, default all 5). Setup: parse $ARGUMENTS; mkdir -p /tmp/audit/fo4; gh issue list --repo matiaszanolli/ByroRedux --limit 200 --json number,title,state,labels > /tmp/audit/issues.json; confirm Fallout 4/Data/ exists (else note dimensions losing real-data validation).
Dimensions
Dimension 1: M49 Precombined Geometry — Decode + Spawn (highest FO4 risk)
Subagent: general-purpose
Paths: crates/nif/src/import/precombine.rs, byroredux/src/cell_loader/precombined.rs, byroredux/src/cell_loader/load.rs + exterior.rs + partial.rs, byroredux/src/cell_loader/spawn/{mesh_instance,precombined}.rs, byroredux/src/streaming/pre_parse.rs (worker pre-parse), crates/plugin/src/esm/cell/helpers.rs (XCRI decode), crates/bsa/src/csg.rs, crates/bsa/src/uvd.rs
First step: git log --since=<last report> --format='%h %cs %s' -- crates/nif/src/import/precombine.rs byroredux/src/cell_loader/precombined.rs crates/bsa/src/csg.rs
Guards (confirm live, not #[ignore]d/vacuous): precombine.rs tests decode_keeps_every_lod_band + lod_bands_skip_empty_bands_and_bound_the_read (#4234); csg.rs oversized_read_len_is_rejected_before_it_is_reserved (#3758 — read_psg must bound its length before Vec::with_capacity; reader discipline is /audit-parsers); precombined.rs tests resolve_precombine_owner_follows_form_id_mod_index, csg_paths_index_every_plugin_in_the_load_order, dedup_requires_both_a_cache_key_and_a_matching_length (#3510), oc_nif_paths_are_already_canonical_cache_keys; finish_partial_tests.rs::finish_partial_import_builds_precombine_geometry_entry; blend restore on both routes (#5101): precombined.rs::merge_precombine_materials_restores_blend_but_keeps_bgsm_flags + finish_partial_tests.rs::finish_partial_import_precombine_route_applies_the_material_merge; zero origin (#5228): exterior_apply_advances_precombine_at_zero_origin; #[ignore]d real-data dlc_rebake_of_a_master_owned_cell_decodes_from_the_dlc_csg + exterior_precombine_instances_are_world_absolute. The runtime baseline is an interior, so no telemetry gate sees exterior placement.
Checklist (what the guards cannot see):
- Stride / transform —
psg_vertex_stride(vertex_desc)vs theBSPackedGeomObjectlayout (off-by-N shifts every vertex silently; cross-check the spec doc).into_imported_meshapplies Z-up→Y-up + instance transform once.cell_originmust beVec3::ZEROon both routes (#5228, fixed f3e1bba62): exterior_oc.nifinstance transforms are world-absolute (same frame as the exterior REFRDATApositions), so addingcell_grid_to_world_yupplaces every tile at ≈2× its world position — the "else precombines stack at origin" rationale was the disproved #1222 premise. - LOD bands (#4234) — the three bands are disjoint sub-meshes whose union is the model (12,096/12,096
BSMeshLODTriShape); every populated band is decoded. Picking one band deletes geometry (12.45% ofMeshesExtra.ba2's baked triangles) and is not z-fighting. Distance LOD may drop tail bands, never pick one. An empty band's offset is untrusted. - Which CSG blob — the
_oc.niffilename keys off the cell's owning plugin (form-id mod byte; DLC bakes under<plugin>.esm\), but the *geometry blob* is chosen by each object'sBSPackedGeomObject::filename_hash→csg_paths_by_name_hash(#2369: DLCs re-bake ~460 master-owned cells into their own blob). A blob no loaded plugin answers to, or a missing CSG, must fail closed to per-REFR (0 spawns), never read another plugin's PSG space or panic. - REFR de-dup gate (#1188 / #5484, fixed 777351ec1) — the skip set is the XCRI pair tail, (remapped REFR form id, bake hash) pairs on
CellData::absorbed_ref_bakes(crates/plugin/src/esm/cell/helpers.rs); XPRI is the disjoint previs participant list, validated and never stored. Pre-fix the two were inverted (every baked STAT/SCOL drawn twice, ~9.9k XPRI statics deleted); census 959,238 XCRI refs, 100% STAT/SCOL, XCRI ∩ XPRI = 0 — which also settles #2699's furniture question.effective_absorbed_refs(bakes, baked_hashes)(shared by interiorload.rsand exteriorexterior.rs) suppresses a REFR only when its own bake drew geometry, so one missing_oc.nifun-skips only its refs (no holes, no double-draw with SCOL expansion). Guards:effective_absorbed_refs_un_skips_only_the_missing_bakes_refs;parse_cell_xcri_pairs_remap_and_keep_their_bake_hash+parse_wrld_exterior_cell_captures_precombined_xcri_xpri(XPRI populates nothing) incrates/plugin/src/esm/cell/tests/. - Texturing — each mesh takes its material from the owning shape's shader/alpha properties (
collect_precombine_geom_refs+precombine_material_from_shape), then the BGSM merge; no REFR overlay on this path (pre_merge_materialsempty by design, #4290). Regression = untextured/checker precombines. - Opaque-architecture blend guard — on this path only,
merge_precombine_materials(called by bothPrecombinedSpawnJoband the streaming drain inpartial.rs) keeps the BGSM merge's two_sided/decal/alpha_test/texture flags but restores the pre-merge alpha-blend triple (has_alpha,src/dst_blend_mode): FO4 authors the "Standard" blend identically on lab glass and opaque Institute metal, so forwarding the mergedhas_alphamakes wallsMATERIAL_KIND_GLASS(see-through, mirror-hazy). - Archive chain precedence (#3637) —
--bsa/--materials-ba2chains resolve last-listed-wins (a DLC's re-baked_oc.nifmust not be shadowed; 1,871 DLC mesh entries were shadowed pre-fix per the #3637 title). Provider rebuilds now share opened handles (#5288, 5402b2530:Archive::open_shared, a process-wide registry validated by file length + mtime) and skip the shadow-count walk for a reused handle — precedence is unchanged, only that diagnostic count is. Guard:byroredux/src/asset_provider/tests/archive_precedence.rs(real-data,#[ignore]d — run it). - Upload once per object (#3510) —
CachedNifImport::geometry_dedupmaps every instance to its object's representative; keying the cache on the flattened sub-mesh index reintroduces one upload + BLAS per instance (1.37x VRAM, 4.6x worst tile). - Exterior streaming —
PrecombinedSpawnJobyields between hashes / meshes / BLAS batches, andPrecombinedPlacement(spawn/precombined.rs) between geometry groups, keeping the placement root + submesh numbering across yields; the synchronousspawn_precombined_mesheswrapper must stay behaviourally identical. The cell-stream worker pre-decodes each cell'sprecombine_oc_nif_pathsviadecode_precombine_csg(per-pluginCsgHandleCache) and the drain caches ageometry_only_cachedentry under the same key so the job only spawns — the two routes must agree on owner resolution, cache key, fail-closed fallback and the blend restore (divergence = different spawn counts per route). Regression guard (#5522, fixed 2ca9bc313):lookup_precombine_cacheseparates Hit /KnownFailure/ Unseen, so the worker's negative cache entry (Some(None): missing file, parse error, contained panic) is a terminal miss, never a main-thread re-parse outsidecatch_unwind; pinned bynegative_registry_entry_is_a_known_failure_not_unseen. .uvdprevis — envelope only (parse_uvd_header, incl.exterior_cell_grid; PVS payload uncracked, no occlusion consumer, #3810). Verify nothing pretends to consume the payload.
Output:/tmp/audit/fo4/dim_1.md
Dimension 2: BGSM / BGEM → ImportedMaterial (merge + roles)
Subagent: general-purpose
Paths: crates/bgsm/src/, byroredux/src/asset_provider/material/{merge,mod,provider}.rs, byroredux/src/cell_loader/spawn/mesh_instance.rs (REFR overlay), byroredux/src/asset_provider/tests/{bgsm_merge,material_flags}.rs
First step: git log --since=<last report> --format='%h %cs %s' -- crates/bgsm byroredux/src/asset_provider/material
Guards: bgsm_merge.rs (bgsm_merge_forwards_alpha_blend_mode, bgsm_blend_to_gamebryo_is_identity_narrowing, the neutral-roughness pair, bgem_glass_forwards_authored_optics_and_overlay_roles); material_flags.rs::bgsm_metalness_*; crates/bgsm/src/template.rs resolve_breaks_*_cycle (#1148). Parser discipline for crates/bgsm is /audit-parsers.
Checklist:
- Narrowed signature —
merge_external_materialtakes&mut ImportedMaterial, never&mut ImportedMesh, and is the only exported fn inmerge.rs(testmerge_external_material_is_the_only_exported_fn_in_this_file); a widened signature is a NIFAL boundary violation. It runs beforetranslate_material. - Roles, not slots — BGSM texture paths and REFR/TXST overlays land in
MaterialTextureSet(22 named roles +decals: [T; 4],crates/nif/src/import/types.rs); FO4 wire-slot numbers must not survive past the import boundary. Overlay output is carried as named roles, not wire slots (#4434). Texture-set slot 7 is the_s.ddssmooth-spec map and routes to the smooth-spec role, not Specular (#4424: the BC5 map fed to the specular-colour multiply tinted every FO4 highlight warm); the gloss-sampler channel semantics are still an open question — do not guess. - Metalness from saturation, not luminance (#1476) — legacy spec-glossiness BGSMs (
pbr == false):bgsm_metalness=(max-min)/maxofspecular_color(white spec → 0). Luminance for the non-PBR branch makes vanilla concrete read chrome; luminance is correct only forpbr == true. - Authored enable bits gate their blocks —
specular_enabled == falsezeroes specular and derives no metalness / conductor tint in either branch (#4654, #4836;bgsm_specular_disabled_*); the envmap fill requires the chain'senvironment_mappingbit (#4428;*_skips_envmap_fill_when_env_mapping_disabled); a dead NIF texture path yields to a resolvable BGSM-chain path (#4636;dead_nif_texture_path_yields_to_resolvable_bgsm_chain_path). Reading a disabled block's values is the regression. - smoothness→roughness exactly once at the merge (
(1 - smoothness)clamped to the near-mirror floor); flag any downstream re-inversion. - Neutral-roughness fallback —
smoothness == 1.0with no resolvable gloss map → neutral 0.5 (NEAR_MIRROR_NEUTRAL_ROUGHNESS): #3639 at the merge (authored-nowhere; fires on thespecular_enabledarm only — a spec-off BGSM keeps the NIF-side roughness, #5012 / e80f7e854; must not fire when a template parent suppliessmooth_spec— pinned bybgsm_merge_keeps_the_floor_when_the_gloss_map_comes_from_the_template_parent, #5234 / 460c55a41), #3905 at spawn viamaterial_translate::resolve_unresolved_gloss_neutral_roughness(authored-but-unresolvable; gated on the shader'sglossMapIndex != 0u). The arms cover disjoint populations — collapsing them regresses. Regression guard (#5230, fixed 9f0a8a7cc): the spawn pass infers "hit the floor" fromroughness <= 0.04, which the glass classifier's forced mirror pane (0.04, metalness 1.0) also hits;classify_glass_into_material_with_provenancenow reports the forced mirror out throughtranslate_material_with_provenanceandresolve_unresolved_gloss_neutral_roughnessskips it (a_forced_mirror_pane_is_exempt_even_sitting_at_the_floor,a_bgsm_mirror_pane_keeps_its_mirror_through_the_spawn_pass_gateinmaterial_translate.rs). Both spawn sites must feed the provenance flag. - Blend factors pass through (#1823) — BGSM/BGEM
src_blend/dst_blendare already Gamebryo-native (Standard (6,7), Additive (6,0), Multiplicative (4,1)); any 0↔1 swap (or agl_to_gamebryo_blend) corrupts Additive/Multiplicative while leaving Standard untouched. - BGEM merge —
merge_bgem_armdeliberately leaves metalness/roughness as NaN soresolve_pbr's classifier runs; effect data merges into the NIF payload (#4425). Glass isglass_enabled-driven and must not misclassify opaque architecture with a stuck flag. - Lookup —
.bgsm/.bgem/.matdispatch on content magic, not extension (#758); last-listed-wins across--materials-ba2(#3637); template resolve is cycle-aware and depth-limited (DEPTH_LIMIT = 16).
Output:/tmp/audit/fo4/dim_2.md
Dimension 3: NIF BSVER 130 Geometry, Shader Flags, Collision (FO4 slice)
Subagent: legacy-specialist
Paths: crates/nif/src/blocks/tri_shape/bs_tri_shape.rs, crates/nif/src/import/mesh/{bs_tri_shape,bs_geometry,skin}.rs, crates/nif/src/blocks/shader/lighting.rs, crates/nif/src/import/material/dedicated_shader.rs, crates/nif/src/import/collision/shape.rs
First step: git log --since=<last report> --format='%h %cs %s' -- crates/nif/src/import/material crates/nif/src/blocks/shader
Guards: fo4_shader_flag_tests.rs, alpha_flag_tests.rs (crates/nif/src/import/material/); bsver_shader_flag_band_tests; parse_rate_fo4_all_meshes (Dim 5). Parser mechanics are /audit-nif; the canonical handoff (single translate_material boundary, resolve-once metalness/roughness, glass classified after resolve_pbr) is /audit-nifal — file there.
Checklist:
VF_FULL_PRECISIONdefault-half unless set; binary16 decode incl. denormals/NaN.BSSubIndexTriShapesegments walked (actors lean on it); skinned indices/weights honour the packed layout. Regression guard (#5389, fixed cf9dec258): a per-vertex bone index>=the skin's own bone count declines the whole weight set (bind pose), never clamps —decline_unbounded_packed_indices(skin.rs) covers the inline arrays, the SSE global-buffer payload and classicNiSkinData; pinned bybs_tri_shape_skin_with_out_of_range_bone_index_declines/bs_tri_shape_skin_last_bone_index_is_in_range(bs_tri_shape_partition_remap_tests.rs). Vanilla census: 0 out-of-range over 23,099 FO4 skinned meshes (largest list 141/144). The FO4BsSkinInstancearm is pinned too (#5524, fixed 656e2bdc3):bs_skin_instance_out_of_range_bone_index_declines/bs_skin_instance_last_bone_index_is_in_range, and the decline warning is arm-neutral. Inline tangents whenVF_TANGENTS | VF_NORMALS(shared with Skyrim SE) — consolidating with theNiBinaryExtraDatapath is a regression of #795/#796.- Render-affecting flags consumed, not just parsed (#1592) —
dedicated_shader.rsORsModel_Space_Normals(F4SF1 bit 12) andAlpha_Test(F4SF2 bit 25) intoMaterialInfo, gated bycarries_typed_shader_flags(BSVER 130 only).Glow_Map(F4SF2 bit 6) is not gated: the(Fallout4, 2)arm inslot_role.rsroutes slot 2 to Emissive regardless — closed docs-only (#4430, 1a12670ae; routing unchanged on purpose — census: gating would strip 116 genuine_g.ddsglow maps, not gating masks 20 with a_d.dds; the BGSMglowmapbool is parsed, never read). The rule still needs an FO4 source; do not re-file or guess. These are lower priority than the BGSM merge (vanilla is BGSM-backed; only inline/loose/modded NIFs exercise them). - Skin/Hair tint
BSLightingShaderTypevariants sample different slots than the default path — the per-type map must match whattriangle.fragreads (#563). A BGSM material must never fall back to Lambert (FO4's inverse of the FNV regression); bareBSShaderPropertyBaseOnlystubs route throughMaterialInfo(#1244). - Parsed-then-dropped is FO4's recurring finding class (
texture_clamp_modeon 7.9% of lit materials #3507; the greyscale→palette enable bit): every decoded FO4 shader/BGSM field must reach anImportedMaterialsink and the canonicalMaterial, with one default across the three material tiers (#3515). The palette row ("Color Remapping Index") has two carriers (#5497): the precombine per-instancegrayscale_to_palette_scaleis stamped byapply_instance_palette_rowand restored across the BGSM merge inmerge_precombine_materials(guardsmerge_precombine_materials_keeps_the_instance_palette_row,apply_instance_palette_row_stamps_finite_rows_only); the REFR-side MSWPCNAM(color_remap_indexinmswp.rs) is applied as the palette row for a swapped placement, stamped on the raw-tierImportedMaterialbeforetranslate_material(0b2ffed89, #5497). - Collision —
BhkMultiSphereShapeandBhkConvexListShapetranslate toCollisionShapeinimport/collision/shape.rs(twodowncast_refarms); an arm reverting to "unsupported + drop" removes power-armor / settlement / debris collision. Packed-Havok (BhkSystemBinary) collision is blocked on the blob decode (PHYSAL,/audit-physics); FO4 architecture uses synthesized ghost trimesh colliders. The precombine collision sibling is<cell_formid:08x>_physics.nif(not_precomb.nif, which appears in no archive) — forward scope.
Output:/tmp/audit/fo4/dim_3.md
Dimension 4: ESM Architecture Records + Cell Expansion
Scope split with /audit-esm: it owns the parser as a parser (GRUP walk, SubReader accounting, FormID remap); this dimension owns FO4's data through it. Shared-mechanism defects → /audit-esm.
Subagent: general-purpose
Paths: crates/plugin/src/esm/records/{scol,movs,pkin,mswp,parse}.rs, crates/plugin/src/esm/records/actor/npc.rs, crates/plugin/src/equip.rs, crates/plugin/src/esm/cell/{mod,support,walkers,wrld}.rs, byroredux/src/cell_loader/{refr,references/,spawn.rs,nif_import_registry.rs}
First step: git log --since=<last report> --format='%h %cs %s' -- crates/plugin/src/esm/records crates/plugin/src/esm/cell byroredux/src/cell_loader/refr.rs
Guards: cell_loader/scol_expansion_tests.rs, pkin_expansion_tests.rs, attach_points_spawn_tests.rs, refr_texture_overlay_tests.rs; the real-data ESM harness (#819) before any parse-rate claim.
Checklist:
- SCOL/PKIN expand into per-instance synthetic REFRs with composed (parent × child) transform; recursion bounded by
MAX_PKIN_DEPTH = 4(vanilla has zero nesting; the cap guards modded cross-recursion); SCOL-of-SCOL has its own gate. Known open, don't re-file: #5523 (SCOL CM path taken verbatim fromMODL, 114 stale DLCRobot MODLs; the expansion gate ismodel_pathemptiness only, so a set-but-absent CM is anextract_meshmiss and renders nothing — the documented absent-CM fallback does not exist). Regression guards: SCOL/PKINFLTRis the CK object-window filter path, a zstring decoded withread_zstringintofilter: Stringand never remapped (#5493, fixed 136048a95 — pre-fix 2,843 false malformed-FormID warns across the FO4 DLCs buried real remap failures; the*_expansion_tests.rspins use real string payloads plus a raw-under-remap test);NPC_.TPTAdecodes ontoNpcRecord::template_actors: [u32; 13](xEditwbTemplateFlagsorder, 0 = use theTPLTdefault) andwalk_inherited_records(crates/plugin/src/equip.rs) takes the per-flag entry for that flag's edge when non-zero (#5498, fixed 99c22a8db; 763 Fallout4.esm records resolved the wrong template before). MOVS is parse-only (physics runtime unwired). TheTXSTgroup arm is inrecords/parse.rsand its decode inesm/cell/support.rs— anunreachable_patternswarning there meansb"TXST"matches earlier than intended. The FO4-architecture maps must surface incategories()(records/index.rs). - TXST DODT (
DecalData) has zero consumers — 303 vanilla payloads parse ontoTextureSet.decal_dataand are never read (grep decal_datafinds only the parser and its tests); DNAM is consumed (TXST_FLAG_MODEL_SPACE_NORMALSinrefr.rs). Known and deferred — #3638 closed documentation-only (a decal-rendering feature build, not a parse bug); forward scope, don't re-file. - PKIN
CNAMis a template CELL, not a content base — fixed d63131c57 (#5231): every vanilla PKIN CNAM resolves to a CELL (the census spans Fallout4.esm + four DLCs) and vanilla FO4 places 0 PKIN REFRs (the CK bakes contents into ordinary REFRs) — an FO4-only premise: Starfield places 97,981, so the skip is live there (open #5489,/audit-starfield). The expander now skips CELL-typed CNAMs with a warn (instancing a template CELL's references under the outer transform is unimplemented) and returnsNonefor an all-CELL list so the outer REFR reaches the default miss accounting; base-record children remain the mod-authored shape. Guards:expand_pkin_cell_typed_cnam_is_an_explicit_miss,expand_pkin_skips_the_cell_child_but_fans_out_base_children,#[ignore]dfo4_pkin_cnam_targets_are_template_cells(crates/plugin/tests/parse_real_esm.rs). - Authored actor state — ACHR "Starts Unconscious" (
0x2000, FO4/FO76/SF only) decodes ontoPlacedRef::starts_unconsciousand spawns the actor dormant (#5017, 3c08cfe50);XRGDcorpse poses decode ontoPlacedRef::ragdoll_pose(260afc33f) with no consumer — open #5015, forward scope. - ARMA female→male biped fallback is Skyrim-only (open sourcing question, not a bug) —
resolve_armor_meshes(crates/plugin/src/equip.rs) falls back to the male biped model for a female wearer only whengame == GameKind::Skyrim(#5510, fixed d678dc578: the Skyrim CK documents the rule; 172 of Skyrim.esm's 766 addons are male-only). FO4 deliberately keeps the strict reading because the FO4 CK page documents no such rule and none is sourced locally — a FO4 male-only addon renders nothing on a female wearer. Needs an FO4 source (xEdit/CK/engine) before any change; do not extend the fallback by analogy and do not file it as a defect. - BSConnectPoint — the import lifts
BSConnectPoint::Parents/ChildrenintoAttachPoints/ChildAttachConnections(crates/core/src/ecs/components/attach_points.rs); spawn must materialise them (modular weapons / power-armor frames otherwise spawn base-receiver only).
Output:/tmp/audit/fo4/dim_4.md
Dimension 5: Archives + Real-Data Validation + Forward Scope
Subagent: general-purpose
Paths: crates/bsa/src/ba2.rs, crates/nif/tests/parse_real_nifs.rs, crates/nif/examples/nif_stats.rs, ROADMAP.md, docs/feature-matrix.md
First step: BYROREDUX_FO4_DATA=… cargo test -p byroredux-nif --test parse_real_nifs -- --ignored fo4
Checklist:
- BA2 (FO4 slice; reader discipline is
/audit-parsers) — version dispatch is an exhaustivematch(v1/v7/v8 FO4 → zlib; v2/v3 Starfield →/audit-starfieldDim 1); unknown majors bail atopen()and a cascadingif v == 1 … elsewith a silent v1 fallback is the regression (#811). GNRL byte-exact; DX10 reconstructs the DDS header from width/height/dxgi_format/mips (BC1/BC3/BC5/BC7, mip0 → mip_last). - Parse rate —
parse_rate_fo4_all_meshescovers all 8 mesh-bearing archives (base pair + six DLCMain.ba2;DLCUltraHighResolutionis textures-only), 235,082 NIFs incl..bto/.btr(widened 2026-08-29 by #3466; re-measured 2026-10-09, 8/8 archives), 100.00% clean. Report the measured number, not ROADMAP's. Regression guard (#5232, fixed 3970c8d19):open_all_mesh_archives/open_ba2_by_name(crates/nif/tests/common/mod.rs) panic on a present-but-unopenable BA2 (Noneis reserved for an absent file) andrun_all_meshes_gateassertswalked > 0; a reversion turns the gates into green no-ops — still confirm the archive count actually walked, not just the pass. DX10 records withchunk_hdr_len != 24are now a hardInvalidData(#5008, cf2a36920; vanilla census: all 24). - Trace
import_nif_sceneon a workshop item, a creature (deathclaw / super mutant), a power-armor frame (heavy skinning + BSConnectPoint) and a modular weapon: mesh count,material_path, skinned vs rigid, connect points. - Forward scope (do not file as blockers): precombine
_physics.nifcollision and packed-Havok decode,.uvdPVS payload / occlusion consumer, MOVS physics runtime, DecalData consumer (above),XRGDpose consumer (#5015), PKIN template-CELL instancing (skipped with a warn since #5231),BSBehaviorGraphExtraDataparse-only (verify nothing pretends to drive it), FaceGen NIF truncation tail, deeper cell coverage (LIGH power state / CONT leveled items / NPC_ face morph).
Output:/tmp/audit/fo4/dim_5.md
Phase 3: Merge
- Read
/tmp/audit/fo4/dim_*.md; combine intodocs/audits/AUDIT_FO4_<TODAY>.md:- Executive Summary — measured parse rate; what is shipped vs pending (above).
- Dimension Findings — grouped by severity per dimension.
- BGSM Consumption Table — BGSM field × merged-into-
ImportedMaterialbymerge_external_material/ surfaced-on-canonical-Material. Texture columns name theMaterialTextureSetrole, never an FO4 slot number. - Forward Scope Chain — precombine collision /
.uvdpayload → MOVS physics → DecalData → deeper REFR/LIGH/CONT/NPC_ coverage.
- Remove cross-dimension duplicates.
Suggest: /audit-publish docs/audits/AUDIT_FO4_<TODAY>.md
(label every finding game:fo4 + legacy-compat, plus its own domain label.)