Synthèse automatique
Audite le parseur ESM/ESP du crate plugin (parcours GRUP, comptabilité d'octets des sous-enregistrements, remap FormID, walkers CELL/WRLD, handoff EsmIndex→ECS) selon huit dimensions exécutées par des agents Task.
Points forts
- Périmètre très explicite : huit dimensions nommées, fichiers cibles listés, et exclusions renvoyées vers les skills voisins (/audit-scripting, /audit-nif, /audit-exterior…).
- Exige une source de vérité (docs/engine, plages wbDefinitions*.pas) ou un test pour chaque décodage non trivial, et impose de signaler toute supposition.
- Réutilise un protocole partagé de déduplication et de sévérité, et va chercher les doublons dans les rapports d'audit existants du dépôt.
- Paramétrable par --focus, --game et --depth, avec parallélisation plafonnée à trois agents.
Limites
- Fortement lié à un dépôt précis (ByroRedux) : chemins, noms de crate et commandes gh/cargo codés en dur, difficile à réutiliser tel quel ailleurs.
- La validation en profondeur dépend de masters de jeux présents sur disque et de l'accès à l'historique des issues GitHub.
- Ne couvre pas la consommation aval des données (cell-loader, VMAD, NIF, BSA/BA2), volontairement déléguée à d'autres audits.
À utiliser lorsqu'on modifie ou débogue le parseur de plugins Bethesda de ce dépôt et qu'on veut un audit structuré par dimension avant ou après un refactor.
Inadapté pour auditer un autre dépôt ou un format non Bethesda, ou pour vérifier le comportement runtime des données de cellules chargées.
Analyse de sécurité
PrudenceThe skill is a legitimate, narrowly-scoped code-audit orchestrator: it reads repo files, greps, runs cargo tests/examples, files nothing, and writes Markdown reports under docs/audits/. No credential exfiltration, no obfuscated payloads, no curl|sh, no safety-disable. The only meaningful exposure is routine shell/network use (gh), heavy real-data parsing with known OOM history, and a fixed-path `rm -rf` in /tmp — hence 'caution' rather than 'safe'.
- •Phase 1 step 2 shells out to `gh issue list --repo matiaszanolli/ByroRedux`, i.e. an outbound network call to GitHub with the operator's CLI credentials; benign read-only query but it is live network access from an agent-run skill.
- •Phase 1 step 4 and Dim 8 run `cargo test -p byroredux-plugin` and `cargo run --release --example ...` against whole game masters (Starfield ≈4 GB parsed); the skill itself notes prior OOM kills past 20 GB. Resource-exhaustion risk on the host, mitigated (but not eliminated) by the explicit 'never run --ignored' instruction.
- •Phase 4 performs `rm -rf /tmp/audit/esm` — a fixed, non-user-scoped path in a world-writable directory; low risk but a fixed destructive path is worth noting.
- •No prompt-injection or instruction-override attempt found; the many 'never do X' / 'do not flag Y' lines are scoping guidance addressed to the auditor, not attempts to subvert this assessment.
- •No `allowed-tools` declared, yet the workflow requires shell execution (git, gh, cargo, mkdir, rm -rf), network access (`gh issue list --repo ...`), and writing artifacts into `docs/audits/`. The capability surface is broader than the declared manifest, which is a governance gap rather than a malicious one.
- •Phase 4 runs `rm -rf /tmp/audit/esm` — the target is a hardcoded, skill-created temp directory, so the blast radius is contained; still, it is an unguarded recursive delete executed by an agent and would be safer as a path-validated or non-recursive cleanup.
- •Phase 8/1 execute locally built binaries against multi-gigabyte masters (`cargo run --release --example esm_dim8_coverage`, `sf_smoke`); resource-heavy but the skill explicitly warns against `--ignored` runs and parallel engine launches due to prior OOM/session-kill incidents — a safety-conscious control, not a risk.
- •The skill parses and reports on untrusted third-party plugin binaries (ESM/ESP); the content directs that parse failures be treated as diagnostics rather than panics, which is appropriate defensive framing.
- •No exfiltration, no `curl | sh`, no base64 payloads, no safety-disabling, no credential/token handling, and no attempt to redirect the auditor's role or instructions. Repo references (matiaszanolli/ByroRedux) are consistent throughout.
Exemples
/audit-esm --depth deep/audit-esm --focus 3,4 --game skyrim --depth shallowRun the ESM parser audit focused on sub-record byte accounting and the ESM→ECS handoff for Skyrim in deep mode, after my SubReader changes.description: "Deep audit of the ESM/ESP plugin parser — GRUP walk, sub-record byte accounting, per-record schemas, FormID remap, CELL/WRLD walkers, ESM→ECS handoff" argument-hint: "--focus <dimensions> --game <name> --depth shallow|deep"
ESM / Plugin Parser Audit
Audit crates/plugin/ (~73k LOC in src/) as a parser: GRUP walker, sub-record byte
accounting, per-record schema dispatch, FormID load-order remap, CELL/WRLD walkers, and
the EsmIndex → ECS handoff. Per-game audits (/audit-fnv, /audit-skyrim, …) each
sample one game's slice; this skill owns the parser itself.
Architecture: Orchestrator. Each dimension runs as a Task agent (max 3 concurrent).
Read .claude/commands/_audit-common.md and .claude/commands/_audit-severity.md for
shared protocol (dedup, methodology, finding format). Do not duplicate them here.
Scope
Crate: crates/plugin/src/ — esm/reader.rs (EsmReader, EsmVariant, GameKind,
FormIdRemap, GlobalSlot, zlib decompression), esm/sub_reader.rs (SubReader),
esm/records/ (parse.rs = the top-level GRUP walker parse_esm[_with_load_order];
mod.rs is a re-export barrel; index.rs EsmIndex; eight dispatch_*.rs routers;
grup_walker.rs; per-type decoders incl. actor/, items.rs, items/consumable.rs,
container.rs, condition.rs, weather.rs, weather_settings.rs (Starfield WTHS), load_screen.rs,
pkin.rs, misc/*.rs incl. story_manager.rs, location.rs (LCTN), relationship.rs (RELA)),
esm/cell/ (walkers.rs, support.rs, wrld.rs, helpers.rs, mod.rs data types),
esm/strings_table.rs, equip.rs, and the Redux-native tier
(datastore/manifest/record/resolver).
Not in scope: cell-loader consumption of CellData (/audit-<game> Dim 1); VMAD
translation (/audit-scripting); NIF (/audit-nif); BSA/BA2/CSG incl. the FO4 previs
header → cell-grid recovery (/audit-parsers); src/extension.rs + crates/sdk extension
manifests (/audit-tooling); WTHR/CLMT/WATR/LTEX/GRAS translation and HNAM sun
consumption (/audit-exterior — byte-level decode of those records stays here);
consumption semantics of src/consumables.rs / expand_leveled_loot (equip.rs) —
timed restoratives, container loot, LSCR selection (/audit-gameplay; the decode contract
stays here, Dim 4); CHARAL formulas (/audit-character).
Ground truth: docs/engine/plugin-loading.md, docs/engine/esm-records.md,
docs/engine/pipeline-overview.md. Every non-obvious decode should cite a
wbDefinitions{TES4,FNV,TES5,FO4,FO76,SF1}.pas:line range (crate docstring in
crates/plugin/src/lib.rs); a decode with no citation and no test is a guess
(feedback_no_guessing) — flag it.
Parameters (from $ARGUMENTS)
--focus <dimensions>: comma-separated dimension numbers. Default: all 8.--game <name>: restrict Dim 8 real-data validation to one game. Default: every game with on-disk data (_audit-common.md§ Game Data).--depth shallow|deep:shallow= schema/contract check;deep= byte-level trace against real masters. Defaultdeep.
Extra Per-Finding Fields
- Dimension: Header & GRUP Walk | Sub-Record Byte Accounting | FormID & Load Order | Record Schema Dispatch | CELL / WRLD Walkers | Localized Strings | ESM→ECS Handoff | Real-Data Validation
- Record / Sub-record: 4-char code(s) (e.g.
NPC_/ACBS), or—.
Phase 1: Setup
- Parse
$ARGUMENTS.mkdir -p /tmp/audit/esm. gh issue list --repo matiaszanolli/ByroRedux --limit 200 --json number,title,state,labels > /tmp/audit/esm/issues.json.- Read the latest
docs/audits/AUDIT_ESM_*.mdplus the ESM dimensions of the latestAUDIT_{FNV,SKYRIM,FO4,STARFIELD}_*reports — this crate's findings historically live there, so that is where your duplicates are. cargo test -p byroredux-plugin; record the pass count (a pre-existing failure is context, not a finding). Never run--ignored/--include-ignoredon this crate — the real-data tests parse whole masters (Starfield ≈ 4 GB parsed) and have OOM-killed sessions past 20 GB; use Dim 8's single-file probes instead.
Suggested emphasis: the 2026-08 → 09-11 reports yielded mostly Localized Strings,
ESM→ECS Handoff, CELL/WRLD and FormID remap; the 2026-09-21 report's MEDIUMs were Byte
Accounting (Oblivion LVLO/LVLD, FO4 TERM, LTEX.GNAM) and Header/GRUP (inflation
ceiling, skip-arm clamp), all found by Dim 8's real-data census — run it, don't skip it.
The 2026-09-29 report's MEDIUM/LOWs were again Byte Accounting + FormID: non-FormID u32s
fed to the remap (FO4 TRDA FFFF sentinel, Oblivion EFID char4 codes), fixed in
9b099b31f (#5075/#5076) — see Dim 3 shape (4). The 2026-10-08/10-09 MEDIUMs were decodes that
contradict their own wbDefinitions*.pas entry (SM SNAM/RNAM, REGN WNAM, PKIN VNAM,
SCOL/PKIN FLTR, PKDD Dialogue Type offset — all since fixed, #5493 136048a95 / #5492 dae08000d) — open the xEdit definition for every decoder
touched since the last report rather than trusting its doc comment.
Phase 2: Dimensions
Dimension 1: Header Detection & GRUP Walk (highest blast radius)
Paths: crates/plugin/src/esm/reader.rs, esm/records/{parse,grup_walker}.rs
First step: grep -rn 'group_content_end(' crates/plugin/src | grep -v 'tests/\|///'
Why first: a header-size or GRUP-bounds error desyncs the whole file; everything
downstream decodes structurally valid garbage.
Guards (run, confirm live — not #[ignore]d, not vacuous):
reader::tests::game_kind_from_header_maps_real_master_hedr_values,
grup_walker::tests::every_recursive_grup_walker_honours_the_shared_depth_cap (table of
walkers nested past MAX_GRUP_NESTING_DEPTH; a walker missing from the table is invisible
to it), reader::tests::bounded_group_content_end_clamps_to_parent_end, and the
compressed_record_* / inflation_ceiling_clears_every_observed_vanilla_shape tests.
Checklist (what the guards cannot see):
EsmVariant::detect(data[20..24] == b"HEDR"⇒ 20-byte Oblivion headers) has its length guard before the slice; no caller hardcodes a variant outside tests /with_variant.GameKind::from_header(variant, hedr_version, record_version)uses banded float compares plus the TES4 record version as a second key (Skyrim LE and FO3 GOTY both stamp HEDR 0.94 → record version 40 vs 2; FO4 DLC 0.95 → 131). Vanilla HEDR: FO3 0.94, FO4 1.0, Starfield 0.96, FNV 1.34, Skyrim SE 1.71, FO76 — a live-service value that drifts with patches (68.0 → 266.0 → 279.0 as of 2026-09-20; floor 60.0). A mis-band is not latent: FO4-only arms initems.rs(ARMO DATAswaps value/weight/health order at the same 12-byte length,WEAP DATAempty,BOOK8-byte) would silently swap armor stats and zero every weapon. Any new per-game schema split widens this blast radius — check.- Missing/short HEDR falls back to
GameKind::Fallout3NV(Default): confirm each call site is deliberate and no branch treats "defaulted" as "detected FO3". - Universal GRUP bound (#3503/#3721/#4076/#4644): every self-recursive walker takes
bounded_group_content_end(header, depth, parent_end, name)and threadsdepth + 1into its_inner; skip arms too seek to the parent-clamped end viaseek_to(the depth-cap arm insidebounded_group_content_endincluded). The rawgroup_content_endmay appear only in the non-recursive top-level loop (records/parse.rs, the onlyskip_groupcaller),cell/wrld.rs::parse_wrld_groupandgrup_walker.rs::walk_info_records' nested-group skip (both.min(end)themselves). Regression = a new recursive walker on the raw accessor, a droppeddepth, or a recurse/skip site that loses theparent_endclamp (guardsoverrunning_child_grup_in_a_skip_arm_seeks_to_parent_end,bounded_group_content_end_depth_cap_skip_is_parent_clampedpin fixtures, not every site). FLAG_COMPRESSED:data_size >= 4checked before the subtraction; declared size is bounded byrecord_inflation_ceiling(min(64 MiB, max(64 KiB, compressed×2048)), from a seven-master census — worst real ratio 785.9:1, a StarfieldSFTR, #4640; 512:1 rejected 82 vanilla records and dropped the whole plugin) and the decoder is held totake(declared + 1); mirrorsbyroredux_bsa::safety::inflate_bounded(#3410). Regression = either bound removed or the two implementations diverging. Output:/tmp/audit/esm/dim_1.md
Dimension 2: Sub-Record Byte Accounting (densest bug class)
Paths: crates/plugin/src/esm/sub_reader.rs, esm/records/**/*.rs, esm/cell/support.rs
First step: git log --since=<last report> --name-only --format= -- crates/plugin/src/esm/records | sort -u (decoders touched since the last report), then re-derive their widths from the cited xEdit range.
ESM analogue of /audit-nif Dim 1: a wrong field width shifts every later field and the
record still "parses" with wrong numbers.
Guard: records::test_support::tests::no_module_redeclares_a_local_subrecord_builder
(fixture hygiene only — nothing here guards decode widths; since #5454, 29f8fa2ef, it flags
any fn returning SubRecord built from a parameter 4-CC, not only fixed names).
Checklist:
- Decoders read through
SubReader; flag direct raw-slice indexing. - The
*_or_defaultfamily swallows short reads: fine for optional tails, a finding mid-fixed-layout (silent zeros). Classify every call site. - Fixed-size sub-records check the declared length against the schema; version-gated tails
are gated on
GameKindor remaining length with an xEdit citation. - Repeating rows (
CTDA,CNTO,NVTR/NVEX→decode_nvtr_row/decode_nvex_row,LVLO):len / stridewith an explicit remainder check; a non-zero remainder is a decode bug. rgb_color(3 B) vsrgba_color(4 B): swapped pairs shift every later byte. Colours are raw monitor-space floats — never flag a missing sRGB conversion (feedback_color_space).- Known offset maps — verify against the code, not older reports:
parse_raceTES5DATAstarting pools at 36/40/44 (new fields land at ≥48; TES4/FO3NV and FO4/FO76 arms differ); WATRdecode_data_fo3nvshared 0..28 prefix (wind speed/direction/amplitude/ frequency at 0/4/8/12, direction degrees-on-the-wire converted once), pinned byparse_watr_186_byte_record_reads_colors_at_40_44_48andwind_direction_converts_shipped_degrees_on_the_fo3nv_dnam_arm(this field moved twice). - Strings: terminator consumed exactly once; a missing terminator cannot read past the sub-record.
- Leveled lists (
parse_leveled_list_for_game): FO76 splitLVLO(4 B ref) + floatLVLV/LVIV/LVCVcompanions only underGameKind::Fallout76; the legacy row accepts ≥8 bytes (Oblivion omits Count → default 1) andLVLDbit0x80becomesLVLF 0x01, not chance-none 128 (#4638). A sub-8-byte legacyLVLOmust never become a valid reference. Output:/tmp/audit/esm/dim_2.md
Dimension 3: FormID Remap, Load Order & ESL Space
Paths: crates/plugin/src/esm/reader.rs (FormIdRemap, GlobalSlot), esm/records/tests.rs, esm/records/common.rs, byroredux/src/cell_loader/load_order.rs
First step: cargo test -p byroredux-plugin -- every_record_parser_takes_a_remap parsers_that_take_a_remap no_parser_with_a_remap remap_fid_has_exactly_one
Guards: records::tests::all_record_parsers walks records/ at test time and requires
every pub fn parse_* to take a remap parameter or sit in EXEMPT_NO_U32_READS
(mechanically re-verified) / EXEMPT_JUSTIFIED (written non-FormID reason);
parsers_that_take_a_remap_actually_use_it; remap_fid_has_exactly_one_definition;
no_parser_with_a_remap_in_scope_passes_an_identity_remap. The cell/ tier is guarded
structurally (remap is a required parameter of read_form_id). Review new exemption
entries in the diff — each is a human decision, not a proof.
What the guards cannot see — the class keeps recurring in three shapes:
(1) an unremapped field inside a shared decoder several parsers call
(CommonNamedFields::from_subs_with_remap, #4067); (2) a GRUP label that encodes a
FormID (DIAL topic-children label vs remapped DIAL id, #4079 — check every label-dispatch
site like a sub-record field); (3) a HashMap<u32, _> in EsmIndex keyed by, or compared
against, a raw plugin-local id. Inverse shape (4) — a non-FormID u32 routed into
remap_fid (only 0 short-circuits, so every row warns through the out-of-range arm on a
plugin with masters): xEdit wbFormIDCk([…, FFFF]) fields go through
remap_fid_or_sentinel, and Oblivion EFID (a wbChar4 effect code resolved via
magic_effects_by_code) through the game-gated remap_efid (records/common.rs;
9b099b31f, #5075/#5076); PKIN VNAM is xEdit's integer Version, stored raw as
PkinRecord.version (0d436e642, #5422, pinned by pkin_vnam_version_stays_raw_under_remap).
A new sentinel-bearing field, char4 code or plain integer calling remap_fid inline is the
regression. SCOL/PKIN FLTR is a wbString filter path: both parsers store filter: String
via read_zstring with no remap_fid (fixed in 136048a95, #5493; pinned by
pkin_fltr_filter_path_stays_raw_under_remap + parse_scol_fltr_decodes_the_filter_path).
CTDA param_1 is remapped only for functions whose first parameter is a FormID:
condition.rs::param1_is_form_id mirrors the scripting catalog by raw function index (rows
for fn 56/71/74 were missing until 722c06a76, #5637; M47.2 added the location/keyword/
relationship/quest-variable rows); param2_is_form_id is the second-parameter twin (fn 605/181/
576/606/608 — everywhere else param_2 is a literal); the drift guard lives in the scripting
crate (param1_form_id_table_matches_the_condition_catalog) — a new catalogued
FormID-param function needs its row.
Checklist:
GlobalSlot::Regularkeeps 24 bits;GlobalSlot::Light(0xFEspace) packs a 12-bit sub-index and keeps only the low 12 object-id bits (0x0FFFmasks on both sides);GlobalSlot::Medium(0xFD, Starfield) packs an 8-bit sub-index + 16-bit object id. The TES4 master bits are per game (#4639,read_file_header): Starfield small0x100/ medium0x400, Skyrim SE + FO4 light0x200, every other game none — a0x200test on Starfield (Update) or a pre-ESL game is the regression.allocate_global_slot(load_order.rs) must keep the three spaces disjoint.FormIdRemap::remaparms: raw0→0(null sentinel, must not become a live ESL id); self-reference (mod_index == master_slots.len()); in-range master; standalone with no masters (pass through,debug— the vanilla Oblivion0x01artifact, #1308; must NOT be "fixed" into a clamp — two forms would collide inEsmIndex); out-of-range with masters (pass through,warn).- Master list read in file order; a missing master fails diagnosably, not as a silently shifted slot table.
- Multi-plugin is live (
--masterrepeatable →parse_esm_with_load_order);parse_esmpassesNone. Verify every embedded FormID in a newly-touched parser goes throughremap_fid. The guard walk sees onlypub/pub(crate)/pub(super) fn parse_*inrecords/(files namedtests.rsand#[cfg(test)]modules excised) —decode_*/from_subs*helpers andcell/are outside it. - Parallel walk (#3813,
parse_record_indexes_in_load_order_with_archive): walks run on the rayon pool, but slot allocation, remap construction,.STRINGSloads and themerge_fromfold stay on the calling thread in load order — fold order, never completion order. Guard:load_order_parallel_tests::parallel_walk_matches_the_inline_walk_on_a_localized_master_chain. Output:/tmp/audit/esm/dim_3.md
Dimension 4: Record Schema Dispatch & Coverage
Paths: crates/plugin/src/esm/records/{parse,index,items,container,actor_value_derive,load_screen}.rs, records/dispatch_*.rs, records/misc/*.rs, crates/plugin/src/equip.rs
First step: cargo test -p byroredux-plugin -- dispatch_handled_fourccs every_index_map_is_a_category
Guards: records::tests::dispatch_handled_fourccs_matches_the_live_dispatch_arms
(DISPATCH_HANDLED_FOURCCS ↔ the live match in parse.rs; era-gated arms count as
handled, PDCL is deliberately absent; LCTN/LCRT/RELA are routed through dispatch_misc_stub into typed maps since M47.2 — parse.rs's DISPATCH_HANDLED_FOURCCS doc still says LCTN has no arm, that prose is stale);
index::tests::every_index_map_is_a_category_or_a_recorded_exclusion (every EsmIndex
map is a categories() row — map_category! or a hand-written row recognized by its count
closure, like dialogues — or a reasoned exclusion: record_types, the derived
generic_greeting_form cache); cell::plugin_loading_doc_pin_tests::the_documented_esm_cell_index_lists_every_field and
…::the_audio_record_split_matches_the_dispatcher (docs ↔ code).
Checklist:
- Build the live matrix per record type: typed decode / minimal stub
(
parse_minimal_esm_record, EDID+FULL —dispatch_misc_stub.rsalso hosts typed decoders:LSCR,CLOT/CCRD/CMNY→items) / skipped. Report the delta against record_type_catalog as coverage, not bugs. A stub must advance the reader correctly; the unknown-record path skips by declared size and neverwarns per record on a vanilla master. - Per-game schema splits within one record (
ARMO/WEAP/AMMO DATA,BOD2vsBMDTvs Skyrim's pre-BOD2BODT— 8 or 12 B, same leading biped mask, read in bothARMOandARMAsince479414ffe(#5358; every Skyrim.esm ARMA authors it, so BOD2-only decoding zeroed all 766 masks),SCOL/PKIN/TXST, FO76LVLO) switch onGameKind. Oblivion's 4-byteARMO BMDTis a u16 Biped Flags + General Flags byte — the mask is the u16 only (a8e37e5fd, #5696; a u32 read put General Flags in bits 16-23 on 661 of 996 armors); FO3/FNV keep the u32.ACREis not Oblivion-only (FO3 ships 3,349ACREvs 2,154ACHR) — flag any new per-game gate on it. - Consumables / items decode contract (decode only; consumption is
/audit-gameplay):ALCHeffect chains viaparse_alch_for_game;MgefRecord.instant_restoration_avviaparse_mgef_for_game; Oblivionparse_clot→ItemRecord;IMOD/CCRD/CMNYcarried as items;LVLImulti-pick (flag0x04) vs single-pick highest-eligible, pinned on the shipped FNV master.expand_leveled_lootretains unknown terminal ids and saturates counts — check it agrees with the decoder'sLeveledEntrysemantics. LSCR(parse_lscr, header flags viaextract_records_with_flags):LNAMstores grid Y before X; malformed known fields land inmalformed_fieldsrather than producing an unrestricted screen; truncated fixed fields are safe and visible.TPLT"Use Stats" resolves each absent stat down the chain to the deepest record that authors it (resolve_inherited_field, #4086;TPLT_MAX_DEPTH6) — the terminal outranks a shallower record and a one-hop chain is unchanged. Check new stat fields join the per-field walk instead of comparing only the chain ends.EsmIndex::merge_from: last-write-wins matches Bethesda override semantics;gameis only adopted whenother.total() > 0(a failed-parseEsmIndex::default()must not relabel the load order as FO3/FNV, #3403);character_rulesis first-non-NONEwins. Exception —dialogues(168b81322, #5375): an override DIAL replaces only the header fields and folds its INFOs into the master's list by FormID (same id in place, new id after itsPNAMpredecessor), and the prune arm drops Deleted-INFO tombstones from every topic; whole-recordextenderased 8.5–9.9k FNV INFOs per DLC. An override'sPNAM == 0run keeps authored file order through one per-override contribution cursor (fold_infos_into_topic,5356c8780, #5494 — the first contribution goes to the front, later ones follow the previous; the run's position relative to the master's heads is an unsourced engine choice). Since0c2bd4d6d(#5644) the fold is one pass over a FormID → node map;fold_info_into_topic_referenceis the test-only one-at-a-time twin andbatched_info_fold_matches_the_one_at_a_time_foldpins that the two agree. Top-level maps merge throughcategories();EsmCellIndex::merge_fromdestructures exhaustively (#4904 — a..there is the regression that droppedlandscape_grasses).- M47.2 added typed maps:
locations(LCTN,misc/location.rs),location_ref_types(LCRT),relationships(RELA,misc/relationship.rs),condition_forms(StarfieldCNDF),keywords(KYWDEDID only),SNDRinto thesoundsmap viaparse_sndr(soun.rs), RACE FO4SRAC/SADD/SAKD/SGNM/SAPT/STKD/SRAFsubgraph data,KWDAonCommonNamedFields, PERK entry bodies (PerkEntry/PerkEntryBody, EPFT/EPFD per the xEdit layout), and a "Use Base Data" template chain terminal (ResolvedNpc::base_data). TheEsmIndex::categories()count is pinned inrecords::tests(109 at HEAD) — read the live number there; each new map must be a category row or a recorded exclusion. actor_value_derive.rsis the CHARAL feed — formulas belong to/audit-character.- Settled decodes — verify they hold, do not re-derive:
INAMis one FormID array (#3356);CREA CNAMis not a class (#3383);FACTrank ladder (#3338);ARMOcontributes every race-matchingARMA(#3357),MOD3is the female mesh (#3414); aREFRtombstone removes the placement wherever it lives (#3362); FO76HEDRis patch-dependent — 279.0 on the 2026-09-20 patch, never pin it (#3405, #4643); DIAL quest ownership readsQSTIorQNAM(Skyrim authorsQNAM;parse_dial); FO3/FNV/Oblivion INFOs also carry their own owning quest inInfoRecord.quest(QSTI, remapped, 23,247/23,247 FNV INFOs;dbc07e8f0, #5271 — Skyrim+/FO4 author none,0= use the topic's ownership); INFODATAis typedInfoDataHeader(info_type / next_speaker / flags1 / flags2; Goodbye = Flags 1 bit 0) and Skyrim's 8-byteDATAlands raw inskyrim_data(25b678106, #5295);SMBN/SMEN/SMQNdecode intostory_manager_nodes(misc/story_manager.rs, #5366 closed — the SM dispatches live):SNAMis the previous sibling (xEdit Previous Node;846e4a6dd, #5385 — the same-parent census holds in both directions and cannot settle it),SMQNRNAMis stored hours × 24 and decoded ÷ 24 (xEdit scale1/24;a621d5352, #5386 — nodeHNAMstays raw), andXNAM/MNAM/HNAM/QNAM/FNAM/UNAM+DNAM0x2/0x40000are named from xEdit (5489cbe65, #5420; per-questFNAM/UNAMlive onSmQuestLinkbeside theNNAM/RNAMthey follow — a node-level field let each later quest overwrite the last,0edd1dede, #5515); INFO Flags 1 bit 5 = Random End (random_end,0436bea5c, #5397); REGNWNAMis the owning worldspace (worldspace_form, remapped), never a weather, and REGNCNAMis xEdit-undefined with zero vanilla occurrences (bd052048a, #5421);NPC_.WNAMis the per-NPC skin ARMO (worn_skin, remapped, no game gate — pre-Skyrim ships none;edb5fbdfe, #5359); OblivionRACE's body-sectionICONrun (INDX restarts at 0 pastNAM1) lands inbody_part_textures(1b45763fa, #5487); StarfieldWTHSdecodes the REFL/RDIF reflection schema only, not instance values (weather_settings.rs, #5364; the instance-value reader is open tracker #5627; the DIFF value walk is depth-capped atMAX_NESTING_DEPTH64,02187c7f9, #5687, pinned byhostile_self_referential_diffs_decline_at_the_depth_cap):DIFFis (u16 field index, inline value) pairs to the0xFFFFterminator andUSRD/MAPCjoin the instance census (f0f8dceef, #5514; real-data pins 163 in-range indices / 47USRD); the CDB builtin ids live in one table (1f56d5742, #5615); FO76LSCRdecodes on the modern (FO4-shape) arm incl.BNAM→ icon (47f927cbf, #5345); FO3/FNVPKDDDialogue Type is the u32 at offset 16, and OblivionPKDT(4 B) /PTDT(12 B) legacy shapes are accepted (dae08000d, #5492/#5504); FO3/FNV non-auto-calc NPCs keep their authoredDATASPECIAL inNpcRecord::data_attributes(4957536ba, #5338); FO4NPC_template flags resolve per flag throughtemplate_actors(TPTA, 13 entries in xEditwbTemplateFlagsorder,0= useTPLT;99c22a8db, #5498);MSWPCNAMis the Color Remapping Index palette row (color_remap_index,0b2ffed89, #5497);MGEFDATAoffsets/flags are named constants inmisc/magic.rs'smgef_data(abe8d7e96, #5614). equip.rs::main_body_bitFO76/Starfield arms are a provisional inference (PROVISIONAL (#4074), pinned byfo76_and_starfield_arms_are_marked_provisional) — known and marked; report only if the marker is removed without an xEdit citation. Output:/tmp/audit/esm/dim_4.md
Dimension 5: CELL / WRLD Walkers & Placement Data
Paths: crates/plugin/src/esm/cell/**/*.rs, esm/records/misc/world.rs, esm/records/spatial_units.rs
First step: git log --since=<last report> --format='%h %cs %s' -- crates/plugin/src/esm/cell crates/plugin/src/esm/records/misc/world.rs
Checklist:
- Cell child groups: type 6 is the
Cell Childrencontainer; 8 = Persistent, 9 = Temporary, 10 = Visible Distant nest inside it (#4169 — earlier legends said 6/8/9).PlacedRef.group_typeis re-derived at each nested GRUP inparse_refr_group_inner(only the nested position accepts 10; at CELL/WRLD level 10 is Quest Children). No consumer reads it yet — verify it stays populated per-scope, not that a consumer exists. - Interior and exterior CELL sub-record walkers share one decode,
CellSubrecordFields::absorb(cell/helpers.rs, #5309) — a field added to only one walker's arm is the regression. All three walkers depth-bounded (see Dim 1):parse_cell_group,parse_wrld_children,parse_refr_group. - Placement header flags (
f87490826, #4813/#4814):PlacedRef.initially_disabled=0x800on every placement type;PlacedRef.starts_dead=0x200onACHRgated onEsmVariant::Tes5Plus;starts_unconscious=0x2000onACHR, FO4/FO76/Starfield only (#5017). The placement bit is not the corpse marker on every game, and each other game is settled at its own layer: Oblivion = baseNPC_/CREAheader0x80000(base_actor_starts_dead,dispatch_actor.rs, Oblivion-gated, #5013); FO3/FNV = baseDATAhealth ≤ 0 (NpcRecord::starts_dead, #5005 —0x80000is not a marker there, andXRGDis a ragdoll pose, not a death signal) plus the script-killed idiom (717f39a82, #5223 — a dismember-family trigger'sXLKRtarget; FO3/FNVXLKRis 4 B, the target FormID alone, vs TES5's 8 B keyword+target — alen >= 8gate silently dropped every FO3/FNV link). A zero placementstarts_deadmeans "no placement-level marker", not "alive"; the cell loader ORs both (byroredux/src/cell_loader/references/mod.rs→apply_starts_dead). Flag any per-game gate that reads one of these bits on a game whose xEdit list does not define it. - Starfield metric → 70/m lift (
records/spatial_units.rs::normalize, called once at the end of each plugin's walk inparse.rs): positions, light radii, XCLL/LGTM fog/fade + SF height-fog (#5002; the 20-field lighting tail decodes once incell::decode_starfield_lighting_tailand the height-fog lift is the sharedstarfield_heights,9dbd59b8b, #5346), WTHR fog distances (only whenfog_distances_authored— an FNAM-less weather keeps the unlifted 10 000 default,ec75b1439, #5301, pinned bystarfield_wthr_without_fnam_keeps_the_unlifted_fog_defaults) + height-fog tail (#5001/#5134), water heights, WATRDNAMlengths ×70 and per-metre absorption ÷70 — only for offsets the record authored (#5151,24cb577d2), LAND heights, NAVM vertices, XRGD ragdoll-pose offsets (5f1a862be, #5299; Euler stays wire-valued). FO76 is BU-native except the shared-decoder WATR absorption lane, its only lift (#5169). Three Starfield WATRDNAMlane families (noise-UV tiles, displacement simulator, normal falloff) are deliberately not lifted — unit unsettled, pinned bystarfield_watr_defers_the_unclassified_dnam_lanes; the LGTM + cell-XCLL lift is pinned throughparse_esmbystarfield_index_lifts_lgtm_and_xcll_lighting_distances(2dab4ff48, #5170/#5171,records/spatial_units_tests.rs). Check new Starfield distance fields join it, dimensionless fields (XSCL, Euler) stay out, decoder defaults for an unauthored field are never lifted, and no path normalizes an index twice. - M47.2 placement/base fields, all remapped: REFR
XAPR(activate parent + delay,XAPDParent Activate Only),XMRK→FNAMmap-marker flags, per-referenceXEZN(PlacedRef.encounter_zone_form); base-objectDEST/DSTD→DestructionData.ECZN DATAis per game (parse_eczn(.., game, ..): FO3/FNV 8 B, Skyrim/FO4/Starfield add theLCTNand max level; xEditwbECZN).EsmCellIndex::cell_by_form_idis a lazily-built O(1) lookup (cell_form_id_lookup, #5489) thatmerge_fromresets — a CELL-map edit after the first call leaves it stale. - Lighting-template inheritance is per-field; "absent" and "authored zero" stay distinct.
- CELL
XCCMis per-game (3b28dca71, #5463): TES4/FO3/FNV = Climate (CLMT) →CellData.climate_override; TES5/FO4 = "Sky/Weather from Region" (REGN) →sky_region(xccm_targets_region,cell/helpers.rs; decoded, no consumer yet; FO76/Starfield keep the CLMT reading, no verified xEdit definition). Guardsparse_cell_oblivion_fo3nv_xccm_populates_climate_override,parse_cell_tes5_fo4_xccm_is_a_region_not_a_climate. Regression = a REGN id reachingclimate_override. - XCLL is size- and game-validated:
xcll_canonical_sizes(game); the ≥92-byte ambient-cube arm fires only for Skyrim/FO4/FO76 (Starfield has its own ≥108 arm) — a non-canonical 92+ XCLL on Oblivion/FO3NV keeps base + per-field reads and warns (guard: the cell testparse_cell_non_canonical_92plus_xcll_on_pre_skyrim_games_stays_out_of_the_ambient_cube). Parsed-but-unconsumed tail fields are parked indocs/engine/lighting-from-cells.md. - LIGH
LightData:falloff_exponent0.0is a sentinel resolved per layout generation at the translation boundary (canonical_light_falloff_exponent,byroredux/src/systems/light_anim.rs) — the parser must store authored values verbatim. Starfield light shape is theDAT2+56Light Type enum (starfield_light_type: 0 omni, 1 shadow spot, 2 non-shadow spot);0x200is Focus Spotlight Beam, not a shape bit.DAT2is gated on LIGH (an AMMODAT2must not synthesize light data). XCLWwater height is tri-state: absent → inherit WRLD default; finite → override;INT_MIN/FLT_MAXsentinel → suppress (gated_water_heightinesm/cell/helpers.rs+water_height_is_explicit;docs/engine/watal.md).- Exterior grid
XCLC, worldspace parenting and selective-inheritance flags (wrld.rs): a child inherits only the flagged categories. Oblivion authors noPNAM, soparse_wrld_groupstamps land/LOD/water/climate inherit bits on every Oblivion child (67afa4b95, #5374 — pre-FO3 "child ⟹ inherit all"; the flag-less duplicate fallback was deleted in65cadde2f, #5516, so the stamp is the only copy — pinned byoblivion_child_gets_the_inherit_all_stamp_and_eras_stay_real); an authored zeroPNAMon FO3+ must stay zero. parse_land_record: quadrant/layer counts and splat rows are fixed-stride. AnATXTwith no followingVTXTmust flush withalpha: None(pending_atxt, #4078; measured 14× onOblivion.esm).LTEX.GNAMis an array of grasses, kept in authored order intolandscape_grassesbyparse_ltex_group(#4642; last-wins was the bug).- FO4 precombine CELL sub-records (
CellSubrecordFields::absorb): theXCRItail isref_count / 2× (ref FormID, bake hash) pairs →CellData.absorbed_ref_bakes(the skip set);XPRIis the disjoint previs list, validated and not stored (777351ec1, #5484 — the two were inverted, double-drawing every baked STAT/SCOL). Guardsparse_cell_xcri_pairs_remap_and_keep_their_bake_hash,parse_wrld_exterior_cell_captures_precombined_xcri_xpri. - Starfield
TXST: an unmodelled slot warns once (#4438), not per record. - Navmesh: classic
NVTR/NVEXand packedNVNM(decode_nvnm,NVNM_MAX_DIVISOR) yield the sameNavmRecordthrough shared row decoders; the cursor refuses to pass the blob end; retained-raw-on-failure is diagnosable, not a silent success. Output:/tmp/audit/esm/dim_5.md
Dimension 6: Localized Strings (Skyrim+ .STRINGS family)
Paths: crates/plugin/src/esm/strings_table.rs, esm/records/common.rs (StringsTableGuard, lstring readers), byroredux/src/cell_loader/load_order.rs (table loading)
First step: cargo test -p byroredux-plugin strings_table
Checklist:
- The TES4 localized flag (
0x80) — not "looks like a small integer" — decides literal vsu32string id. lstring id0means "no string" (empty, never the<lstring 0x…>placeholder, #4072); only a genuinely unresolved id yields the placeholder. .STRINGShas no length prefix;.DLSTRINGS/.ILSTRINGSdo (has_length_prefix). A flag/data disagreement warns loudly (declared length vs NUL offset, ±1 band, #4178) instead of yielding shifted strings.- Missing string files degrade to "no string" without panic or per-form warn spam; a
localized plugin that resolves zero tables logs a diagnostic (#4073,
load_order.rs). - Tables and the localized flag are thread-locals: since the parallel walk (#3813) the
caller only loads tables (
load_string_tables) and each walk installs its ownStringsTableGuardon the walking thread. A guard installed on the caller yields<lstring 0x…>placeholders under the pool. - The language token is not the file-name segment: Skyrim spells it out
(
dawnguard_english.strings), FO4/FO76/Starfield use a short tag (Fallout4_en.STRINGS).language_candidatesexpands both; check it exists before reporting the defect (two audits re-derived it before it was filed, #4168). - Decode is UTF-8-first with a cp1252 fallback and that order is load-bearing (#4170: English tables carry real cp1252 bytes, localized tables are real UTF-8). A flat cp1252 decode is the regression; the fallback is not a bug.
- Loose files are found case-insensitively before the archive fallback
(
load_with_archive, #4176) — a case-mismatched mod override must beat the archive. Output:/tmp/audit/esm/dim_6.md
Dimension 7: EsmIndex → ECS Handoff & the Redux-Native Tier
Paths: crates/plugin/src/{record,datastore,resolver,manifest,equip}.rs, esm/records/index.rs; consumers byroredux/src/cell_loader/references/, byroredux/src/npc_spawn.rs
First step: grep -rn 'resolve_entity_by_global_form_id\|find_by_form_id' byroredux/src | head -20
Checklist:
EsmIndexis a session-long bag ofHashMap<u32, _>; nothing evicts it. Check growth on a large load order againstdocs/engine/memory-budget.md(RAM, not VRAM).- Sample high-traffic
Option<u32>FormID fields (SCRI,RNAM,CNAM,PKID,XEZN, teleportXTEL): the consumer must resolve viaresolve_entity_by_global_form_id, not a rawWorld::find_by_form_id. equip.rsbiped-slot constants: BMDT vs BOD2 vs FO4 bit meanings differ; each block cites its xEdit definition; slot →addon_index(AddonData::addon_index) matches equipment_system.equip_template_tests.rscovers template equip resolution.- Redux-native tier: audit for rot (compiles against the current
Recordshape; docs consistent withdocs/engine/plugin-loading.md) and dead-but-documented API. "Unused" is not a bug on its own — but #5609 (open) records thatDataStore+DependencyResolver+PluginManifesthave no production consumer while four docs present them as the live load path; cite it rather than re-filing. Output:/tmp/audit/esm/dim_7.md
Dimension 8: Real-Data Validation
Paths: crates/plugin/examples/, crates/plugin/tests/parse_real_esm.rs, byroredux/src/{list_cells,sf_smoke}.rs
First step: cargo run --release -p byroredux-plugin --example esm_dim8_coverage -- <master.esm> (one master at a time)
Checklist:
- Per
--game: records seen / decoded / stubbed / skipped and unknown sub-record codes per record type (esm_dim8_coverageemits the TSV;esm_dim8_benchgives parse timeEsmIndextotals; other probes:sf_smoke,probe_form,dump_record_subs,weather_coverage_census—ls crates/plugin/examples). Unknown-subrecord share is the coverage signal per-game audits never compute across the whole file.
- A decode rate that drops between games is a schema-split suspect — correlate with Dim 4
GameKindarms. Compare Starfield against the--sf-smokeresolve-rate baseline; a drop is a regression, not a new finding. - Report parse time and peak RSS per master (ESM parse cost is on every cell load's
critical path; NIF parse cost is
/audit-performance). - No windowed engine launch (feedback_no_parallel_engine_launch); example binaries and
default
cargo testonly (see Phase 1 step 4 on--ignored). Output:/tmp/audit/esm/dim_8.md
Phase 3: Merge
- Read all
/tmp/audit/esm/dim_*.md; combine intodocs/audits/AUDIT_ESM_<TODAY>.md:- Executive Summary — findings by severity; coverage posture; which games' real data was actually parsed.
- Record Coverage Matrix — record type × game × {decoded, stubbed, skipped, unknown-subrecords}. The durable artifact; keep it even with no findings.
- Findings — grouped by severity, deduplicated.
- Cross-Audit Pointers —
/audit-<game>Dim 1,/audit-scripting(VMAD),/audit-character(AVIF/class),/audit-physics(collision-relevant placement),/audit-exterior,/audit-gameplay,/audit-parsers.
- Deduplicate against the per-game reports read in Phase 1 — a finding already filed under
an
/audit-<game>ESM dimension isExisting: #NNN, not new.
Phase 4: Cleanup
rm -rf /tmp/audit/esm; tell the user the report is ready; suggest
/audit-publish docs/audits/AUDIT_ESM_<TODAY>.md (domain label esm-plugin; plus the
matching game:* when specific to one title's records).
Du fichier au premier essai
Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.
Consulter la source et ses fichiers annexesCe téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.
1. Télécharger le fichier
Télécharger SKILL.mdDepuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.
Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.
mkdir -p '.skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/esm-esp-plugin-parser-audit-mvg6ty2n/download' --output '.skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n/SKILL.md.part' && mv '.skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n/SKILL.md.part' '.skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n/SKILL.md'2. Vérifier les prérequis
Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.
Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.
Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.
3. Charger la skill
Le fichier ne fournit pas un en-tête name/description exploitable pour cette procédure native. Le téléchargement reste disponible pour lecture ; utilisez le mode manuel ci-dessous.
Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.
4. Faire un premier essai
Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :
Lis .skill-downloads/esm-esp-plugin-parser-audit-mvg6ty2n/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.
Bonnes pratiques React Hook Form
Developpement
Maîtrisez React Hook Form : évitez les anti-patterns, gérez les abonnements, et traitez correctement reset, dirty state et entrées contrôlées.
Migration Emotion vers Mantine
Developpement
Migrer les styled-components Emotion vers des composants Mantine avec style props et modules CSS. Pour convertir .styled.tsx ou supprimer les imports @emotion.
Détecteur de dérive de périmètre
Developpement
Analyse les diffs git par rapport à une intention pour détecter la dérive de périmètre, fichiers non liés et PR trop larges, puis suggère de garder, diviser ou justifier.