"Per-game audit of Oblivion (TES4) compatibility — NIF v20.0.0.4 + the v10.x NetImmerse family, BSA v103, live ESM path"
Install
npx skillscat add matiaszanolli/byroredux/claude-commands-audit-oblivion Install via the SkillsCat registry.
Oblivion Compatibility Audit
Oblivion is the oldest title and exercises code no other game reaches. This audit owns Oblivion's data through the shared mechanisms (routing: .claude/commands/_audit-owners.md): only dimensions whose Paths are Oblivion-specific stay here; a defect in the shared mechanism goes to its owner audit.
Architecture: Orchestrator. Each dimension runs as a Task agent (max 3 concurrent).
Read .claude/commands/_audit-common.md (layout, methodology, dedup, finding format) and .claude/commands/_audit-severity.md for shared protocol.
What Makes Oblivion Different
Two NIF eras share Oblivion - Meshes.bsa: the retail body (v20.0.0.4, bsver 11, plus a v20.0.0.5 minority; inline strings, u16 flags) and a v10.x NetImmerse tail (down to pre-Gamebryo v3.3.0.13) with tight nif.xml version bands. Neither has a per-block size table: one N-byte under-read discards the rest of the file (no block_size recovery as on 20.2.0.7+), so drift is silent and Oblivion-only. The v10.x stride-drift family (#1506-#1509) is resolved — a regression-guard set. The 2026-09-05 CRITICAL (a NiSkinPartition reservation: clean rate 100% -> 92.41%, 730 of 9 612 meshes lost) is what a skipped corpus run looks like.
| Aspect | State (verify; pull live figures) |
|---|---|
| NIF | Baselines: crates/nif/tests/data/per_block_baselines/oblivion.tsv, crates/nif/tests/data/block_coverage_baselines/oblivion_truncations.tsv (truncating=0; 9 612 NIFs = base + 8 DLC archives) |
| BSA | v103; 17 archives / 147 629 files extract with 0 errors. Folder record is 16 B for v103 and v104 (24 B only for v105, open.rs) |
| Exterior | Tamriel (0,0) radius 1 renders on-device; live figure in docs/engine/exterior-readiness-plan.md (#2377 / #2368 closed); ROADMAP.md Oblivion row |
| Runtime baseline | .claude/audit-baselines/runtime/oblivion-ICMarketDistrictTheGildedCarafe.tsv |
| Reference data | /mnt/data/SteamLibrary/steamapps/common/Oblivion/Data/ |
Authoring census (byte-scan of Oblivion.esm, 2026-09-19): 2 393 SCPT, 390 QUST, 604 CLOT, 8 228 PGRD, 0 NAVI / NAVM, 19 278 INFO with 0 PNAM / 0 ANAM, 306 LVSP, 229 LTEX, 37 WTHR (all with HNAM). No locomotion\ directory (the walk clip is handtohandforward.kf); 9 889 .lod names in Oblivion - Meshes.bsa.
Known-open, do not re-file (gh issue view first): #5505 (child worldspaces draw no parent LAND / terrain LOD, Dim 4), #5351 (HNAM doc rot), #5526 (Phase V voice never resolves — no VTCK/VTYP, voices ship as race\sex .mp3, Dim 5), #5439 (the drift corpus treats an unopenable archive as absent, Dim 1), #5011 (the EGM sweep pins nothing). Latent, closed as a doc fix (#5350, 9a95a50fe, Dim 2): Oblivion's opening dialogue menu is the player's known-topics list (an INFO NAME "Add Topics" or a script AddTopic; 1 761 of 3 184 Topic DIALs are TCLT targets and never NAME targets), but topic_entry (byroredux/src/systems/npc_dialogue.rs) still has no Oblivion arm — every branch-less Oblivion topic classifies TopLevel. Nothing observes it today (Oblivion has no quest aliases, so no NPC owns a topic); wiring Oblivion ownership needs an arm returning LinkOnly for the TCLT-only set. Fixed since 2026-10-08 — verify, don't re-file: #5504 (legacy 4-byte PKDT / 12-byte PTDT, dae08000d, Dim 2), #5073 (empty-payload compressed BSA entry, fa2af0e89, Dim 2), #5335 (Oblivion distant water, e38ff49d2, Dim 4), #5516 + #5519 (one inherit-all boundary, REGN-CNAM claim retired, 65cadde2f, Dim 4), #5487 (body table + race body skins, 1b45763fa, Dim 3), #5397 (Random stack, 0436bea5c, Dim 2), #5374 + #5388 (child water + climate, 67afa4b95 / 217bcbc9f, Dim 4), #5400 + #5377 (menuxml 16-bpp / narrow-mask DDS, 5c53ad9c7 / 015e10549, Dim 5), #5402 (parse_dds decodes DDPF_LUMINANCE L8 / L8A8 — all 470 luminance *_g.dds glow maps in the base + Shivering Isles texture archives parse and expand; guards parse_and_expand_l8_luminance, parse_and_expand_l8a8_luminance_with_alpha, Dim 3).
Parameters (from $ARGUMENTS)
--focus <dimensions>: comma-separated numbers (e.g. 1,3). Default: all 5.
Phase 1: Setup
- Parse
$ARGUMENTS;mkdir -p /tmp/audit/oblivion. - Dedup baseline:
gh issue list --repo matiaszanolli/ByroRedux --limit 200 --json number,title,state,labels > /tmp/audit/issues.json. - Confirm
Oblivion/Data/exists; if not, name the dimensions that lose real-data validation. - Run the corpus lane first (release build; the plain
cargo testlane never runs it):cargo test -p byroredux-nif --release --test parse_real_nifs --test per_block_baselines --test block_coverage_baselines --test oblivion_stream_drift_corpus -- --ignored oblivion no_block_sizes. Any red gate is the headline finding. - Scope by delta:
git log --since=<last AUDIT_OBLIVION date> --format='%h %cs %s' -- <dimension Paths>. - Toolchain: the
byroreduxbin crate needs rustc >= 1.96 (wasmtime 49, 15a6b1d2d) — rustup cargo perdocs/contributing.md§ Toolchain note (#4466). - Always name
Oblivion.esmreal-data tests (cheap: ~3 s): a whole-cratecargo test -p byroredux-plugin -- --ignoredspikes >20 GB and can kill the session.
Phase 2: Launch Dimension Agents (parallel)
Dimension 1: NIF Version Handling & Corpus Integrity (v20.0.0.4 + the v10.x tail)
Subagent: legacy-specialist
Routing: generic stream position / dispatch / version gating -> /audit-nif; collision translation -> /audit-nifal, /audit-physics. This dimension owns the Oblivion-only bands and the corpus.
Paths: crates/nif/src/header.rs, crates/nif/src/version.rs, crates/nif/src/stream.rs, crates/nif/src/lib.rs, crates/nif/src/blocks/ (controller/, properties.rs, legacy blocks), crates/nif/tests/
First step: the Phase 1 corpus lane; git log --since=<date> --format='%h %cs %s' -- crates/nif/src/header.rs crates/nif/src/blocks crates/nif/src/lib.rs
- Corpus gates are the guard: 0 truncating, 0 unknown, per-block histogram equal to
oblivion.tsv(per_block_baseline_oblivion,oblivion_block_count_parity,no_block_sizes_drift_detector_has_zero_false_positives_on_real_corpus). Coverage differs: the histogram gate walks onlyOblivion - Meshes.bsa(8 032 NIFs —Game::mesh_archiveskeeps DLC optional), whileoblivion_block_count_parityadds the 8 DLC archives (9 612) — a DLC-only histogram shift is invisible to the former.unknowngrowth orparsedshrinkage is a stride-drift regression — escalate, do not re-derive; diff the histogram for new block types. - Header quirks (each pinned; aim at what the pin cannot see — a new off-band version):
user_versionexists only for >= v10.0.1.8; BSStreamHeader is the nif.xml dual bandV10_0_1_2 || (user_version >= 3 && (V20_2_0_7 || V20_0_0_5 || (V10_1_0_0..=V20_0_0_4 && user_version <= 11)))(bs_stream_header_not_read_for_off_spec_version, #170); v10.0.0.0..10.1.0.114 blocks carry a leading 4-byte group_id (mixing stream-relative and file offsets is the classic false trail); pre-5.0.0.1 files inline block-type names, decided by version, not by an empty type table (uses_inline_block_type_names, #4257). NiTexturingPropertyreads au32shader-map count directly — no leadingHas Shader Texturesbool (nif.xml is wrong; Gamebryo 2.3 is authoritative). Pin:parse_ni_texturing_property_with_zero_shader_maps(crates/nif/src/blocks/properties_tests.rs, #149).NiGeomMorpherControllertrailing field gates onbsver >= bsver::MORPH_LEGACY_CUTOFF(10;blocks/controller/morph.rs):doghead.nif(v10.2.0.0 bsver 9) must skip it, bsver-11 rigs (obgatemini01.nif) keep it (path_lookat_tests.rs, #1509).- The sizeless runtime-size-cache recovery must refuse a skip that contradicts the failed block (#3926,
crates/nif/src/lib.rs) — a plausible-looking recovery is silent mis-alignment. - Scene walk:
as_ni_node(crates/nif/src/import/walk/mod.rs) unwraps every NiNode-wrapper subclass (BSOrderedNode,NiBillboardNode,BsFaceGenNiNode, …); a missed subclass drops the whole subtree.NiSwitchNode/NiLODNodedeliberately takeswitch_active_childreninstead (active index / LOD 0 only). All five walkers share a per-walk visited bitset +MAX_NIF_NODE_DEPTH(revisit_guard, #5485,cf8730adc; hostile-input defense owned by/audit-nif— vanilla trees visit each block once either way). - Deep-trace 3 meshes (Heinrich Oaken Halls chandelier, a book, a creature head) through
import_nif_scene: mesh count + material chain. Tools:nif_stats,recovery_trace(crates/nif/examples/).
Output:/tmp/audit/oblivion/dim_1.md
Dimension 2: BSA v103 & ESM Data Slice (Oblivion.esm)
Subagent: general-purpose
Routing: reader discipline /audit-parsers; GRUP walk / SubReader / remap / dispatch /audit-esm. This dimension owns TES4-only decode branches and their real-data pins.
Paths: crates/bsa/src/archive/, crates/plugin/src/esm/records/ (actor/, items.rs, climate.rs, pathgrid.rs, misc/dialogue.rs, misc/pack.rs), crates/plugin/src/esm/cell/walkers.rs
First step: cargo test -p byroredux-bsa --release --test bsa_real -- --ignored oblivion, then cargo test -p byroredux-plugin --release --test parse_real_esm -- --ignored clas_oblivion_knight race_oblivion (single named tests).
- BSA v103 is a regression guard (#699):
oblivion_all_bsas_v103_brute_force_extract_zero_errorssweeps all 17 archives; the 35 zero-byte.txtplaceholders Oblivion stores compressed extract as empty files —BsaArchive::extractdecides the empty-stream edge itself before any decoder, because flate2 1.1.10 reports an empty zlib stream asUnexpectedEof(#5073 / #5660,fa2af0e89;extract_returns_empty_for_a_zero_size_compressed_placeholder,extract_rejects_a_nonzero_size_with_no_compressed_streamincrates/bsa/src/archive/tests.rs);embed_file_namesgates on>= BSA_V_FO3_SKYRIM, and the "Xbox archive" flag several v103 archives set must not change name handling. - 16-byte ACBS (#1650): Oblivion
NPC_/CREAACBS is 16 B (flags u32 @0, level i16 @10), distinct from the >=24 B FNV/FO3/Skyrim layout, soparse_npcneeds theGameKind::Oblivionarm gated onlen >= 16before the FNV arm (else level defaults to 1 and every actor reads Male). Pins:oblivion_16byte_acbs_parses_level_and_gender,fnv_ignores_16byte_acbs. - DIAL/INFO selection: every INFO authors
QSTI(its own quest), decoded intoInfoRecord.questand applied at selection by the FO3/FNV dialogue path (#5271,dbc07e8f0; consumerbyroredux/src/systems/npc_dialogue.rs). Every Oblivion activation opens through the genericGREETINGtopic (#5367 Phase G), so the positional Random stack is live here: the first passing INFO wins, and a Random one rolls with the immediately following passing Random INFOs up to a plain line or a Random End (Flags1bit 5; GREETING carries 461 Random INFOs in 102 runs, 57 Random End) (#5397,0436bea5c;earlier_random_set_wins_over_a_later_plain_line,random_end_and_plain_lines_bound_the_random_stackincrates/scripting/src/dialogue.rs). No dialogue live gate runs Oblivion (docs/smoke-tests/dt1-dialogue-layers.shis FNV) — the rule is unit-pinned, not verified in a conversation. Topic reachability is a separate, latent gap (#5350, above): the P4 classifier treats every branch-less Oblivion topic as a list entry, not the known-topics set. - Legacy leveled lists (#4638): TES4
LVLOrows may be 8 bytes with no count (default 1; 94 rows / 14 lists) andLVLDbit 0x80 is the all-levels flag, not chance-none. Pins:oblivion_8byte_lvlo_rows_decode_with_default_count,lvld_high_bit_is_flag_not_chance_none(crates/plugin/src/esm/records/container.rs). - EFID is a 4-char effect code, not a FormID: SPEL/ENCH/ALCH/INGR effect ids resolve through
EsmIndex::resolve_magic_effect->magic_effects_by_codeon Oblivion (#5084,ce73f658b; 142 of 149 distinct SPEL codes resolve,resolve_magic_effect_routes_oblivion_efid_codes) and are kept out of the load-order FormID remap (#5076,9b099b31f). A new effect consumer that indexesmagic_effectsdirectly silently no-ops on Oblivion. - Corpses are marked on the base actor:
NPC_/CREArecord-header bit 0x80000 Starts Dead, decoded only underGameKind::Oblivionand ORed onto placements (#5013,dca2bb401). Pins:oblivion_only_base_starts_dead_bit(crates/plugin/src/esm/records/dispatch_actor.rs),oblivion_starts_dead_bases_and_their_placements_match_the_audit_census(87 NPC_ + 48 CREA bases, 787 placements). - Legacy PACK shapes (#5504,
dae08000d):parse_packaccepts Oblivion's 4-bytePKDT(u16 flags, u8 procedure, junk byte) beside the 8-byte FO3/FNV form, and the 12-bytePTDT(type, target, count — no trailing f32). Before this 479 packages read as Find (650 placements) and no package carried a target, so all 208 Follow packages idled. Pins:oblivion_legacy_pkdt_and_twelve_byte_ptdt_decode(misc/pack.rs),parse_rate_oblivion_pack_legacy_pkdt_and_ptdt(crates/plugin/tests/parse_real_esm.rs,--ignored). - Other TES4 branches: MGEF-by-code map (
oblivion_mgef_populates_magic_effects_by_code), CONT 4-byte payload guard, CLMT three-entry WLST keyed onGameKind, RACE/CLASis_oblivion/flags_oblivionarms (real-data pins above), XCLL sizes[28, 32, 36]with the >= 92 ambient-cube arm game-validated to Skyrim/FO4/FO76 (crates/plugin/src/esm/cell/tests/cell.rs). - CLOT enters inventory as a wearable
ItemKind::Armor(parse_clot: BMDT slots, MODL/MOD3 male/female meshes; SLGM likewise). Pin:oblivion_clothing_enters_inventory_and_gender_aware_equipment(records/tests.rs). TheEsmIndex.clothingdoc now carries the measured 604 (#5086,e7e8829b9). - Navigation is PGRD-only (
pathgrid.rs, 8 228 grids; layout census in its header). As of 2026-09-29CellData.pathgridshas no consumer beyond the walker:docs/engine/navmesh-pathfinding.mdcovers NAVM only, so Oblivion packages have no path graph. - DIAL/INFO: with zero
PNAM/ANAM, speakers resolve through theGetIsIDcondition fallback andbuild_conversation_treemust order without PNAM chains (#3600); TCLF / NAME / CTDT and multi-segment responses are kept (#3614, #3616); LVSP (306) dispatches (#3617); SBSP (33) / ROAD (2) are recorded inRecordTypeas deliberate non-goals (#3619).
Output:/tmp/audit/oblivion/dim_2.md
Dimension 3: Legacy-Property Rendering Path (highest historical yield)
Subagent: renderer-specialist
Routing: the single material boundary / no render-time fallback / emitter translation /audit-nifal; Disney-BSDF gate (MAT_FLAG_PBR_BSDF must be 0, no BGSM/.mat) /audit-renderer.
Paths: crates/nif/src/import/material/ (legacy_properties.rs, walker.rs), byroredux/src/npc_spawn.rs, byroredux/src/npc_spawn/seam_blend.rs, byroredux/src/cell_loader/spawn/mesh_instance.rs, byroredux/src/scene/nif_loader.rs, byroredux/src/render/static_meshes.rs, byroredux/src/asset_provider/texture.rs, crates/renderer/shaders/include/material_sampling.glsl
First step: cargo test -p byroredux --bin byroredux parallax_alpha_gate (rustup toolchain, see Phase 1) and git log --since=<date> --format='%h %cs %s' -- crates/nif/src/import/material byroredux/src/render byroredux/src/cell_loader/spawn
APPLY_HILIGHT2is Oblivion's parallax flag. The importer recordsparallax_height_in_alphaon the intent alone (parallax_map.is_none()) because Oblivion authors no normal slot (14bump_textureuses in the whole mesh archive pair); normals resolve by filename (derive_normal_map_path, #1303) downstream ofMaterialInfo, and the asset provider binds that_n.ddsinto the height slot (mesh_instance.rs,nif_loader.rs, #3596). Height is the normal's alpha: when the bound normal has none (BC1/BC4/BC5 returnA = 1.0, sliding the whole surface) the render site must withholdPARALLAX_ALPHA_HEIGHT_BITand zero the height slot (#3562, #4260). Guard:byroredux/src/render/parallax_alpha_gate_tests.rs; it cannot see the real alpha-format split — 2026-09-11 census: 1 274APPLY_HILIGHT2properties, 100 with a BC1_n.dds; re-measure.- The bit is bit 31 of
parallaxMapIndex: every shader reader must mask it (textures[0x8000000N]is out of bounds) and both POM marchers (material_sampling.glsl, secondary-rayray_hit.glsl) must honor the channel. Scale defaults are the namedDEFAULT_PARALLAX_*constants, never literals (#4263). - Apply modes 1 (
APPLY_DECAL) and 3 (APPLY_HILIGHT) are decoded and unconsumed: 681 of 30 121 instances are non-default and their PC semantics are unsourced (#3625, 2026-09-11) — no value is guessed for them. - Legacy properties (pins in
crates/nif/src/import/material/:emissive_source_tests.rs,alpha_flag_tests.rs,double_sided_tests.rs,stencil_state_capture_tests.rs): raw monitor-spaceNiMaterialPropertycolors; all 11 AlphaFunction values routed, per-slot blend fallback (#4262);NiWireframeProperty-> LINE pipeline andNiShadeProperty.flat_shading-> fragment shader (#869);NiStencilPropertydormant except the two-sided promotion (#930). The dark-map role is live only on Oblivion (8 meshes / 6 files, #4523):dark_texture_role_population_matches_the_documented_census(crates/nif/tests/translation_completeness.rs,--ignored) anddark_combine_is_the_pinned_bare_multiply_in_both_paths(crates/renderer/src/vulkan/scene_buffer/shader_contract_tests.rs). - NPC body assembly (
humanoid_body_paths,byroredux/src/npc_spawn.rs): Oblivion has its own kf-era table —upperbody/lowerbody/hand/footplusfemale*twins, biped bits 0x04 / 0x08 / 0x10 / 0x20 — never FO3/FNV'slefthand/righthand, which its archive does not ship. Each part skins from the RACE body-sectionICONrun (INDX 0 UpperBody .. 4 Tail,RaceRecord::body_part_textures; 15 / 15 races author 8), swapped in at import so the shared NIF cache keeps the Imperial skin every body NIF authors (#5487,1b45763fa;body_paths_kf_era_match_each_games_archive,installed_oblivion_body_paths_exist_in_the_meshes_archive(--ignored) inbyroredux/src/npc_spawn/tests.rs). Pre-fix most humanoids spawned handless, partly dressed ones legless, and beast/mer torsos human. - Spawn-time NPC skin seam blend (
seam_blend.rs; checklist in/audit-fnvDim 4) bakes a tone ratio into head/hand vertex colours and now reads the race-overridden body skins — verify on Oblivion's owntextures\characters\skins that necks tone against the race's torso, and no armour/clothing mesh is blended into. - Emitters: 140 of 208 emitter-bearing Oblivion NIFs carry several
NiPSysEmitters (obgatemini01.nif11,transformation.nif13); eachImportedParticleEmitterneeds its own kinematics / curve / rate, not the first match (#4261, then #4467 when that fix lost rates behind opaque sibling controllers, and #4560 for the legacyNiPSysEmitterCtlrDatatier);APP_CULLED/ editor-marker particle leaves are skipped in both walkers (#4561). Oblivion's leg ofreal_archive_torch_meshes_surface_particle_emitters(--ignored) is the guard; the pre-Skyrim emitter layout is version-gated (#1239).
Output:/tmp/audit/oblivion/dim_3.md
Dimension 4: Exterior & Lighting Data (Tamriel)
Subagent: general-purpose
Routing: WTHR / sun / terrain / LOD / water translation mechanism -> /audit-exterior. This dimension checks the Oblivion-only inputs.
Paths: byroredux/src/cell_loader/ (placement_lod.rs, terrain.rs), byroredux/src/env_translate/, byroredux/src/systems/light_anim.rs, crates/plugin/src/esm/cell/, docs/engine/exterior-readiness-plan.md
First step: docs/smoke-tests/m-exteriors.sh oblivion static (needs a Vulkan device), and git log --since=<date> --format='%h %cs %s' -- byroredux/src/cell_loader byroredux/src/env_translate
_far.nifplacement LOD is real only here (placement_lod_supported_is_oblivion_only; FO3/FNV ship none and useObjectLodScheme::FalloutLegacyBlocks): confirm placements and LOD textures still resolve on a Tamriel exterior.WTHR.HNAM(Oblivion-only HDR block, 37 / 37 weathers): the sunlight dimmer rides the EXAL boundary ontoWeatherDataResand multiplies the sampled sun colour; absence is a neutral 1.0 and a short/degenerate block must clamp, not go negative (sunlight_dimmer_translates_from_the_hnam_block,env_translate/sky_weather.rs).- LTEX / default land textures: Oblivion
LTEX.ICONis relative tolandscape\and rooted there at the parse boundary (226 of 229 resolve; three ship nowhere), default land texture isDefault.DDS(DefaultLandTexture::for_game), and layers withoutTX01take the diffuse's_nsibling. Pins (--ignored,byroredux/src/cell_loader/terrain.rs):oblivion_ltex_paths_exist_in_vanilla_archives,default_land_textures_exist_in_vanilla_archives. A checkerboard on lake/sea beds means a missing key. __MAX_Default_Light(the 3ds Max key/fillNiDirectionalLightpair on 48 Oblivion meshes, 0 on FO3/FNV) never spawns as a scene light (#5189,e44152d45;byroredux/src/cell_loader/nif_light_spawn_gate_tests.rs); the Gilded Carafe runtime baseline'slight_count_directionalis 0 — a 1 there is this artifact back.- Child-worldspace inheritance: Oblivion authors no
PNAM, socrates/plugin/src/esm/cell/wrld.rsstamps the land / LOD / water / climate inherit bits on every child (WNAMset) at parse — pre-FO3 "child inherits everything"; 54 / 54 roots authorNAM2, 0 / 30 children do (#5374,67afa4b95). Bravil's canals, Leyawiin's river, the Imperial City districts and New Sheoth resolve the parent's water (default_water_for_worldspace, pinoblivion_child_inherits_parents_water_through_the_parse_stamp); nothing consumes the land / LOD bits yet (#5505). This parse-time stamp is the only inherit-all implementation (#5516,65cadde2f, deleted the dead flag-lessinherit_all_up_chaincopy andoblivion_climate_rungs' unconditional first chain step); a fixture with zero flags no longer exercises Oblivion inheritance. Pin:oblivion_child_gets_the_inherit_all_stamp_and_eras_stay_real(crates/plugin/src/esm/cell/tests/wrld.rs: child0x1B, root 0, an authored FO3+ zero stays zero). - Distant water is live (#5335,
e38ff49d2): Oblivion authorsNAM2but noNAM3/NAM4/DNAM, so the oldNAM4gate suppressed all of it.resolve_distant_water(env_translate/lod.rs) takes the full-detail resolver's height / form (default_water_for_worldspace) and prefers the authored LOD form only whereNAM3exists; the mesh is holed by the resident cell set (is_resident), not a radius. A flat sea missing past the loaded ring on a Tamriel exterior is this regressing. - Climate — one boundary,
resolve_exterior_climate(byroredux/src/env_translate/climate.rs; #5423,b3e679dba, moved the rungs out of the cell loader; #5584,c140cb48e, splitenv_translate.rsintolod/climate/water/sky_weather/fallback): the WRLDCNAMup theWNAMchain (SEWorld children -> SEWorldClimate), then the centre cell'sXCLR-> REGNCNAM(region_climate_for_center, pinregion_climate_follows_xclr_order_and_neighbour_probesinexterior.rs) — inert on vanilla, which authors 0 REGNCNAMon 211 regions (#5421) — then the Oblivion-onlyoblivion_climate_rungs(#5388,217bcbc9f; inherit-all is already resolved by the stamp-gatedresolve_worldspace_climatethat runs first, #5516): the CS naming convention<worldspace>Climatealong the whole chain root included (vanilla authors no Tamriel WRLDCNAM, so Tamriel and its city/district children resolveTamrielClimate, WLST Clear 100), and the richest-weather climate as awarn-logged last resort (pre-fix all 30 children fell toAllWeather). Pins:oblivion_child_inherits_the_parent_authored_climate,oblivion_child_finds_the_named_climate_through_the_chain_root,richest_climate_is_the_logged_last_resort. A generic warm-band palette + fallback clock, or thatwarn, on a Tamriel exterior means the chain fell through;cell_climate_probe(crates/plugin/examples/) prints it per grid. - LIGH falloff: an authored 0.0 means "default", which is the quadratic 2.0 pre-Skyrim (
falloff_exponent_sentinel_resolves_per_layout_generation,light_anim.rs) — not the Skyrim 1.0. - Embedded
NiControllerSequenceanimation (423 files / 792 sequences: gates, banners) must reach the ECS on cell load (#3602); frozen animated statics are the symptom. Repeatable gate:m-exteriors.sh oblivion static|boundary|soak|cycle|waterand/audit-runtime --game oblivion.
Output:/tmp/audit/oblivion/dim_4.md
Dimension 5: Gameplay & UI Data Slice (M47.3 ObScript quests, MenuXml HUD)
Subagent: general-purpose
Routing: quest-script runtime /audit-scripting; HUD drivers / MenuXml eval-layout-raster /audit-ui, parse side /audit-parsers; gameplay mechanism /audit-gameplay.
Paths: crates/scripting/src/obscript_vm.rs, crates/scripting/src/obscript_quests.rs, byroredux/src/hud.rs, crates/menuxml/, byroredux/src/cell_loader/references/attach.rs
First step: cargo test -p byroredux-scripting --release -- --ignored vanilla_oblivion_quest_scripts_execute_and_setstage (BYROREDUX_OBLIVION_DATA, ~160 MB resident; it fails rather than passes without data under BYROREDUX_REQUIRE_GAME_DATA=1 and runs as a named step in .github/workflows/real-data-gates.yml — #5088, 1377d3463) and git log --since=<date> --format='%h %cs %s' -- crates/scripting crates/menuxml byroredux/src/hud.rs
- M47.3 (mechanism
/audit-scripting): 255 vanilla QUST scripts install once per load order and tick every 5 s (fQuestProcessInterval); command ids were derived empirically fromOblivion.esmand 2 349 of 2 393 SCPT decode clean (residue: per-command string-argument signatures); unknown commands are counted, never faked. Reference operands index the SCRO and SCRV subrecords interleaved in record order (ScriptRef;SCHR.num_refscounts both, 2 393 / 2 393), a reference-variable slot resolves to nothing and an unresolved explicit caller is declined (#5629,aac6b25b3— before it 39 of 511 quest-stage calls named the wrong quest); a tag-rSetisset <ref>.<remote var>and is declined and counted, not stored as a ref assignment (#5630); argument payloads are bounds-checked against the statement's argument region (#5655,291af4528). Oblivion's dialect isObse(obscript_dialect_follows_the_profile_not_the_game_kind). Object-script blocks,MessageUI and actor-state functions are phase 2 (ROADMAP M47.3). - MenuXml HUD (
--hud,HudGameProfile::oblivion): the XML authors the art, so the driver pushes only trait overrides (hudmain_health_full/hudmain_magic_full/hudmain_fatigue_full,hudmain_compass_windowuser0,HUDMainMenuuser3); XML + 5 font slots come fromOblivion - Misc.bsa, art fromOblivion - Textures - Compressed.bsa(threeMenus/Menus80/Menus50sets). The MenuXml DDS decoder (crates/menuxml/src/tex.rs) takes 16-bpp A4R4G4B4 / R5G6B5 and zero/narrow-mask L8 / A8 headers (#5400,5c53ad9c7; #5377,015e10549) — 49 vanillatextures\menus\files that warned "not found" now decode, and an undecodable candidate warns as such; pinmenu_texture_header_census_decodes(crates/menuxml/tests/vanilla_corpus.rs, zero undecodable headers). Bars key on AVIF editor idsHealth/Magicka/Fatigueresolved per load throughPlayerVitals(#4675);Oblivion.esmauthors no AVIF records and the legacy actor-value resolver is unbuilt (ROADMAP Status), so none resolve and the bars draw full unlesshud.valuespins them. (The comment aboveHudBarinhud.rsstill describes the old Skyrim-keyed0x3E8read — doc rot, not behaviour.) Guards:crates/menuxml/tests/vanilla_corpus.rs(BYROREDUX_OBLIVION_DATA),docs/smoke-tests/m48-4-oblivion-hud.sh. The driver, throttle, overlay and eval/layout/raster are shared ->/audit-ui. - Playable gate:
docs/smoke-tests/p0-door-interaction.sh oblivionis the only gateoblivion.envdeclares; an undeclared gate exits 2 (unmeasured, not covered).
Output:/tmp/audit/oblivion/dim_5.md
Phase 3: Merge
- Read all
/tmp/audit/oblivion/dim_*.md; combine intodocs/audits/AUDIT_OBLIVION_<TODAY>.md:- Executive Summary — compatibility level (NIF parse incl. v10.x tail, archive extract, ESM parse, render); state the corpus-lane result first; cite ROADMAP / feature-matrix numbers.
- Dimension Findings — grouped by severity per dimension.
- Regression Guard List — stride-drift family (#1506-#1509), #170 dual band,
NiTexturingPropertyu32 count, BSA v103 sweep,parallax_alpha_gate_tests, Disney gate stays 0. - Open Work — from
ROADMAP.mdKnown Issues; interiors and the Tamriel exterior already render, so neither is a blocker.
- Remove cross-dimension duplicates.
Suggest: /audit-publish docs/audits/AUDIT_OBLIVION_<TODAY>.md
(label every finding game:oblivion + legacy-compat, plus its own domain label.)