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).
See .claude/commands/_audit-common.md for project layout, game data locations, key reference docs, methodology, deduplication rules, and finding format. See .claude/commands/_audit-severity.md for severity. This file only carries FO4-specific context — do not duplicate the common map here.
What's Landed (audit as regression guards, not blockers)
FO4 is one of the more complete game paths. Treat each item below as shipped — the audit job is to confirm it still holds, not to re-propose it.
- NIF — BSVER 130 + Next-Gen patch range parses. Half-float vertices, BSTriShape, BSGeometry, inline tangents, BSSubIndexTriShape.
- BA2 — exhaustive version match
{1, 2, 3, 7, 8}(GNRL + DX10); unknown majors bail atopen(). - BGSM/BGEM materials — full external-material parser crate
crates/bgsm/, merged into the mesh before NIFAL translate. - ESM architecture records — SCOL / MOVS / PKIN / TXST parsed; SCOL/PKIN placements expand into individual statics.
- M49 precombined geometry (CSG) — closed Session 45, 2026-06-02 (#1351 / #1188 Stage A).
.csgreader, NIF precombine decode, cell-loader spawn, per-tier LOD selection, owning-REFR texture slot routing all shipped. ROADMAP language describing M49 as "blocked / no spec" is stale — the format was cracked from first principles, spec written, pipeline landed. - FO4 metalness from spec chromaticity (#1476) — legacy spec-glossiness BGSMs derive metalness from spec-color saturation, not luminance (luminance made all vanilla concrete read chrome).
Still open (legitimate forward scope): _precomb.nif collision, .uvd occlusion volumes, MOVS physics runtime, FaceGen NIF truncation tail, deeper cell coverage (LIGH power state / CONT leveled items / NPC_ face morph), Havok behavior-graph driving.
Game Context
| Aspect | State |
|--------------|-------|
| NIF format | BSVER 130 + Next-Gen patches; FALLOUT4..FO4_DLC_UPPER (130..=139) carries DLC trailing fields (crates/nif/src/version.rs) |
| BA2 format | Exhaustive match over {1, 2, 3, 7, 8} in crates/bsa/src/ba2.rs (consts BA2_V_FO4=1, BA2_V_FO4_NEXT_GEN_TEX=7, BA2_V_FO4_NEXT_GEN_MESH=8); GNRL + DX10 |
| ESM records | SCOL / MOVS / PKIN / TXST in crates/plugin/src/esm/records/; SCOL/PKIN expand in byroredux/src/cell_loader/refr.rs |
| Precombines | M49 CSG pipeline landed — crates/bsa/src/csg.rs → crates/nif/src/import/precombine.rs → byroredux/src/cell_loader/precombined.rs |
| Parse rate | 100.00% clean on both vanilla mesh archives per #1457 (parse_rate_fo4_all_meshes, 2026-06-14); the FaceGen truncation tail the 2026-06-02 ROADMAP snapshot recorded is gone. ROADMAP compat-matrix now also reads 100.00% (159 866/159 866, #1593) — still re-run the harness before citing a parse rate. |
| Rendering | Interior cells render end-to-end (MedTekResearch01 bench, ~21k entities). BGSM/BGEM merged; precombined entities spawned interior + exterior. |
| Reference | /mnt/data/SteamLibrary/steamapps/common/Fallout 4/Data/ |
| Bench | --cell MedTekResearch01 (see ROADMAP for the full --bsa/--textures-ba2/--materials-ba2 invocation) |
Known FO4 Specifics
- Half-float vertices —
VF_FULL_PRECISIONcontrols f32 vs u16 binary16 positions/normals; FO4 defaults to half. - FO4 shader flags —
BSLightingShaderPropertyflags are a u32 pair (shader_flags_1 + shader_flags_2), not the single mask of FO3/FNV/Skyrim. - DLC trailing fields — subsurface, rimlight, backlight, fresnel, wetness in the
FALLOUT4..FO4_DLC_UPPERBSVER band. - External materials — PBR-ish params live in
.bgsm/.bgemfiles; the NIF carries only the path. The block parser returns a material-reference stub when BSVER ≥ 155 and Name is a BGSM/BGEM path;crates/bgsm/parses the file. - Inline per-vertex tangents (#795 / #796) — when
VF_TANGENTS | VF_NORMALSare both set, FO4+ BSTriShape ships tangents inline in the packed-vertex blob, NOT via a separateNiBinaryExtraData(the Skyrim/FO3/FNV path, #786). Consolidating the two into one is a regression of #795/#796. - Architecture records — SCOL (prefab collections), MOVS (movable statics), PKIN (packins), TXST (texture sets w/ DODT decal-data + DNAM flags).
Parameters (from $ARGUMENTS)
--focus <dimensions>: Comma-separated dimension numbers (e.g.,1,3). Default: all 9.
Phase 1: Setup
- Parse
$ARGUMENTS. mkdir -p /tmp/audit/fo4.- Dedup baseline:
gh issue list --repo matiaszanolli/ByroRedux --limit 200 --json number,title,state,labels > /tmp/audit/issues.json. - Confirm
Fallout 4/Data/exists; if not, note which dimensions lose real-data validation.
Phase 2: Launch Dimension Agents (parallel)
Dimensions are ordered by FO4 risk: the precombine pipeline, BGSM material translation, and the BA2 reader are the hot, recently-churned areas.
Dimension 1: M49 Precombined Geometry — CSG + Decode + Spawn (highest FO4 risk)
Subagent: general-purpose
Entry points: crates/bsa/src/csg.rs (CsgArchive::open, read_psg), crates/nif/src/import/precombine.rs (decode_shared_geom_object, psg_vertex_stride, PrecombineGeometry::into_imported_mesh, collect_precombine_geom_refs, PrecombineMaterial::apply), byroredux/src/cell_loader/precombined.rs (spawn_precombined_meshes), byroredux/src/cell_loader/load.rs (the pc_spawned / absorbed_refs gate), crates/plugin/src/esm/cell/wrld.rs + crates/plugin/src/esm/cell/mod.rs (precombined_mesh_hashes). Spec: docs/engine/fo4-csg-format.md.
Checklist:
- Vertex stride —
psg_vertex_stride(vertex_desc)must match theBSPackedGeomObjectpacked layout; an off-by-N stride silently shifts every vertex. Cross-check against the spec doc. - Y-up convert —
into_imported_meshapplies the Z-up→Y-up + per-instance transform. Confirm the instance transform composes (no double-apply withcell_origin). - CSG resolve —
spawn_precombined_meshesopens the companion<Plugin> - Geometry.csgonce per cell; missing CSG must fall back to per-REFR (return 0 spawns) rather than panic. - Owning-plugin resolution (#1592→#1590,
d6bf8437) — the CSG path AND the_oc.nifprecombine-object path key off the cell's owning plugin (form-id high byte), NOT the last-loaded--esm(precombined.rs::open_geometry_csgis fed the owning path). A DLC / multi-master cell must open<owning-plugin> - Geometry.csg, else every DLC-owned precombine silently falls back to per-REFR (or reads the wrong CSG blob). Tests: the#1590cases inprecombined.rs. - REFR de-dup gate (#1188) —
cell.absorbed_refsare suppressed only whenpc_spawned > 0; verify theload.rsgate renders absorbed REFRs when the precombine spawns nothing (no double-draw, no holes). - LOD selection — one tier per object, not all three (the
a30c088afix); a regression here triples precombine geometry. - Texturing — precombine shapes texture through the owning REFR's shape-slot indices (
2900de70); a regression renders untextured/checker precombines. - Opaque-architecture alpha-blend guard (
887aae52) — on the precombine path only,spawn_precombined_mesheskeeps the BGSM merge's two_sided/decal/alpha_test/texture flags but restores the pre-merge (NIF-shape) alpha-blend state. FO4 authors the "Standard" blend mode identically on transparent lab glass and opaque Institute metal architecture, so forwarding the mergedhas_alphamakes every precombined wallMATERIAL_KIND_GLASS→ see-through, mirror-hazy walls. A regression that re-applies the merged alpha-blend on the precombine path reintroduces the transparent/reflective-wall bug. - Exterior path —
cell_origin = cell_grid_to_world_yup(gx, gy)for exterior (#1221 / #1222); without it exterior precombines stack at world origin. Output:/tmp/audit/fo4/dim_1.md
Dimension 2: BGSM / BGEM Consumption + Metalness-from-Chromaticity (regression pin #1476)
Subagent: general-purpose
Entry points: crates/bgsm/src/ (lib, base, bgsm, bgem, reader, template), byroredux/src/asset_provider/material.rs (merge_bgsm_into_mesh).
Checklist:
- Single data source —
merge_bgsm_into_meshis the FO4 material merge: albedo/diffuse tint, specular, emissive, smoothness→roughness, translucency suite (#1147), model-space-normals bit, texture-slot paths. It runs beforetranslate_material. - Metalness from saturation, NOT luminance (#1476,
08ed03be) — for legacy spec-glossiness BGSMs (leaf.pbr == false, ~all vanilla architecture), metalness =(max-min)/maxofspecular_color(mult-invariant saturation): white spec[1,1,1]→ 0 (concrete is dielectric), tinted spec → metallic. Any reversion to luminance (0.2126·r + …) for the non-pbr branch is the #1476 regression — it makes vanilla concrete read chrome. The luminance path is correct only forleaf.pbr == true. - smoothness→roughness applied exactly once at parse —
mesh.roughness_override = Some((1.0 - leaf.smoothness).clamp(0.04, 1.0)). Audit any downstream re-application of the1.0 - smoothnessinversion. - Magic-vs-extension reconciliation (#758 footgun) — the
.bgsm/.bgem/.matarm dispatches on file content; verify it logs and does not blindly trust the extension. - Cycle-aware template resolve (#1148) —
template.rs::resolve/resolve_depthtrackvisitedlowercase keys and breakA→B→Aself-refs (vanilladefaulttemplate_wet.bgsm); verify the cycle-break +DEPTH_LIMITstill fire. - Blend-factor pass-through, NOT a swap (#1823 fixed a regression of #1651,
27334481) —src_blend/dst_blendin BGSM/BGEM are already Gamebryo-native (bgsm_blend_to_gamebryo, a plainu8cast); Standard=(6,7), Additive=(6,0), Multiplicative=(4,1) feedgamebryo_to_vk_blend_factorverbatim.#1651's premise ("GL-style enum inverted at 0/1") was false and its 0↔1 swap corrupted Additive (dst 0→1→ZERO, kills accumulation) and Multiplicative (dst 1→0→ONE, dest leaks through); Standard's (6,7) is a fixed point of the swap, which is why it slipped through review. Any reintroduction of a 0↔1 swap (or a function namedgl_to_gamebryo_blend) is the regression. Output:/tmp/audit/fo4/dim_2.md
Dimension 3: BA2 Reader — GNRL + DX10 (Exhaustive Version Match)
Subagent: general-purpose
Entry points: crates/bsa/src/ba2.rs.
Checklist:
- Exhaustive dispatch (#811,
f480337) — version handling is amatchover the supported set; unknown majors (0, 4, 5, 6, 9, …) bail atopen(). Any reintroduction of a cascadingif v == 1 { … } else if v == 7 { … }chain with a silent v1 fallback is the regression. Mirrors the BSA allowlist now atcrates/bsa/src/archive/open.rs(the oldarchive.rs:165-173comment inba2.rsreferencing the pre-split file is stale). - GNRL — extract a mesh and a script, byte-exact.
- DX10 — DDS header reconstruction from width/height/
dxgi_format/mip count; DXT1/DXT5/BC5/BC7 encoding; mip chunk assembly (mip0 largest → mip_last); per-archive zlib-vs-uncompressed flag. - Cross-version —
BA2_V_FO4(1) /BA2_V_FO4_NEXT_GEN_TEX(7) /BA2_V_FO4_NEXT_GEN_MESH(8) all force zlib; confirm the Next-Gen mesh/tex archives extract. Output:/tmp/audit/fo4/dim_3.md
Dimension 4: NIF BSVER 130 + Half-Float Vertices + FO4 Collision
Subagent: legacy-specialist
Entry points: crates/nif/src/blocks/tri_shape/bs_tri_shape.rs, crates/nif/src/import/mesh/bs_tri_shape.rs, crates/nif/src/import/mesh/bs_geometry.rs, crates/nif/src/import/collision/mod.rs.
Checklist:
- VF_FULL_PRECISION resolution (default-half unless set); half-float decode matches IEEE 754 binary16 incl. denormals/NaN.
- BSSubIndexTriShape segment data walked (FO4 actors lean on it); skinned bone indices/weights honor packed layout.
- DLC trailing fields on
BSLightingShaderPropertyin theFALLOUT4..FO4_DLC_UPPERband (subsurface/rimlight/backlight/fresnel/wetness). Next-Gen patch BSVER values still dispatch. - #1242 constant rename — the BSVER bound is
FO4_DLC_UPPER(= 140), not the old misnamedFO4_ENV_SCALE. Any reference to the old name anywhere undercrates/nif/orcrates/plugin/is a regression target. - FO4 collision (regression pin) —
BhkMultiSphereShapeandBhkConvexListShapetranslate toCollisionShapeincollision.rs(search the twodowncast_refarms) instead of falling through to the "unsupported" log + drop. Power-armor / settlement / destructible-debris collision depends on this; either arm reverting to a drop is a regression (NIFAL "parsed then dropped" leak class). Output:/tmp/audit/fo4/dim_4.md
Dimension 5: FO4 Shader Flags & BGSM PBR Routing (Disney path)
Subagent: renderer-specialist
Entry points: crates/nif/src/blocks/shader.rs (BSLightingShaderProperty), crates/nif/src/import/material/ (mod, walker, shader_data), crates/renderer/shaders/include/lighting.glsl + include/pbr.glsl (Disney lobe gates).
Checklist:
- u32 flag pair read as two separate fields (shader_flags_1 + shader_flags_2). FO4 flag bit positions differ from Skyrim — verify decal / alpha-test / skinned / glow / window / refraction / parallax / facegen bits read from the correct mask + bit.
- Render-affecting bits consumed, not just parsed (#1592,
f7fbbed5) — the walker (crates/nif/src/import/material/walker.rs) must OR Model_Space_Normals (F4SF1 bit 12) and Alpha_Test (F4SF2 bit 25) intoMaterialInfo. Glow_Map (F4SF2 bit 6) is deliberately not gated here (#1733 doc-fix, corrected the #1592 comment) — glow sources unconditionally from theBSShaderTextureSetglow slot regardless of the flag bit; do not re-propose wiring it as a gap. Vanilla FO4 is BGSM-backed so this only bites inline/loose/modded NIFs, but a regression decodes an object-space normal map as tangent-space or renders an inline cutout opaque. Tests:crates/nif/src/import/material/fo4_shader_flag_tests.rs,alpha_flag_tests.rs. - BGSM Name stopcond — when the material is external, the block parser returns before reading the inline Phong trailing fields (those belong to the BGSM).
ImportedMesh.material_pathflows toMaterial.material_pathfor diagnostics;mesh.info <id>surfaces it when texture_path is absent. - BSShaderTextureSet slot routing (#563) — SkinTint / HairTint
BSLightingShaderTypevariants sample different slots than the default LSP path; verify the per-type slot map is in lockstep with whattriangle.fragreads. - PBR flag, not Lambert —
crates/nif/src/import/material/walker.rsroutes BGSM-authored material through the PBR path; the FO4-specific regression is a BGSM material falling back to Lambert (opposite of the FNV/FO3 regression). #1241 surfaces smoothness/IOR/specular_strength intoMaterialInfoso the Disney lobe has data. - #1244
BSShaderPropertyBaseOnlyconsumer wired at import — bare-stub BSShaderProperty routes through MaterialInfo; verify FO4 fallback meshes aren't default-everything. - The per-game→canonical handoff is covered in depth by Dimension 7; cross-game canonical invariants live in
/audit-nifal. Output:/tmp/audit/fo4/dim_5.md
Dimension 6: ESM Architecture Records (SCOL / MOVS / PKIN / TXST)
Subagent: general-purpose
Entry points: crates/plugin/src/esm/records/ (scol.rs, movs.rs, pkin.rs, mswp.rs; TXST lives in the misc set), crates/plugin/src/esm/cell/ (mod, walkers, support, wrld + cell/tests/).
Checklist:
- SCOL — prefab of child-static placements w/ per-instance scale/rotation, parsed into an expandable structure. PKIN — packins (grouped bundles). MOVS — movable statics (parse-only; physics runtime not wired). TXST — texture sets referenced by NIF material paths.
- TXST DODT + DNAM (#813 / #814) — decal-data sub-record + flags parsed via
DecalData; pre-fix, 207/382 (DODT) + 382/382 (DNAM) vanilla TXSTs dropped authoring. Any TXST read path withoutDecalDatais the regression. The TXST match arms live incell/walkers.rs; anunreachable_patternswarning there suggestsb"TXST"matches before the intended arm. - Category index exposure (#817) — the FO4-architecture maps must surface in
categories(); a missing entry hides records from category iteration. - Re-run the real-data ESM parse harness (#819) before reporting any parse-rate finding.
Output:
/tmp/audit/fo4/dim_6.md
Dimension 7: NIFAL Canonical Material Translation (FO4 is PBR-canonical)
Subagent: renderer-specialist
Entry points: byroredux/src/material_translate.rs (translate_material — the single boundary), crates/core/src/ecs/components/material.rs (Material, Material::resolve_pbr), byroredux/src/cell_loader.rs (pack_bgsm_material_flags, pack_effect_shader_flags). Spec: docs/engine/nifal.md. See also /audit-nifal.
Checklist:
- Single boundary —
translate_material(mesh, paths, extra_material_flags)is the onlyImportedMesh → Materialsite; both the cell-loader REFR-spawn path and the loose-NIFscenepath must route through it, not verbatim struct literals. - BGSM PBR flag routing —
effect_shader_flagsORspack_effect_shader_flags+pack_bgsm_material_flags(mesh)+ callerextra_material_flags. Verify BGSM meshes getBGSM_PBR(and where authoredBGSM_TRANSLUCENCY/BGSM_MODEL_SPACE_NORMALS) → shader-sideMAT_FLAG_PBR_BSDF. Tests:pack_bgsm_material_flags_testsincell_loader.rs. - Resolve-once contract —
Material.metalness/Material.roughnessare plainf32(notOption+ per-draw classify), seeded frommesh.metalness_override/mesh.roughness_override(orf32::NANfor legacy inline-shader content), thenresolve_pbr()fills NaN from the classifier and clamps (metalness 0..1, roughness 0.04..1).resolve_pbris idempotent (testsresolve_pbr_is_idempotent,resolve_pbr_preserves_upstream_translator_values,resolve_pbr_fills_only_missing_slotinmaterial.rs). Any consumer re-deriving roughness or re-running a classifier at draw-time is the regression. - Glass is classified after
resolve_pbr(forced glass roughness wins) viahelpers::classify_glass_into_material. Output:/tmp/audit/fo4/dim_7.md
Dimension 8: FO4 Cell Load End-to-End (SCOL/PKIN Expansion)
Subagent: general-purpose
Entry points: byroredux/src/cell_loader/refr.rs (expand_scol_placements, expand_pkin_placements), byroredux/src/cell_loader/references/mod.rs (wires the expanders), byroredux/src/cell_loader/exterior.rs (Phase-3a).
Checklist:
expand_scol_placements/expand_pkin_placementsturn a prefab/packin into per-instance synthetic REFRs with composed transform (parent × child), recursion bounded byMAX_PKIN_DEPTH = 4(shared, #1180 / #1182; vanilla has zero nesting, the cap guards modded cross-recursion).references.rsfires the first matching expander and composes placements.- Tests:
cell_loader/scol_expansion_tests.rs,cell_loader/pkin_expansion_tests.rs. - Precombine spawn (Dim 1) and SCOL/PKIN expansion are independent carriers of the same cell — confirm no double-draw between an expanded SCOL static and a precombine that subsumed it (the
absorbed_refsgate in Dim 1 is the de-dup mechanism). - BSConnectPoint attach-graph consumer (#1594,
c16600a5) — the import liftsBSConnectPoint::Parents/::ChildrenintoAttachPoints/ChildAttachConnectionsECS components (crates/core/src/ecs/components/attach_points.rs); the cell-load spawn path (cell_loader/spawn.rs+references.rs, cached innif_import_registry.rs) must materialize them onto spawned entities. Pre-fix the chain dead-ended at the import boundary and modular weapons / power-armor frames spawned as base-receiver only. Regression target: a spawn path that imports the connect graph but attaches noAttachPointscomponent. Tests:cell_loader/attach_points_spawn_tests.rs. Output:/tmp/audit/fo4/dim_8.md
Dimension 9: Real-Data Validation + Forward Scope
Subagent: general-purpose
Entry points: crates/nif/tests/parse_real_nifs.rs (parse_rate_fallout_4, parse_rate_fo4_all_meshes), crates/nif/examples/nif_stats.rs, ROADMAP.md, docs/feature-matrix.md.
Checklist:
- Re-run
BYROREDUX_FO4_DATA=… cargo test -p byroredux-nif --test parse_real_nifs -- --ignored fo4(parse_rate_fo4_all_meshescovers bothFallout4 - Meshes.ba2+Fallout4 - MeshesExtra.ba2) — both archives parse 100.00% clean per #1457. Report the measured number; do not cite the stale ROADMAP figure without re-running. - Trace
import_nif_sceneon: a settlement workshop item, a creature (deathclaw / super mutant), a power-armor frame (heavy skinning + BSConnectPoint), a modular weapon (receiver/barrel/stock via BSConnectPoint). For each: mesh count, material_path (BGSM ref or null), skinned vs rigid, connect-point extra-data presence. - Forward scope (do NOT re-file as blockers):
_precomb.nifcollision,.uvdocclusion volumes, MOVS physics runtime, FaceGen NIF truncation tail, deeper cell coverage (LIGH power state / CONT leveled items / NPC_ face morph),BSBehaviorGraphExtraDataparse-only (verify nothing pretends to drive it). The BGSM parser, SCOL/PKIN expansion, and the M49 CSG pipeline are all shipped — never list them as pending. Output:/tmp/audit/fo4/dim_9.md
Phase 3: Merge
- Read all
/tmp/audit/fo4/dim_*.md. - Combine into
docs/audits/AUDIT_FO4_<TODAY>.md:- Executive Summary — NIF + BA2 + BGSM parser + SCOL/PKIN expansion + M49 CSG precombines all landed; parse rate per the latest
parse_rate_fo4_all_meshesrun. Pending: precombine collision /.uvdvolumes, MOVS physics, deeper cell coverage (LIGH/CONT/NPC_). - Dimension Findings — grouped by severity per dimension.
- BGSM Consumption Table — BGSM field × merged-into-
ImportedMesh-by-merge_bgsm_into_mesh/ surfaced-on-canonical-Material(post-NIFAL). - Forward Scope Chain — precombine collision /
.uvd→ MOVS physics → deeper REFR/LIGH/CONT/NPC_ coverage. Do NOT list the CSG reader, BGSM parser, or SCOL expansion as pending.
- Executive Summary — NIF + BA2 + BGSM parser + SCOL/PKIN expansion + M49 CSG precombines all landed; parse rate per the latest
- Remove cross-dimension duplicates.
Suggest: /audit-publish docs/audits/AUDIT_FO4_<TODAY>.md
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.