---
description: "Per-game audit of Fallout 4 compatibility — BA2, half-float verts, BGSM materials, M49 precombines (CSG)"
argument-hint: "--focus <dimensions>"
---

# 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`; ragdoll articulation incl. the FO4 `bhkRagdollSystem` decode (`crates/nif/src/blocks/collision/np_ragdoll.rs`) and `docs/smoke-tests/fo4-ragdoll.sh` → `/audit-physics`; the Havok 2014 behavior-graph reader (`crates/hkx/`) → `/audit-parsers`, its runtime (`byroredux/src/asset_provider/{actor_graph,object_graph,archetype_idle}.rs`, `byroredux/src/systems/graph_locomotion.rs`) → `/audit-gameplay` Dim 5; the LSCR loading model (FO4 TNAM → TRNS, `byroredux/src/systems/loading_model.rs`, `docs/smoke-tests/p6-loading-model.sh`) → `/audit-gameplay` Dim 6; terrain, ground cover (the Commonwealth is arid since 1a2330361) and WTHR → `/audit-exterior`; 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 the `BSPackedGeomObject` layout (off-by-N shifts every vertex silently; cross-check the spec doc). `into_imported_mesh` applies Z-up→Y-up + instance transform once. `cell_origin` must be `Vec3::ZERO` on both routes (#5228, fixed f3e1bba62): exterior `_oc.nif` instance transforms are **world-absolute** (same frame as the exterior REFR `DATA` positions), so adding `cell_grid_to_world_yup` places 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% of `MeshesExtra.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.nif` *filename* 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's `BSPackedGeomObject::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. Known open: `resolve_precombine_owner` reads the FormID slot byte as a load-order position (#6088).
- **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 interior `load.rs` — whose `InteriorCellApplyJob` now drives both the sync and the budgeted load, #5589 / a4496c79b — and exterior `exterior.rs`) suppresses a REFR only when *its own* bake drew geometry, so one missing `_oc.nif` un-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) in `crates/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_materials` empty by design, #4290). Regression = untextured/checker precombines.
- **Opaque-architecture blend guard** — on this path only, `merge_precombine_materials` (called by both `PrecombinedSpawnJob` and the streaming drain in `partial.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 merged `has_alpha` makes walls `MATERIAL_KIND_GLASS` (see-through, mirror-hazy).
- **Archive chain precedence (#3637)** — `--bsa` / `--materials-ba2` chains resolve last-listed-wins (a DLC's re-baked `_oc.nif` must 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. Loose `Data\` files (the `--esm` plugin's directory) are appended after every archive in the texture, mesh and material pools, so a loose override wins (#6200, 48e422a71; vanilla installs carry none, so no baseline moved). Guards: `byroredux/src/asset_provider/tests/archive_precedence.rs` (real-data, `#[ignore]`d — run it), `byroredux/src/asset_provider/tests/loose_override.rs`.
- **Upload once per object (#3510)** — `CachedNifImport::geometry_dedup` maps 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** — `PrecombinedSpawnJob` yields between hashes / meshes / BLAS batches, and `PrecombinedPlacement` (`spawn/precombined.rs`) between geometry groups, keeping the placement root + submesh numbering across yields; the synchronous `spawn_precombined_meshes` wrapper must stay behaviourally identical. The cell-stream worker pre-decodes each cell's `precombine_oc_nif_paths` via `decode_precombine_csg` (per-plugin `CsgHandleCache`) and the drain caches a `geometry_only_cached` entry 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_cache` separates 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 outside `catch_unwind`; pinned by `negative_registry_entry_is_a_known_failure_not_unseen`. `NifImportRegistry`'s LRU evicts per-cell `_oc.nif` entries first and warns once on a shared-model eviction (6e1853d24); still open: the 2,048 cap is below the FO4 working set (#6114), one geometry copy per instance in precombine entries (#6116), and the worker pre-parses absorbed refs the main thread skips (#6097). `effective_absorbed_refs` is built once per apply job (67e724c3e, #6127), not per slice.
- **`.uvd` previs** — 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) + `resolve_reports_per_file_parse_diagnostics` (#5010, 746e0858e); `crates/bgsm/tests/parse_all.rs` real-data sweeps (hardened #5011, e8676b097) and `asset_provider/tests/bgsm_diagnostics.rs`. Parser discipline for `crates/bgsm` is `/audit-parsers`.
**Checklist**:
- **Narrowed signature** — `merge_external_material` takes `&mut ImportedMaterial`, never `&mut ImportedMesh`, and is the only exported fn in `merge.rs` (test `merge_external_material_is_the_only_exported_fn_in_this_file`); a widened signature is a NIFAL boundary violation. It runs **before** `translate_material`. `merge_bgsm_arm` is split into group helpers (`apply_bgsm_texture_slots` / `apply_bgsm_scalars` / `apply_bgsm_alpha_and_addressing`, #5735 / a6cef30a5) and size-pinned by `merge_bgsm_arm_stays_within_its_size_pin`; the helpers stay private.
- **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.dds` smooth-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)/max` of `specular_color` (white spec → 0). Luminance for the non-PBR branch makes vanilla concrete read chrome; luminance is correct only for `pbr == true`.
- **Authored enable bits gate their blocks** — `specular_enabled == false` zeroes specular and derives no metalness / conductor tint in either branch (#4654, #4836; `bgsm_specular_disabled_*`); the envmap fill requires the chain's `environment_mapping` bit (#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.0` with no resolvable gloss map → neutral 0.5 (`NEAR_MIRROR_NEUTRAL_ROUGHNESS`): #3639 at the merge (authored-nowhere; fires on the `specular_enabled` arm only — a spec-off BGSM keeps the NIF-side roughness, #5012 / e80f7e854; must not fire when a template parent supplies `smooth_spec` — pinned by `bgsm_merge_keeps_the_floor_when_the_gloss_map_comes_from_the_template_parent`, #5234 / 460c55a41), #3905 at spawn via `material_translate::resolve_unresolved_gloss_neutral_roughness` (authored-but-unresolvable; gated on the shader's `glossMapIndex != 0u`). The arms cover disjoint populations — collapsing them regresses. Regression guard (#5230, fixed 9f0a8a7cc): the spawn pass infers "hit the floor" from `roughness <= 0.04`, which the glass classifier's forced mirror pane (0.04, metalness 1.0) also hits; `classify_glass_into_material_with_provenance` now reports the forced mirror out through `translate_material_with_provenance` and `resolve_unresolved_gloss_neutral_roughness` skips it (`a_forced_mirror_pane_is_exempt_even_sitting_at_the_floor`, `a_bgsm_mirror_pane_keeps_its_mirror_through_the_spawn_pass_gate` in `material_translate.rs`). Both spawn sites must feed the provenance flag.
- **Blend factors pass through** (#1823) — BGSM/BGEM `src_blend`/`dst_blend` are already Gamebryo-native (Standard (6,7), Additive (6,0), Multiplicative (4,1)); any 0↔1 swap (or a `gl_to_gamebryo_blend`) corrupts Additive/Multiplicative while leaving Standard untouched.
- **BGEM merge** — `merge_bgem_arm` deliberately leaves metalness/roughness as NaN so `resolve_pbr`'s classifier runs; effect data merges into the NIF payload (#4425). Glass is `glass_enabled`-driven and must not misclassify opaque architecture with a stuck flag.
- **Lookup** — `.bgsm`/`.bgem`/`.mat` dispatch on content magic, not extension (#758); last-listed-wins across `--materials-ba2` (#3637); template resolve is cycle-aware and depth-limited (`DEPTH_LIMIT = 16`) and surfaces parse diagnostics (lossy strings, unconsumed bytes, version past the ceiling) for every file in the chain through `TemplateResolver::parsed`, not only the recovery arm (#5010).
- **MNAM-only landscape TXSTs** — 32 of FO4's 128 LTEXs point at a TXST that authors only `MNAM`; terrain resolves each layer through `LandscapeLayerTextures` (`byroredux/src/cell_loader/terrain.rs`) and `MaterialProvider::fill_texture_set_from_bgsm` fills still-empty roles first-wins along the template chain, so authored TX slots keep precedence (#6199, e62e258bd). A layer reading only TX00/TX01/TX07 is the regression (untextured grass / rubble / cliffs).
**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_PRECISION` default-half unless set; binary16 decode incl. denormals/NaN. `BSSubIndexTriShape` segments 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 classic `NiSkinData`; pinned by `bs_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 FO4 `BsSkinInstance` arm 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 when `VF_TANGENTS | VF_NORMALS` (shared with Skyrim SE) — consolidating with the `NiBinaryExtraData` path is a regression of #795/#796.
- **Render-affecting flags consumed, not just parsed (#1592)** — `dedicated_shader.rs` ORs `Model_Space_Normals` (F4SF1 bit 12) and `Alpha_Test` (F4SF2 bit 25) into `MaterialInfo`, gated by `carries_typed_shader_flags` (BSVER 130 only). `Glow_Map` (F4SF2 bit 6) is not gated: the `(Fallout4, 2)` arm in `slot_role.rs` routes slot 2 to Emissive regardless — closed docs-only (#4430, 1a12670ae; routing unchanged on purpose — census: gating would strip 116 genuine `_g.dds` glow maps, not gating masks 20 with a `_d.dds`; the BGSM `glowmap` bool 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 `BSLightingShaderType` variants sample different slots than the default path — the per-type map must match what `triangle.frag` reads (#563). A BGSM material must never fall back to Lambert (FO4's inverse of the FNV regression); bare `BSShaderPropertyBaseOnly` stubs route through `MaterialInfo` (#1244).
- **Parsed-then-dropped is FO4's recurring finding class** (`texture_clamp_mode` on 7.9% of lit materials #3507; the greyscale→palette enable bit): every decoded FO4 shader/BGSM field must reach an `ImportedMaterial` sink and the canonical `Material`, with one default across the three material tiers (#3515). The palette row ("Color Remapping Index") has two carriers (#5497): the precombine per-instance `grayscale_to_palette_scale` is stamped by `apply_instance_palette_row` and restored across the BGSM merge in `merge_precombine_materials` (guards `merge_precombine_materials_keeps_the_instance_palette_row`, `apply_instance_palette_row_stamps_finite_rows_only`); the REFR-side MSWP `CNAM` (`color_remap_index` in `mswp.rs`) is applied as the palette row for a swapped placement, stamped on the raw-tier `ImportedMaterial` before `translate_material` (0b2ffed89, #5497) — live only since f81e6e722 (#5755): both swap arms (per-shape, eager XATO) match through `mswp_source_matches` (`refr.rs`: `normalize_material_path` both sides, case-insensitive, the CK `*` wildcard on 127 vanilla BNAMs), because a bare BNAM never equalled the NIF's `Materials\…` / build-path / `Data\materials\…` spelling verbatim and no unbaked XMSP placement in Fallout4.esm swapped. MSWP `FNAM` is the CK tree folder (`MaterialSwapRecord::tree_folder`), never a path filter (#5756 — as one it rejected every swap of 806 records). Guards: `mswp_source_matches_canonicalizes_and_honours_wildcards`, `build_overlay_xmsp_fnam_tree_folder_does_not_gate_the_swap` (`refr_texture_overlay_tests.rs`), `#[ignore]`d `fo4_mswp_canvas_swap_matches_the_picture_frame_nif`. Starfield's REFR `XLMS` → LMSW swaps (#5757, `17bd40f4f`; `layered_material_swap_entries` / `canonical_swap_target`) ride this same per-shape `material_swaps` arm, so an FO4 swap-path regression also breaks Starfield swaps (`/audit-starfield` Dim 4).
- **Collision** — `BhkMultiSphereShape` and `BhkConvexListShape` translate to `CollisionShape` in `import/collision/shape.rs` (two `downcast_ref` arms); an arm reverting to "unsupported + drop" removes power-armor / settlement / debris collision. Packed-Havok (`BhkSystemBinary`) collision is blocked on the `hknpCompressedMeshShapeData` decode (PHYSAL, `/audit-physics`) — the skeleton `bhkRagdollSystem` blob is a different payload and *is* decoded (e1bd40bcf); 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. Regression guard (#5523, fixed a10b5d578): `scol_cm_path` (`byroredux/src/cell_loader/refr.rs`) answers which combined mesh a SCOL draws — the authored `MODL` when it resolves, else the derived *meshes\scol\<owner>\cm<local id>.nif* (114 DLCRobot SCOLs author dev-plugin MODLs) — and `expand_scol_placements_if` expands the SCOL into its parts when it answers `None` (13 vanilla placements name a CM that ships nowhere); the static spawn (`byroredux/src/cell_loader/references/synth_child.rs`) and the expansion gate in `references/mod.rs` read the same answer, so they cannot disagree. A SCOL nested under a PKIN keeps the emptiness-only gate. Pinned by `scol_cm_path_prefers_the_authored_modl_then_the_derived_path` + `a_scol_with_an_absent_cm_expands_but_a_present_one_does_not` (`scol_expansion_tests.rs`). Also: SCOL/PKIN `FLTR` is the CK object-window filter path, a zstring decoded with `read_zstring` into `filter: String` and never remapped (#5493, fixed 136048a95 — pre-fix 2,843 false malformed-FormID warns across the FO4 DLCs buried real remap failures; the `*_expansion_tests.rs` pins use real string payloads plus a raw-under-remap test); `NPC_.TPTA` decodes onto `NpcRecord::template_actors: [u32; 13]` (xEdit `wbTemplateFlags` order, 0 = use the `TPLT` default) and `walk_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). The `TXST` group arm is in `records/parse.rs` and its decode in `esm/cell/support.rs` — an `unreachable_patterns` warning there means `b"TXST"` matches earlier than intended. The FO4-architecture maps must surface in `categories()` (`records/index.rs`).
- **TXST DODT (`DecalData`) has zero consumers** — 303 vanilla payloads parse onto `TextureSet.decal_data` and are never read (`grep decal_data` finds only the parser and its tests); DNAM *is* consumed (`TXST_FLAG_MODEL_SPACE_NORMALS` in `refr.rs`). Known and deferred — #3638 closed documentation-only (a decal-rendering feature build, not a parse bug); forward scope, don't re-file.
- **PKIN `CNAM` is 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 (fixed #5489: the expander now instances a CELL-typed CNAM's template-cell references under the outer transform, outer PKIN entry first, via the O(1) `EsmCellIndex::cell_by_form_id`) and returns `None` only when nothing was placeable so the outer REFR reaches the default miss accounting; base-record children remain the mod-authored shape. Guards: `expand_pkin_empty_template_cell_places_nothing`, `expand_pkin_empty_template_leaves_the_base_children_fanning_out`, `expand_pkin_instances_its_template_cell_under_the_outer_transform`, the `#[ignore]`d `starfield_cydonia_pack_ins_instance_their_template_cells`, `#[ignore]`d `fo4_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 onto `PlacedRef::starts_unconscious` and spawns the actor dormant (#5017, 3c08cfe50); `XRGD` corpse poses decode onto `PlacedRef::ragdoll_pose` (260afc33f); the consumer is `byroredux/src/ragdoll_pose.rs`, gated per rig by body-name set. FO4 (#5015 closed into follow-ups; e1bd40bcf + 28e1f413e): the skeleton's `bhkRagdollSystem` (hknpRagdollData) blob decodes into the classic ragdoll/limited-hinge records, and XRGD entries map to bodies by each body's `pose_bone_id`, not entry order; `is_fo4_posable_ragdoll` admits the human, feral-ghoul and super-mutant sets (979 of 994 corpses resolve, per 28e1f413e) and refuses every other creature/robot rig (#6065 open). The layout is corpus-derived, not sourced — widen the gate only when `scripts/xrgd-fo4-rigs.py` passes a rig on both checks, never on a count match. Bodies take the blob's own material / motion-property rows by *motion* index, Skyrim's constants only as the no-rows fallback (#6066, fixed 019b087e3; pinned by `a_body_reads_its_material_and_damping_rows_by_motion_index` in `np_ragdoll.rs` + `#[ignore]`d `crates/nif/tests/np_ragdoll_corpus.rs`). Convex/compound shapes and breakable joints decode (fc4a15104); the 67 bind-pose pivot disagreements are a recorded data property — the body-frame reading stays, the affected rigs hold live (#6067, closed 0d775dc61; `docs/engine/physal.md`). Live wiring (fc2b32d9e + 7c6e6c3e1, #6070 closed): FO4 RACE `ANAM` skeleton paths are read, `template_from_imported` keeps `pose_bone_id`, `spawn_skeleton_unit` installs the one-body template only on a skeleton with no decoded one, and activation frees the `FallbackActorCollider` stand-in capsule (left behind it threw every limb; stand-up restores it). Regression guard: `docs/smoke-tests/fo4-ragdoll.sh` (all bodies/joints live, no recovery, `BYRO_VALIDATION=1`) + `#[ignore]`d `byroredux/src/ragdoll_installed_tests.rs` (`fo4_bind_pose_ragdoll_holds_together_in_free_fall`, six rigs; `fo4_xrgd_posed_ghoul_corpse_holds_together_in_free_fall`). Not established, no tracking issue: a corpse whose REFR carries a large X/Y tilt (466 of 971 posed human/ghoul corpses) — the pose composition is unknown; the smoke covers only |X|,|Y| < 0.25 rad. Articulation defects → `/audit-physics`.
- **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 when `game == 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.
- **CELL `XCCM` is game-dependent (#5463, 3b28dca71)** — FO4 (like TES5) authors it as 'Sky/Weather from Region' [REGN] (xEdit `wbDefinitionsFO4.pas`), decoded onto `CellData::sky_region`; only TES4/FO3/FNV decode it onto `climate_override` (a CLMT). No consumer for `sky_region` yet (forward scope). A FO4 cell reading `climate_override` from XCCM is the regression.
- **BSConnectPoint** — the import lifts `BSConnectPoint::Parents/Children` into `AttachPoints` / `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 exhaustive `match` (v1/v7/v8 FO4 → zlib; v2/v3 Starfield → `/audit-starfield` Dim 1); unknown majors bail at `open()` and a cascading `if v == 1 … else` with 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_meshes` covers all 8 mesh-bearing archives (base pair + six DLC `Main.ba2`; `DLCUltraHighResolution` is 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 (`None` is reserved for an absent file) and `run_all_meshes_gate` asserts `walked > 0`; a reversion turns the gates into green no-ops — still confirm the archive count actually walked, not just the pass. DX10 records with `chunk_hdr_len != 24` are now a hard `InvalidData` (#5008, cf2a36920; vanilla census: all 24).
- Trace `import_nif_scene` on 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.nif` collision and packed-Havok decode, `.uvd` PVS payload / occlusion consumer, MOVS physics runtime, DecalData consumer (above), XRGD poses for the unverified FO4 creature/robot rigs (#6065) and for tilted-reference corpses, 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

1. Read `/tmp/audit/fo4/dim_*.md`; combine into `docs/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-`ImportedMaterial` by `merge_external_material` / surfaced-on-canonical-`Material`. Texture columns name the `MaterialTextureSet` role, never an FO4 slot number.
   - **Forward Scope Chain** — precombine collision / `.uvd` payload → MOVS physics → DecalData → deeper REFR/LIGH/CONT/NPC_ coverage.
2. 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.)
