description: "Per-game audit of Starfield compatibility — BA2 v2/v3 + LZ4 block, CDB materials, BSGeometry .mesh resolution, walkable Cydonia interior" argument-hint: "--focus <dimensions>"
Starfield Compatibility Audit
Depth/correctness audit of ByroRedux's Starfield support. Starfield is a
first-class GameKind: NIF + BA2 v2/v3, CDB + BGSM/BGEM materials, and a
walkable Cydonia interior all ship today. This is a regression-and-depth
audit of that bring-up surface, not a from-scratch gap inventory.
Architecture: Orchestrator. Each dimension runs as a Task agent (max 3 concurrent).
See .claude/commands/_audit-common.md for project layout, game data locations,
methodology, deduplication rules, finding format, and the SF Material / SF Smoke
entries. See .claude/commands/_audit-severity.md for the severity scale (the
NIFAL rows gate the translate_material boundary at HIGH minimum).
Game Context
Authoritative status lives in ROADMAP.md (Starfield compat-matrix row +
parse-rate breakdown), docs/feature-matrix.md (Starfield-Specific section),
and the two ESM specs docs/engine/starfield-esm-roadmap.md +
docs/engine/starfield-esm-phase0-baseline.md. Do not duplicate counts here —
read those at audit time. Snapshot of the shape, not the numbers:
| Aspect | State (verify against ROADMAP) |
|-------------|--------------------------------|
| NIF format | BSVER 155 (FO76 baseline) → Starfield retail extensions on top |
| BA2 format | v2 + v3; v3 adds a 12-byte header extension carrying compression_method (0 = zlib, 3 = LZ4 block) |
| ESM parser | Live — GameKind::Starfield via HEDR-0.96 classifier; existing dispatch captures Starfield content at ~99.9% record parity (per starfield-esm-roadmap.md plan revision) |
| Mesh path | BSGeometry (inline geom data or external geometries\<X>.mesh companion) — NOT BSTriShape |
| Materials | CDB (crates/sfmaterial/) for vanilla materialsbeta.cdb + external BGSM/BGEM (crates/bgsm/), both wired via --materials-ba2 |
| Cell | Walkable Cydonia interior (#1289/#1291/#1292/#1294/#1295) |
| Reference | /mnt/data/SteamLibrary/steamapps/common/Starfield/Data/ |
Known Specifics (where to look, not what to assume)
- CRC32-hashed shader flag arrays (BSVER ≥
FO4_CRC_FLAGS= 132) —BSLightingShaderProperty/BSEffectShaderPropertystore shader flags as arrays of CRC32 hashes (sf1_crcs/sf2_crcs) instead of bit masks. Parsed inparse_skyrim_shader_baseincrates/nif/src/blocks/shader.rs. SF2 array gated on BSVER ≥FO76_SF2_CRCS= 152. - BSVER ==
FO76(155) baseline —BSShaderType155dispatch + the luminance / translucency / texture-array tail; this is where #1510 lived. - BGSM / BGEM material references —
is_material_reference(shader.rs) short-circuits whenNameis a non-empty.bgsm/.bgempath and returns a material-reference stub; the real material is the external file, parsed bycrates/bgsm/and folded in bymerge_external_material(byroredux/src/asset_provider/material.rs). - CDB material database — vanilla Starfield ships all material data inside a
single
materials\materialsbeta.cdbComponent Database (inStarfield - Materials.ba2), consumed bycrates/sfmaterial/and extracted via--materials-ba2. - BA2 v3 compression — header has a 12-byte extension (vs 8 for v2). GNRL +
DX10 both dispatch through a unified decompress path selected by archive-level
compression_method(Ba2Compressionincrates/bsa/src/ba2.rs).
Parameters (from $ARGUMENTS)
--focus <dimensions>: Comma-separated dimension numbers (e.g.,2,9). Default: all 9.
Phase 1: Setup
- Parse
$ARGUMENTS. mkdir -p /tmp/audit/starfield.- Fetch dedup baseline:
gh issue list --repo matiaszanolli/ByroRedux --limit 200 --json number,title,state,labels > /tmp/audit/issues.json. - Confirm
Starfield/Data/exists; if not, note which dimensions lose real-data validation.
Phase 2: Launch Dimension Agents (parallel)
Dimensions are ordered by Starfield-specific risk: the highest-risk seams
(BA2 v3/LZ4 decompression, CDB material correctness, BSGeometry .mesh
resolution, ESM resolve-rate) come first.
Dimension 1: BA2 v2 / v3 — LZ4 Block Decompression
Subagent: general-purpose
Entry points: crates/bsa/src/ba2.rs
Checklist: v2 header (8-byte extension) vs v3 header (12-byte extension with
compression_method at the correct offset). Dispatch via the Ba2Compression
enum: 0 → zlib, 3 → LZ4 block, others → error (confirm the unsupported-method
branch is a hard error, not a silent fall-through). lz4_flex block decompress —
does it need an explicit max_size, and does BA2 supply it from the chunk's
uncompressed size? Per the module doc, v3 DX10 mips can mix raw and
LZ4-compressed chunks within one texture — verify the per-chunk
compressed/uncompressed-size comparison selects raw-vs-decompress correctly.
GNRL + DX10 must both reach the unified decompress path. Regression guard:
DX10 chunk layout is unchanged from FO4 v1 — the v3 issue was the
compression_method offset, not a per-chunk-layout difference. Parse-rate sweep
across all v2 and v3 archives (extract rate is 100% per the compat matrix —
confirm it holds).
Output: /tmp/audit/starfield/dim_1.md
Dimension 2: BSGeometry Mesh Extraction (Starfield's actual mesh path)
Subagent: legacy-specialist
Entry points: crates/nif/src/import/mesh/bs_geometry.rs (geometry extraction),
crates/nif/src/blocks/bs_geometry.rs (block parse), with
crates/nif/src/import/mesh/bs_tri_shape.rs as the FO4/Skyrim contrast
(Starfield does NOT use BSTriShape)
Checklist: extract_bs_geometry — Stage A inline geometry
(has_internal_geom_data()) vs Stage B external .mesh companion.
#1292 — external .mesh resolved via the canonical geometries\<X>.mesh
path; the importer must NOT prepend meshes\ (implemented in
byroredux/src/asset_provider/archive.rs, regression-guarded by the
normalize_mesh_path tests in byroredux/src/asset_provider/tests/material_path.rs —
confirm the geometries\ head is left untouched). Without this the Cydonia spawn rate
collapses. #1209 — iterate every LOD slot, not meshes.first() (a None
short-circuit when LOD 0 was external despite later internal slots).
#1828/#1829 (ba728882) — both the Stage A find_map and the Stage B
external-.mesh loop must also skip a slot whose body is the scale<=0
sentinel (empty vertices/triangles) even when it parses Ok/matches
Internal first — accepting a sentinel-first slot silently drops the whole
BSGeometry. A regression re-accepting the first Internal match or first
Ok(...) parse without the emptiness check reintroduces this.
#1203 — skin chain resolved via BSSkin::Instance + BSSkin::BoneData.
#1232 — empty/zero-length tangent blobs route through
synthesize_tangents_yup (Mikkelsen); verify the fallback is reached and
produces unit-length tangents. PBR scalars metalness_override /
roughness_override are forwarded from the BGSM-resolved legacy_pbr. Watch for
new vertex-attribute bits beyond FO4's set and Starfield's far-higher vertex
counts.
Output: /tmp/audit/starfield/dim_2.md
Dimension 3: CDB Material Database Correctness
Subagent: renderer-specialist
Entry points: crates/sfmaterial/src/reader.rs (ComponentDatabaseFile::parse,
index_chunks), crates/sfmaterial/src/chunk.rs, string_table.rs, types.rs,
value.rs, byroredux/src/asset_provider/material.rs (--materials-ba2 wiring)
Checklist: ComponentDatabaseFile::parse consumes materials\materialsbeta.cdb
extracted from Starfield - Materials.ba2 via --materials-ba2. #762 —
guard index_chunks against the chunk-index regression already referenced in
byroredux/src/asset_provider/tests/starfield_mat.rs. DLC/Creation CDB discovery by scanning (#1571, 8c99c50d) —
asset_provider/material.rs::discover_starfield_cdbs scans each materials archive for
every materials\materialsbeta.cdb AND DLC/Creation-namespaced
materials\creations\<plugin>\materialsbeta.cdb, instead of extracting one
hardcoded base path; a regression that re-hardcodes the single base path silently
drops every DLC/Creation material database. Walk the parse path: header (parse_header) → chunk index
(index_chunks) → class parse (parse_class). Are unknown ChunkType /
Value variants handled (warn-and-skip) or do they bail/panic? Confirm
peek_magic correctly distinguishes a CDB from a loose BGSM. Correctness, not
just "it parses": does the per-.mat material resolution forward roughness /
metalness / texture-slot values into the ImportedMesh, or do .mat-resolved
materials currently reach the Disney lobe with NIF defaults? (Per the ROADMAP
forward-blocker chain, per-field CDB extraction is the #1289 Phase 2 follow-up —
confirm current state and scope the gap, don't re-report it as new.) Count
unique CDB material handles vs loose BGSM/BGEM references in a Starfield archive
to confirm CDB supersedes loose-file BGSM for vanilla content.
Output: /tmp/audit/starfield/dim_3.md
Dimension 4: Starfield ESM Resolve-Rate Baseline
Subagent: general-purpose
Entry points: byroredux/src/sf_smoke.rs (--sf-smoke <CELL_EDID> resolve-rate
harness), crates/plugin/examples/sf_smoke.rs + crates/plugin/examples/sf_parse_check.rs
(top-level GRUP byte-coverage tools), docs/engine/starfield-esm-phase0-baseline.md
Checklist: The two tools answer different questions — keep them straight.
crates/plugin/examples/sf_smoke.rs measures byte/FourCC coverage of the
top-level GRUP walk vs DISPATCH_HANDLED_FOURCCS; byroredux/src/sf_smoke.rs
(--sf-smoke) measures the per-cell base-form resolve rate (of N REFRs in a
named interior cell, how many point at a base form actually decoded into
EsmCellIndex.statics). Run --sf-smoke against Cydonia and confirm the resolve
rate has not regressed below the Phase 0/1 baseline. A drop = the CELL handler
silently dropped REFRs (moved subrecord size, new XCLL field) or a base record
(STAT/MSTT/FURN/LIGH) failed to index — REFRs then spawn the 3D-unit-cube
placeholder. Cross-check the per-record-type breakdown for new Starfield-only
base types (GBFM/GBFT/PNDT/STDT/BIOM) showing up where a real parser is missing;
note frequency, don't re-report the known GBFM stub gap.
#1567 (0d9ee07f) — Starfield LIGH records carry no MODL/DATA, only a
component-block DAT2 payload; build_static_object_from_subs must decode it
(test: starfield_ligh_dat2_decodes_to_light_data) or every REFR pointing at a
LIGH misses at the static lookup and drops silently (656 Cydonia lights
pre-fix). Regression guard for this dimension's resolve-rate baseline.
Output: /tmp/audit/starfield/dim_4.md
Dimension 5: ESM + Cell Bring-up Regression Surface
Subagent: general-purpose
Entry points: crates/plugin/src/esm/reader.rs (GameKind::Starfield HEDR-0.96
classifier), crates/plugin/src/esm/records/mod.rs (FourCC dispatch),
crates/plugin/src/esm/cell/walkers.rs (XCLL + per-cell NAVM),
byroredux/src/cell_loader/spawn.rs (REFR placement)
Checklist: HEDR-0.96 → GameKind::Starfield classification (reader.rs).
FourCC dispatch coverage in records/mod.rs — which record types are parsed vs
warned-skip; cross-check against the resolve-rate baseline from Dim 4.
PDCL conscious skip (#1568, b804c180) — the Starfield PDCL
(BGSProjectedDecal) GRUP is skipped consciously (named into
index.skipped_unconsumed_groups + a one-shot warn) rather than vanishing into
the anonymous catch-all; verify it stays a named skip (so coverage tooling counts
it) and does not silently regress into the catch-all.
#1291 — XCLL_SIZES_STARFIELD = [28, 108] (walkers.rs), split off the
Fallout-era [28, 40] bucket. Important correction to any stale doc: the
108-byte Starfield XCLL is NOT "Skyrim's 92-byte body + a 16-byte tail" — per
the walkers.rs doc comment it shares only bytes 0-39 with Skyrim and is decoded
in full against xEdit SF1 wbStruct(XCLL,'Lighting') (the old #1293
"16-byte-tail follow-up" framing is resolved). Per-cell NAVM collection
(walkers.rs, #1272). Spawn-path regression guards in cell_loader/spawn.rs:
#1294 static-trimesh fallback gated on base_layer not final_layer
(synthesized collider count was 0 before the fix); #1235 SceneFlags::from_nif
(crates/core/src/ecs/components/scene_flags.rs) attached at spawn;
#1295 DoorTeleport stamped from REFR XTEL; #1212/#1213/#1214
FormIdComponent / LocalBound / BSXFlags at spawn; #1284 SkinSlotPool
ceiling raise (crates/core/src/ecs/resources/skin_slot_pool.rs) for Cydonia's skinned density.
Also confirm synthesized colliders carry IsCollisionOnly (components.rs) so
they stay out of the BLAS (R6a-stale-13/14 collider-cost fix, see ROADMAP).
Output: /tmp/audit/starfield/dim_5.md
Dimension 6: NIF Shader Blocks — BSVER 155+ (regression guard)
Subagent: legacy-specialist
Entry points: crates/nif/src/blocks/shader.rs (parse_skyrim_shader_base,
BSLightingShaderProperty, BSEffectShaderProperty), docs/legacy/nif.xml
Checklist: CRC32 flag-array parsing for BSVER ≥ FO4_CRC_FLAGS (132) —
num_sf1 + per-element u32 CRC into sf1_crcs; SF2 array for BSVER ≥
FO76_SF2_CRCS (152) into sf2_crcs. Is there a CRC32 hash → flag-name table,
or are the hashes opaque? #1510 regression guard — BSShaderType155 dispatch
- the luminance / translucency / texture-array tail in
shader.rspreviously over-read by 4 B, truncating all ~1036 Starfield full-bodyBSLightingShaderPropertyblocks toNiUnknown; confirm the block-histogram NiUnknown count for these stays at 0. WetnessParams extended fields, refraction power onBSEffectShaderProperty(FO76-style), and the new BSEffectShaderProperty textures (Reflectance / Lighting / Emittance / Emit Gradient) — verify byte consumption against nif.xml. #1606 undocumented BSLightingShaderProperty tail (497700e7) — the empty-name full-body StarfieldBSLightingShaderPropertycarries a 38-byte trailing field (9× f32 + 2 B) that nif.xml does NOT document; the dispatcher passes the declaredblock_sizetoBSLightingShaderProperty::parse_with_size, which capturesblock_size - consumedtrailing bytes opaquely intostarfield_tail: Vec<u8>(gatedbsver >= STARFIELD). The legacyparse(None size) path is unchanged and yields an empty tail. Verify the tail is captured to-block_size (not a hardcoded 38, no over-read) and that LODMeshes drift stays at 0 — do NOT fabricate field names/semantics. Tests:parse_bs_lighting_starfield_captures_trailing_tail+..._tail_empty_without_size_or_driftincrates/nif/src/blocks/shader_tests/starfield.rs(split by era, #2056). The sibling BSEffectShaderProperty +32 B under-read on the same archive is a known follow-up (left scoped out) — note frequency, don't re-file as new. Output:/tmp/audit/starfield/dim_6.md
Dimension 7: Real-Data Validation
Subagent: general-purpose
Entry points: crates/nif/examples/nif_stats.rs, crates/nif/tests/parse_real_nifs.rs
Checklist: Parse rate holds at the compat-matrix figure (see ROADMAP
Starfield row + docs/engine/game-compatibility.md) via
BYROREDUX_*_DATA=... cargo test -p byroredux-nif --test parse_real_nifs parse_rate_starfield_all_meshes -- --ignored
(walks all 5 vanilla mesh archives; parse_rate_starfield covers Meshes01 only).
The residual truncation tail in Meshes01/MeshesPatch is tracked at #746/#747 —
confirm it has not grown. Verify Starfield texture archives matching
Starfield - *Textures*.ba2 extract cleanly (compat matrix records 100% extract
recover, post-#754). Pick 5 representative meshes — a clutter item, a ship hull,
a character body, a weapon, a landscape feature — and trace each through
import_nif_scene (crates/nif/src/import/mod.rs). Watch for NiUnknown
placeholders in the block histogram — these flag new block types introduced since
the FO76/Starfield baseline.
Output: /tmp/audit/starfield/dim_7.md
Dimension 8: NIFAL Canonical Material Translation for Starfield
Subagent: renderer-specialist
Entry points: byroredux/src/material_translate.rs (translate_material — the
single boundary), crates/core/src/ecs/components/material.rs
(Material::resolve_pbr)
Checklist: translate_material is the single raw ImportedMesh → ECS
Material boundary — per-game / per-material classification happens here, never
per-draw in the shader (see also /audit-nifal). Verify BSGeometry/BGSM/CDB-
resolved Starfield meshes land with Material.metalness / Material.roughness as
plain resolved f32 (material.rs), set once — no Option<f32> per-draw
classify_pbr plumbing (removed by the NIFAL refactor; resolve_pbr is the
resolve-once fill). Confirm Material::resolve_pbr and the EmissiveSource
discriminator (#1280, tagged in crates/nif/src/import/material/dedicated_shader.rs
and legacy_properties.rs since the #2059 walker.rs split) behave
for SF content. NIFAL particle slice reaching SF NIFs: typed NiPSysEmitter /
NiPSysEmitterCtlr (crates/nif/src/blocks/particle.rs) →
extract_emitter_params / extract_emitter_rate (crates/nif/src/import/walk/mod.rs)
→ apply_emitter_params (byroredux/src/systems/particle.rs). NIFAL collision
slice (Cydonia's synthesized + bhk colliders): BhkMultiSphereShape +
BhkConvexListShape translate to CollisionShape in
crates/nif/src/import/collision/shape.rs.
Output: /tmp/audit/starfield/dim_8.md
Dimension 9: BGSM/BGEM External Material Flow
Subagent: renderer-specialist
Entry points: crates/bgsm/src/bgsm.rs + crates/bgsm/src/bgem.rs (external
parser), byroredux/src/asset_provider/material.rs (merge_external_material),
byroredux/src/cell_loader.rs (pack_imported_material_flags)
Checklist: The material-reference stub from shader.rs resolves to the
external file — confirm the BGEM variant (bgem.rs) is handled distinctly from
BGSM (bgsm.rs): different texture-set conventions plus the BGEM glass_enabled
flag. merge_external_material folds the parsed result into ImportedMesh.material
(an ImportedMaterial — it takes &mut ImportedMaterial, so it cannot touch
geometry/skinning; a widened signature is a NIFAL boundary violation);
Starfield .mat texture paths must land in MaterialTextureSet roles, never
in a CDB-specific slot index;
pack_imported_material_flags packs byroredux_renderer::vulkan::material::material_flag::{BGSM_AUTHORED, PBR_BSDF, TRANSLUCENCY, MODEL_SPACE_NORMALS, EFFECT_PALETTE_COLOR}
(#1147 / #1077 / #1076 / #1280) — verify each flag derives from the right
ImportedMaterial field. BGEM glass_enabled (bgem.rs) is the authoritative glass
signal (#1280), consumed in byroredux/src/helpers.rs (and must NOT misclassify an
opaque architecture piece carrying a stuck flag — there's a regression test for
that). Disney BSDF / PBR (#1248-#1252) is the canonical lobe (GLSL-PathTracer
MIT + Burley 2012, attribution at top of crates/renderer/shaders/triangle.frag);
the classification feeding it happens at the single translate_material boundary
(Dim 8), not per-draw.
Output: /tmp/audit/starfield/dim_9.md
Phase 3: Merge
- Read all
/tmp/audit/starfield/dim_*.mdfiles. - Combine into
docs/audits/AUDIT_STARFIELD_<TODAY>.mdwith structure:- Executive Summary — Current state: Starfield is a first-class
GameKindwith NIF + BA2 at the compat-matrix rate, CDB + BGSM/BGEM materials, and a walkable Cydonia interior. This is a depth/correctness audit — focus on regressions in the bring-up surface (BA2 v3 decompress, CDB chunk index, BSGeometry.meshresolution, spawn gates, NIFAL translation) and the remaining ESM phase work. - Dimension Findings — Grouped by severity per dimension.
- CRC32 Flag Table — Known/unknown flag-name → CRC32 mappings for the shader flag arrays (anything derivable empirically from observed hashes).
- Remaining-Work Chain (per
starfield-esm-roadmap.md— Phases 0+1 done, 2-4 invalidated by the 99.9%-parity measurement) — in order: per-field CDB extraction (#1289 Phase 2 follow-up —.mat-resolved materials currently reach the Disney lobe with NIF defaults), exterior worldspace tiles, space-cell / planet / GBFM records, and the #746/#747 NIF truncation tail. Do NOT frame this as a "BGSM parser first / ESM very far" chain — both have shipped.
- Executive Summary — Current state: Starfield is a first-class
- Remove cross-dimension duplicates.
Suggest: /audit-publish docs/audits/AUDIT_STARFIELD_<TODAY>.md
TDD Red-Green-Refactor
Testing
Skill qui guide Claude a travers le cycle TDD complet.
Audit d'Accessibilité Web
Testing
Réalise un audit d'accessibilité web complet selon les normes WCAG.
Générateur de Tests UAT
Testing
Génère des cas de test d'acceptation utilisateur structurés et complets.