From 14e39cb334e1482e4e058dd5398a48cc892da075 Mon Sep 17 00:00:00 2001 From: ejams1 Date: Mon, 19 Oct 2026 11:08:00 -0600 Subject: [PATCH 1/2] Reduce agent context with focused plans and skill references --- .claude/skills/adding-a-command/SKILL.md | 2 +- .claude/skills/auditing-conflicts/SKILL.md | 2 +- .claude/skills/building-patches/SKILL.md | 2 +- .claude/skills/checking-parity/SKILL.md | 347 ++-------- .../checking-parity/references/background.md | 3 + .../checking-parity/references/checks.md | 18 + .../references/close-out-summary.md | 12 + .../checking-parity/references/inputs.md | 30 + .../references/investigating-a-difference.md | 6 + .../references/quirks-found-and-reproduced.md | 28 + .../references/results-check-for-errors.md | 25 + .../references/results-cleaning.md | 17 + .../references/results-conflict-status.md | 35 + .../references/results-filter.md | 33 + .../references/results-lod-generation.md | 23 + .../references/results-memory.md | 29 + .../references/results-merged-patch.md | 19 + .../references/results-mod-groups.md | 29 + .../references/results-nif-and-materials.md | 34 + .../results-oracle-edit-sequences.md | 75 +++ .../references/results-oracle-save.md | 30 + .../references/results-reference-index.md | 21 + .../references/results-resources.md | 15 + .../references/results-round-trip.md | 52 ++ .../references/results-scripts.md | 112 ++++ .../references/results-sniff-operations.md | 35 + .../references/results-tool-modes.md | 21 + .../references/running-the-harness.md | 39 ++ .../references/running-the-oracle.md | 30 + .claude/skills/cleaning-plugins/SKILL.md | 2 +- .claude/skills/handling-archives/SKILL.md | 2 +- .claude/skills/nif-batch-operations/SKILL.md | 2 +- .claude/skills/porting-definitions/SKILL.md | 2 +- .claude/skills/porting-pascal-unit/SKILL.md | 2 +- .claude/skills/using-xedit-cli/SKILL.md | 609 ++---------------- .../references/adding-removing-and-copying.md | 53 ++ .../using-xedit-cli/references/archives.md | 23 + .../using-xedit-cli/references/assets.md | 23 + .../using-xedit-cli/references/background.md | 3 + .../references/checking-for-errors.md | 19 + .../using-xedit-cli/references/cleaning.md | 27 + .../using-xedit-cli/references/commands.md | 99 +++ .../using-xedit-cli/references/conflicts.md | 31 + .../references/daemon-and-mcp-server.md | 40 ++ .../references/editing-an-element.md | 10 + .../using-xedit-cli/references/examples.md | 12 + .../filtering-the-navigation-tree.md | 23 + .../references/formids-and-module-flags.md | 32 + .../using-xedit-cli/references/known-gaps.md | 75 +++ .../references/loading-plugins.md | 37 ++ .../references/localization.md | 28 + .../references/lod-generation.md | 12 + .../using-xedit-cli/references/masters.md | 24 + .../references/merged-patch.md | 22 + .../using-xedit-cli/references/mod-groups.md | 26 + .../references/mutation-rules.md | 31 + .../using-xedit-cli/references/output.md | 9 + .../using-xedit-cli/references/references.md | 24 + .../using-xedit-cli/references/saves.md | 11 + .../references/saving-a-plugin.md | 14 + .../using-xedit-cli/references/scripts.md | 51 ++ .../using-xedit-cli/references/sniff.md | 12 + .../using-xedit-cli/references/tool-modes.md | 39 ++ .claude/skills/writing-xedit-scripts/SKILL.md | 180 +----- .../references/background.md | 3 + .../references/corpus-scripts-to-copy-from.md | 17 + .../references/headless-and-the-vcl-seam.md | 56 ++ .../references/known-divergences.md | 66 ++ .../references/output-conventions.md | 22 + .../references/running-one.md | 38 ++ .../references/script-or-xedit-batch.md | 10 + .../references/the-dialect.md | 10 + .../upstream-quirks-a-script-meets.md | 14 + .../references/where-the-api-lives.md | 30 + .rgignore | 10 + CLAUDE.md | 22 + README.md | 2 +- docs/PLAN.md | 210 +----- docs/history/parity-backlog.md | 289 +++++++++ docs/history/phase-0.md | 9 + docs/history/phase-1.md | 14 + docs/history/phase-2.md | 13 + docs/history/phase-3.md | 112 ++++ docs/history/phase-4.md | 223 +++++++ docs/history/phase-5.md | 137 ++++ docs/history/phase-6.md | 248 +++++++ 86 files changed, 3056 insertions(+), 1232 deletions(-) create mode 100644 .claude/skills/checking-parity/references/background.md create mode 100644 .claude/skills/checking-parity/references/checks.md create mode 100644 .claude/skills/checking-parity/references/close-out-summary.md create mode 100644 .claude/skills/checking-parity/references/inputs.md create mode 100644 .claude/skills/checking-parity/references/investigating-a-difference.md create mode 100644 .claude/skills/checking-parity/references/quirks-found-and-reproduced.md create mode 100644 .claude/skills/checking-parity/references/results-check-for-errors.md create mode 100644 .claude/skills/checking-parity/references/results-cleaning.md create mode 100644 .claude/skills/checking-parity/references/results-conflict-status.md create mode 100644 .claude/skills/checking-parity/references/results-filter.md create mode 100644 .claude/skills/checking-parity/references/results-lod-generation.md create mode 100644 .claude/skills/checking-parity/references/results-memory.md create mode 100644 .claude/skills/checking-parity/references/results-merged-patch.md create mode 100644 .claude/skills/checking-parity/references/results-mod-groups.md create mode 100644 .claude/skills/checking-parity/references/results-nif-and-materials.md create mode 100644 .claude/skills/checking-parity/references/results-oracle-edit-sequences.md create mode 100644 .claude/skills/checking-parity/references/results-oracle-save.md create mode 100644 .claude/skills/checking-parity/references/results-reference-index.md create mode 100644 .claude/skills/checking-parity/references/results-resources.md create mode 100644 .claude/skills/checking-parity/references/results-round-trip.md create mode 100644 .claude/skills/checking-parity/references/results-scripts.md create mode 100644 .claude/skills/checking-parity/references/results-sniff-operations.md create mode 100644 .claude/skills/checking-parity/references/results-tool-modes.md create mode 100644 .claude/skills/checking-parity/references/running-the-harness.md create mode 100644 .claude/skills/checking-parity/references/running-the-oracle.md create mode 100644 .claude/skills/using-xedit-cli/references/adding-removing-and-copying.md create mode 100644 .claude/skills/using-xedit-cli/references/archives.md create mode 100644 .claude/skills/using-xedit-cli/references/assets.md create mode 100644 .claude/skills/using-xedit-cli/references/background.md create mode 100644 .claude/skills/using-xedit-cli/references/checking-for-errors.md create mode 100644 .claude/skills/using-xedit-cli/references/cleaning.md create mode 100644 .claude/skills/using-xedit-cli/references/commands.md create mode 100644 .claude/skills/using-xedit-cli/references/conflicts.md create mode 100644 .claude/skills/using-xedit-cli/references/daemon-and-mcp-server.md create mode 100644 .claude/skills/using-xedit-cli/references/editing-an-element.md create mode 100644 .claude/skills/using-xedit-cli/references/examples.md create mode 100644 .claude/skills/using-xedit-cli/references/filtering-the-navigation-tree.md create mode 100644 .claude/skills/using-xedit-cli/references/formids-and-module-flags.md create mode 100644 .claude/skills/using-xedit-cli/references/known-gaps.md create mode 100644 .claude/skills/using-xedit-cli/references/loading-plugins.md create mode 100644 .claude/skills/using-xedit-cli/references/localization.md create mode 100644 .claude/skills/using-xedit-cli/references/lod-generation.md create mode 100644 .claude/skills/using-xedit-cli/references/masters.md create mode 100644 .claude/skills/using-xedit-cli/references/merged-patch.md create mode 100644 .claude/skills/using-xedit-cli/references/mod-groups.md create mode 100644 .claude/skills/using-xedit-cli/references/mutation-rules.md create mode 100644 .claude/skills/using-xedit-cli/references/output.md create mode 100644 .claude/skills/using-xedit-cli/references/references.md create mode 100644 .claude/skills/using-xedit-cli/references/saves.md create mode 100644 .claude/skills/using-xedit-cli/references/saving-a-plugin.md create mode 100644 .claude/skills/using-xedit-cli/references/scripts.md create mode 100644 .claude/skills/using-xedit-cli/references/sniff.md create mode 100644 .claude/skills/using-xedit-cli/references/tool-modes.md create mode 100644 .claude/skills/writing-xedit-scripts/references/background.md create mode 100644 .claude/skills/writing-xedit-scripts/references/corpus-scripts-to-copy-from.md create mode 100644 .claude/skills/writing-xedit-scripts/references/headless-and-the-vcl-seam.md create mode 100644 .claude/skills/writing-xedit-scripts/references/known-divergences.md create mode 100644 .claude/skills/writing-xedit-scripts/references/output-conventions.md create mode 100644 .claude/skills/writing-xedit-scripts/references/running-one.md create mode 100644 .claude/skills/writing-xedit-scripts/references/script-or-xedit-batch.md create mode 100644 .claude/skills/writing-xedit-scripts/references/the-dialect.md create mode 100644 .claude/skills/writing-xedit-scripts/references/upstream-quirks-a-script-meets.md create mode 100644 .claude/skills/writing-xedit-scripts/references/where-the-api-lives.md create mode 100644 .rgignore create mode 100644 CLAUDE.md create mode 100644 docs/history/parity-backlog.md create mode 100644 docs/history/phase-0.md create mode 100644 docs/history/phase-1.md create mode 100644 docs/history/phase-2.md create mode 100644 docs/history/phase-3.md create mode 100644 docs/history/phase-4.md create mode 100644 docs/history/phase-5.md create mode 100644 docs/history/phase-6.md diff --git a/.claude/skills/adding-a-command/SKILL.md b/.claude/skills/adding-a-command/SKILL.md index 90a8eb42..cc6806a3 100644 --- a/.claude/skills/adding-a-command/SKILL.md +++ b/.claude/skills/adding-a-command/SKILL.md @@ -1,6 +1,6 @@ --- name: adding-a-command -description: Use when adding a session command to xedit-session so the CLI, the serve daemon and the MCP server all offer it. Covers the request and response structs with their JSON Schema, registering the command (name rules, mutates and dry_run, the edit gate), the CLI subcommand, keeping stdout clean, the tests that pick the command up automatically, the coverage ledger entry, the upstream map and the docs to update. +description: Add an xedit-session command and expose its typed request, response, schema, edit gate, and CLI across CLI, serve, and MCP. --- # Adding a command diff --git a/.claude/skills/auditing-conflicts/SKILL.md b/.claude/skills/auditing-conflicts/SKILL.md index 1f857907..a1634d56 100644 --- a/.claude/skills/auditing-conflicts/SKILL.md +++ b/.claude/skills/auditing-conflicts/SKILL.md @@ -1,6 +1,6 @@ --- name: auditing-conflicts -description: Use when a load order's record conflicts have to be audited with the native xedit CLI of this repository - which override of a FormID wins and what xEdit's conflict statuses mean, narrowing the list to the records a plugin wins or loses, comparing a record's overrides side by side, the mod groups that hide records from the comparison, and the reference index that shows what a conflicting record is used by. The native replacement for the xEdit-llm daemon's comparison routes (comparisons.records, records.conflict_status). +description: Audit Bethesda plugin override winners, conflict statuses, comparisons, mod groups, and reference usage with the native xedit CLI. --- # Auditing conflicts diff --git a/.claude/skills/building-patches/SKILL.md b/.claude/skills/building-patches/SKILL.md index 2460cabe..5d68a02f 100644 --- a/.claude/skills/building-patches/SKILL.md +++ b/.claude/skills/building-patches/SKILL.md @@ -1,6 +1,6 @@ --- name: building-patches -description: Use when a conflict found in a load order has to be resolved by building a patch plugin with the native xedit CLI of this repository - xEdit's "Create Merged Patch" for the leveled lists, containers, factions, form lists and keywords several plugins change, or a purpose-built override patch made with files new, records copy and elements set. The native replacement for the xEdit-llm daemon's patch routes (patches.merge, patches.delta). +description: Resolve Bethesda plugin conflicts with a native merged patch or a targeted override patch using the xedit CLI. --- # Building patches diff --git a/.claude/skills/checking-parity/SKILL.md b/.claude/skills/checking-parity/SKILL.md index eebcace3..4aecfe3b 100644 --- a/.claude/skills/checking-parity/SKILL.md +++ b/.claude/skills/checking-parity/SKILL.md @@ -1,314 +1,45 @@ --- name: checking-parity -description: Use when verifying that the Rust port behaves the same as Delphi xEdit, when a phase gate in docs/PLAN.md has to be closed, or when a parity difference has to be investigated. +description: Verify xEdit Rust against the official Delphi release, investigate an output difference, or close a phase's parity gate. --- # Checking parity -The official xEdit release build of the tag in `upstream-map.toml` is the oracle. The project has no Delphi compiler, so only released binaries can be the oracle. The port is correct when it produces the same output as the oracle on the same input. - -## Running the harness - -``` -cargo xtask parity dump|saves|roundtrip|oracle-save|oracle-edit|conflicts|refs|clean|clean-scenarios|check|check-dump|modgroups|merged|filter|tool-modes|script [--game ]... [--file ]... [--record ]... [--mode ]... [--oracle-only] - [--jobs ] [--memory-budget ] [--max-memory ] [--oracle-timeout ] -``` - -The harness runs the oracle `xDump.exe` and the port's `xedit dump` on every vanilla plugin of the selected games and compares the outputs byte for byte. The games are `fo4`, `sse`, `tes3`, `tes4`, `fo3`, `fnv`, `tes5`, `tes5vr`, `fo4vr`, `fo76` and `sf1`. Without `--game` it checks every game whose data directory is set and prints a `skipped` line for the others. `--file` restricts the run to named plugins, vanilla or not. It prints the first differing line of each file, writes `target/parity/dump.json` and exits with an error when any file differs. - -Environment variables: - -| Variable | Content | -| --- | --- | -| `XEDIT_ORACLE_DIR` | Unpacked release archive of the baseline tag. | -| `XEDIT__DATA` | `Data` directory of each game: `XEDIT_FO4_DATA`, `XEDIT_SSE_DATA`, `XEDIT_TES3_DATA` (Morrowind's `Data Files`), `XEDIT_TES4_DATA`, `XEDIT_FO3_DATA`, `XEDIT_FNV_DATA`, `XEDIT_TES5_DATA`, `XEDIT_TES5VR_DATA`, `XEDIT_FO4VR_DATA`, `XEDIT_FO76_DATA`, `XEDIT_SF1_DATA`. | -| `XEDIT__SAVES` | Save folder of each game for `parity saves`: `XEDIT_FO4_SAVES`, `XEDIT_SSE_SAVES`, `XEDIT_TES5_SAVES`, `XEDIT_FNV_SAVES`, `XEDIT_FO3_SAVES`, `XEDIT_TES4_SAVES`. | -| `XEDIT_PARITY_CACHE` | Cache directory. Optional; defaults to the user cache directory. Shared: it holds the expensive oracle outputs. | -| `XEDIT_PARITY_SCRATCH` | Where the port's saves (`roundtrip`, `oracle-save`, `oracle-edit`), the files kept for a difference and the oracle's run folders go. Optional; defaults to the cache. Set it per branch when several branches run the round trip at once. | -| `XEDIT_ORACLE_KEEP_WORK` | Set to keep the run folder of a GUI oracle run (the generated script, the private data folder) to read it or start the GUI on it by hand. | - -Oracle output is cached zstd-compressed as `///..oracle.txt.zst`, so the oracle runs once per input file; plain `.oracle.txt` files from older runs are still read. A run that the oracle ended with `Unexpected Error` is kept as `..oracle.crashed.txt.zst`, with the exception's log line in `..oracle.error`, and compared as a prefix: the port has to match it up to the crash, and the rest of its output must still finish without an error, which the report shows as `equal-prefix`. A port that stops with the same error message after the same output (`error: ` as its last log line) is reported as `equal-error`; the Oblivion saves end that way, because upstream's Oblivion save definitions expect the Fallout 3 header magic. An oracle run that ended with `EInOutError` (I/O error 1450 under memory pressure) is not kept and is reported as `oracle-failed`; run the file again. An oracle assertion failure while loading (`EAssertionFailed` in `wbImplementation.pas`) was seen once on a Fallout New Vegas save and did not repeat: delete the `.oracle.crashed.txt.zst` and run the file again before reading anything into it. The oracle is slow on large masters: it writes about 80 MB of dump per minute and needs more than ten minutes for `Fallout4.esm`. `--oracle-only` fills the cache without running the port. The port output is compared while the port writes it; it is kept only for a file that differs, as `.port.txt.zst` up to the first difference (`zstd -dc` reads it), next to the port's log `.port.log`. - -## Close-out results (2026-10-18) - -The parity backlog's close-out (issue #41, pull request #77) ran every mode at the final tip, one step per mode with the oracle cache shared, and `sniff` one step per game (the crash-carrying step runner of rule 16). The numbers per mode are the README Status table and the report of each run is written to `target/parity/.json`, as the sections below describe. The full logs of the close-out are under the batch's scratch folder, not committed. - -What the close-out learned about the oracle itself: - -- The released `Sniff.exe` corrupts its heap under threads (`0xc0000374`) and hangs on two Fallout 4 Creation Club texture archives (`cctosfo4001-neosky`, `cctosfo4002_neonflats`, `find-textures-copy`) even on one thread. The harness retries a crash twice and a hang once on one thread, then records the archive as `oracle-failed` and carries on; it never ends the run. -- The `Find textures` report prints `XBOX` from an uninitialised local record on every line of a BA2 DDS archive; the port prints nothing. The saved bytes are equal, so `sniff` counts these as log differences, not failures. -- `clean` of Fallout 76's `NW.esm` loses the oracle log's counts on every run (`oracle-log-lost`); the saved bytes are compared and equal. -- `check-dump` of `Starfield.esm` needs more than the 23.7 GiB per-process cap in a batch run (a rerun alone with `--max-memory 28` also met the machine's limit), and `tool-modes`'s `setesm` on `BlueprintShips-SFBGS050.esm` exceeds the 20-minute oracle limit and still exceeds 60 minutes alone (the other four modes on that file and the other eight `setesm` files are equal). -- The script run phase has four cases whose oracle timed out at 60 or 120 minutes under load; they are rerun owed with `--jobs 1 --oracle-timeout 180`. -- BSArchPro's autopacking mode starts no work when its window is on a desktop nothing displays: `bsarchpro` runs it on the user's desktop, shown. The case list named asset sources as `\` where the fixtures are under `\src`, and the XBox refusal is shown on the `TFormTaskProgress` form (`Packing...`), not a `#32770`: all six cases are equal now (five archives byte for byte, one refusal). - -### Memory - -Every oracle and port process runs in a Windows job object that caps its committed memory at `--max-memory` (default: the budget), so a runaway dump fails alone instead of pushing the machine out of memory. A port that reaches the cap is reported as `port-memory-limit`; an oracle that reaches it is reported as `oracle-failed` and its output is not cached. The peak of each complete run is stored next to the cache as `.oracle.peak` and `.port.peak` and shown in `dump.json` as `oracle_peak` and `port_peak`. - -The processes that run at once stay within `--memory-budget` (default: three quarters of the installed memory). Each run reserves the peak its file had in the last run, or, before the first run, 6 times the size of the plugin and its game master; a file whose reservation exceeds the budget runs alone. `--jobs` (default 3) is only an upper bound on the parallel runs. The port dumps on all CPUs (phase 3 step 8): the largest port peak of the Fallout 4 and Skyrim SE corpus is 2.5 GiB (`DLCCoast.esm`; 1.6 GiB on one thread), `Skyrim.esm` peaks at 1.8 GiB. The wall clock is bounded by the largest file: the port dumps `Skyrim.esm` (8.3 GB of text) in about 30 seconds and `Update.esm` in about 4 seconds on the reference machine (16 threads; 3 minutes and 28 seconds on one). Each port process uses every CPU, so `--jobs` above 1 mostly overlaps the oracle runs with the port's. - -`parity saves` runs `xDump.exe -saves` and `xedit saves dump` on every save and co-save in the save folder of each game (`.fos`, `.ess`, `.f4se`, `.skse`, `.nvse`, `.fose`, `.obse`), with the game's data folder as `-D:` and `--data`, and caches the oracle output under `//-saves`. The report is `target/parity/saves.json`. The oracle is far too slow on Skyrim LE saves to finish: it raises an exception for every hardcoded FormID it cannot resolve (see `using-xedit-cli`) and writes about 1 KB per second, where the port writes 140 MB for `quicksave.ess`. `--oracle-timeout ` stops the oracle after that long and keeps its output as `..oracle.timeout.txt.zst`, compared as a prefix like a crashed run (`equal-prefix`). The Skyrim LE saves were checked with `--oracle-timeout 15`; delete the `.oracle.timeout.txt.zst` of a save to extend its prefix. Oblivion saves need `XEDIT_TES4_SAVES` (`.ess`, `.obse`) and Fallout 3 saves `XEDIT_FO3_SAVES` (`.fos`, `.fose`); on this machine both folders are under `M:\projects\saves`. - -### Round trip - -`parity roundtrip` runs `xedit --edit --game --load save --no-backup --output //-roundtrip/..saved` on every corpus plugin and compares the saved bytes with the input, byte for byte. No oracle binary runs: the input file is the oracle, which holds exactly where xEdit writes a loaded file back unchanged, and the GUI oracle's own save decides wherever `parity oracle-save` has cached it (below): a save that equals the oracle's save is `equal` whatever it changed in the input (the detail says how the oracle's save differs from the input), one that does not is `different`, and a refusal with the oracle's message stays `refused` with the oracle's confirmation in the detail. Only a file without a cached oracle save falls back to the classes below that judge the input alone. The report is `target/parity/roundtrip.json` and the statuses are: - -- `equal`: identical bytes; the saved file is deleted. -- `ofst-dropped`: the saved file is the input without the `OFST` subrecord (and its `XXXX` size prefix) of every worldspace (and `CLSZ` for Fallout 4, Fallout 76 and Starfield, `VISI` for Fallout 76, the `RNAM` of the game master), with the record and group sizes adjusted. xEdit drops those offsets when it loads a worldspace and writes the record without them, so this is the expected result for a file that overrides a worldspace; the harness rebuilds the expected bytes from the input and compares. Not counted as passing until the oracle's own save confirms it (step 6 of phase 3). -- `header-edited`: the bytes after the file header record are the input's (or the input's without the `OFST` data), and only the header record differs; the detail names what changed (`HEDR 1234 -> 1233 records`, `INCC added (12 interior cells)`, `ONAM added (857 entries)`, `flags 00000000 -> 00000001`). Those are the edits upstream makes to the header on every save, so this is the expected result for a master or a file whose header is off; step 6 confirms the exact bytes against the oracle's own save. -- `structure-edited`: every main record of the saved file is the input's (or the input's without the `OFST` data), but the header and the groups changed: the load drops an empty top level group and merges a duplicated one, as upstream does, and the detail gives the group counts (`groups 50494 (1013 empty) -> 50487 (1006 empty)`). Expected for the same reason as `header-edited`. -- `different`: the first differing offset is printed, the saved file kept, and the detail lists the records that changed, were dropped or were added by signature and FormID, with the first few sizes. Investigate as any difference, but check first whether upstream itself changes the records on save in a way the input cannot show (the subrecord diff of the step 2 results below tells the three known cases apart): it initialises every worldspace record on load, so `wbWorldAfterLoad` adds the members the parent flags call for (`CNAM`, `NAM2`, `DNAM`, `MNAM`, `INAM`; a worldspace grows by 10 or 20 bytes) and the required members are added; `PrepareSave` clamps a FormID whose file index is beyond the masters (the record is reported dropped and added under its new FormID) and sets the persistent flag of an `ONAM` override whose earlier override has it; and `PrepareSave` marks the child group of a worldspace whose offsets were dropped modified, so every record under it is rebuilt from its elements (its `AfterLoad` fixes apply, its subrecords are sorted, a short `DATA` is written at its definition's size, compressed records are compressed again with libdeflate level 12). Those files need the oracle's own saved bytes, which step 6 adds (the GUI build driven headlessly on a copy of the corpus). -- `refused`: the port refused the save with an upstream `PrepareSave` message, which the oracle gives for the same file (a Starfield blueprint module, for example). Counted as passing. -- `unsupported`: a save upstream edits in a way this version cannot yet. None since the element editing step. -- `port-failed`, `port-memory-limit`: as for the dump. - -The saves run under the same memory cap and budget as the dumps and reuse the `.port.peak` files. The port's log of each save is `..port.log` next to the saved file. - -#### Step 1 results (2026-10-07) - -249 plugins of 11 games in 1.5 minutes: 132 `equal`, 38 `ofst-dropped`, 2 `refused` (the Starfield blueprint masters), 77 `unsupported`, 0 `different`. The `unsupported` saves are the header edits upstream makes on save: 39 `ONAM` rebuilds, 34 `HEDR` record counts that differ from the loaded tree because the load drops empty top level groups and merges duplicated ones (verified on `Dragonborn.esm`: the raw file holds an empty `AVIF` group the oracle and the port both drop, so the count is one less), the ESM flag of the 3 Morrowind masters, one clamped FormID (`FalloutNV`). The numbers are also in the phase 3 status of `docs/PLAN.md`. - -#### Step 2 results (2026-10-07) - -249 plugins in 8 minutes: 125 `equal`, 46 `ofst-dropped`, 24 `header-edited`, 15 `structure-edited`, 5 `refused`, 0 `unsupported`, 34 `different`, 0 failed. The 3 Morrowind `refused` are a port gap (`must have a FormID`: the identity FormID of a Morrowind record, owed). Every `different` file was diffed at subrecord level with `cmp.py` (a scratch script: walks the groups of both files, pairs the records by FormID, decompresses, and prints the subrecords that differ) and is one of the three upstream changes listed under `different` above: 25 files whose worldspaces gained the members `wbWorldAfterLoad` adds (`CNAM` with its default `351` in a Skyrim worldspace, `INAM` too in Fallout 3), 9 files with a clamped FormID (`Oblivion.esm` 10 `REFR`, `Skyrim.esm` the `GMST` `0123C00E`, the `INFO`/`QUST` of the Fallout 3 DLC, `GunRunnersArsenal.esm` 1 `REFR`), and the Skyrim masters (359 `CELL` records of `Tamriel` rebuilt with their `XCLW` fix, sorted subrecords and a two byte `DATA`; 4 `REFR` overrides of `Dawnguard.esm` set persistent by the `ONAM` rebuild). None is a port bug by that analysis; the oracle's own save (step 6) decides. - -#### Step 6 results (2026-10-07) - -With the oracle's saves cached: 239 `equal`, 10 `refused` (7 confirmed by the oracle's refusal: the two blueprint masters and the official Starfield modules whose header `PrepareSave` would have to edit; the 3 Morrowind masters unconfirmed, as the oracle saves no Morrowind plugin), 0 `ofst-dropped`, 0 `header-edited`, 0 `structure-edited`, 0 `different`. Every file of the classes above equals the oracle's save. - -### Oracle save - -`parity oracle-save` has the GUI build of the oracle (`xTESEdit.exe`, `xFOEdit.exe`, `xSFEdit.exe` in `XEDIT_ORACLE_DIR`) load every corpus plugin with its masters and write it with `FileWriteToStream` (`crates/xtask/oracle/save.pas`), which runs the `PrepareSave` and `WriteToStream` of the GUI's save, and compares the port's save (as the round trip makes it) with it byte for byte. Statuses: - -- `equal`: identical bytes; the detail says whether the oracle's save equals the input. -- `equal-error`: both refused the save with the same message (the oracle's exception, the port's `save_refused`). Counted as passing. -- `different`: the first differing offset, the record report (dropped: only in the oracle's save, added: only in the port's) and the two files, kept in the scratch folder as `..oracle.saved` and `.port.saved`. Also a refusal on one side only, or with another message. -- `oracle-unsupported`: Morrowind. The 4.1.5q GUI runs Morrowind in its view mode only (`ToolModes := [tmView]` in `xeInit.pas`) and saves nothing, so there is no oracle save. -- `oracle-failed`: the GUI run failed (a dialog, a timeout, a hang); the detail has the reason. Nothing is cached; run the file again. -- `port-failed`, `port-memory-limit`, `oracle-only`: as for the round trip. - -The oracle's result is cached as `//-oracle-save/..saved.zst` (zstd) or `..error` (the exception's message), with the script's log as `.oracle.log`; the key hashes the plugin, its masters (found through the `MAST` subrecords, recursively) and the script, so a change to `save.pas` runs the oracle again on everything. The script text is hashed with its line endings normalised to LF (and `.gitattributes` keeps `crates/xtask/oracle/*.pas` LF), so a CRLF checkout under `core.autocrlf` finds the same cached saves. The GUI is fast: `Starfield.esm` loads and saves in about 2 minutes, the whole corpus in about an hour with `--jobs 3`, most of it the port's saves of the big masters. - -How a GUI run works (`crates/xtask/src/parity/gui.rs`), and what to check when one fails: - -- The GUI's script mode (`-script:`) loads the plugins, runs the script and stays open; `-autoload` and `-autoexit` work in the edit mode only. The harness presses OK on the module selection (`TfrmModuleSelect`, with the plugins of the private plugin list checked), waits for `done.txt` (the script writes `status.txt` and then the marker), and ends the process through its job object, which also kills it when the harness dies. -- Every run gets a private folder under `//oracle-work`: a copy of the executable (the GUI writes its log, ini and exception log next to it), copies of the plugins as the data folder (`-D:`), a plugin list (`-P:`, with `*` where the game uses it), an empty game ini (`-I:`, with `-M:` and `-G:`, so no archive and no setting of the user's `My Games` folder applies, and a game that was never started does not stop the GUI with `Fatal: Could not find ini`), and `-T:`, `-C:`, `-B:`, `-S:`. Nothing is written to a game install, the user's `Plugins.txt` or the settings next to the oracle binaries. The folder is removed after the run. -- Any other dialog that stays up for 10 seconds ends the run with its texts (a Windows task dialog shows only its buttons), as does a `Fatal:` line or `Aborted: Applying script` (a script that did not compile) in the message log of the main form, the timeout (`--oracle-timeout`, default 120 minutes) or 10 minutes without CPU time. The windows of the GUI appear on the desktop while it runs. -- JvInterpreter resolves names without case and before locals: a parameter called `path` or `name`, or a variable `e` next to `on E: Exception`, breaks a script (`Undeclared Identifier 'Message'`), so the scripts use `aPath`, `aName`, `el`. -- The script mode builds the reference information unless `-nobuildrefs` is passed; the plain save passes it. Building the references initialises every record of the loaded files, which applies their load fix-ups (required members, `AfterLoad`), so a save then differs from a plain one wherever a record has a fix-up. - -### Oracle edit sequences - -`parity oracle-edit` runs the scripted edit sequences of `crates/xtask/oracle/edits/*.json` (`--file ` selects some, `--game` the games) on both sides and compares every saved plugin, reported as `/` in `target/parity/oracle-edit.json`. A sequence names the game, the plugins to load (masters first, in the order the GUI loads them: the official order, or the file dates for Oblivion to New Vegas), the commands as `xedit batch` takes them, the plugins to save, and `build_refs` (whether the oracle builds the reference information, which a FormID change needs; see above for what that costs). The port runs the batch with a `files.save` per plugin; the oracle runs a Pascal script that `oracle_save.rs` generates from the same commands around `crates/xtask/oracle/edit.pas`: a record is the one the named file sees (`RecordByFormID` with the file's FormID), `elements.set` is `SetElementEditValues` (or the native and default setters) with the element required afterwards, `records.copy` adds the required masters and copies as the GUI's copy commands do (`wbCopyElementToFile` with the record's contents, or the child group with `deep`), `formids.change` collects `ReferencedBy` before `SetLoadOrderFormID` and updates the editable referencing records, and `formids.renumber` (with `start`, in the file itself) follows `mniNavRenumberFormIDsFromClick`. A command or parameter the translation does not know fails the sequence instead of running on one side only. The oracle's saves are cached in `//-oracle-edit`, keyed by the sequence, the generated script and the plugins. Since the copy batch: a sequence may give `answers`, the dialogs of the GUI's handlers in the order they come (a copy with overwriting shows the module selection, whose keys go to the module list with `control`, and the overwrite and deleted-record questions; "Create New File" the template selection and its `InputQuery`), and `allow_make_partial` runs the GUI with `-AllowMakePartial` and the port with `--allow-make-partial`. `records.copy` with `overwrite` takes the GUI's own path (the record shown with `JumpTo`, then the "with overwriting" menu item), so the sequence must show the record with a `records.compare` step of the same record and `from` file first; `elements.assign` and `elements.move` are `ElementAssign`/`AssignTemplateByName`/`TemplateAssign` and `MoveUp`/`MoveDown`/`InsertElement`/`RemoveElement`; `records.compare` itself is `JumpTo` (the view's alignment of sorted and aligned arrays, which an assign at a position needs); `files.new` with a template clicks `mniCreateNewFile`, and without one runs `AddNewFileName`. Since parity #33: a sequence with `build_refs` may name `refs`, records (`form_id` as the `file` sees it) whose referenced-by lists are compared after the steps, reported as `/refs`: the oracle writes `ReferencedBy` of each record's master from the reference information it built at load and kept through the edits, the port builds its index before the steps (`refs.build`, for every sequence with `build_refs`, as the GUI builds it at load: the reference build of a copied record initialises its elements, the Starfield perk activity's virtual tree among them, so a step can find a path only then), keeps it through them and reads the lists with `refs.get`; the lines are `R @ ` and `|@` per entry. A staged sequence without `strings` gives neither side the tables of a renamed plugin (the GUI gets no tables then, so the port's staging folder leaves them out too). Deliberate divergence (UPSTREAM-BUG, user decision 2026-10-16): after a record copy upstream loses the first edit of a perk activity's virtual tree (the tree is rebuilt from the old `ATAV` text when Delphi releases the copied record's elements); the port keeps the edit, and the sequences set every tree value twice so both sides end in the same state (`sf1-perk-activity-edit`, `sf1-perk-activity-masters`, the probe `probes/perk-activity.pas`). Issue #73: `sse-copy-npc-vmad` copies Nazeem [NPC_:00013BBF], whose VMAD properties are stored out of name order, into Update.esm without reading him first; `TwbArrayDef.GetSorted` answers False while `wbCopyIsRunning`, so the arrays the copy builds keep the stored order and their `#n` names, and the port resets the records a copy built when the outermost copy ends (as Delphi releases them), so a later read sees them sorted as the release does (the run case `probes/copy-vmad-order.pas`). A perk activity tree that a masters update builds again (a copy as new collapses the record) does not write its `ATAV` text while it is being built (`perk-activity-masters.pas`); the release does (UPSTREAM-BUG, not reproduced: its save of `sf1-light-masters` had every rank's `Data` at the defaults), so that sequence shows the copied PERK before the masters update and both sides keep the text. A cached oracle run whose script stopped is run again rather than replayed: a stop can come from a harness recipe the key does not hash. - -#### Step 6 results (2026-10-07) - -`oracle-save` over the 249 plugins: 239 `equal`, 7 `equal-error`, 3 `oracle-unsupported`, 0 `different` (the first run, before the port fixes listed in `docs/PLAN.md`, had 12 `different`). `oracle-edit`: `fnv-caravanpack`, `fnv-gmst` and `fo4-dlcworkshop01` equal; `fo4-dlcworkshop01-masters` different (31 records whose unchanged sorted arrays the oracle leaves unsorted after a master update and the port sorts; equal since phase 4 steps 5 and 7). - -#### String tables in edit sequences (phase 4 step 7) - -A sequence with `"strings": true` runs on a staging folder of its own (`//-oracle-edit//data`): the plugins it loads with their masters (hard links, or copies across volumes) and, in `Strings`, the tables of every localized one, taken from the game's loose `Data\Strings` or the first archive of the data folder that has them (the interface and localization archives first). The GUI gets the tables in its private data folder, and the harness closes its main form after the script (`GuiExtras::close_after`): `FormClose` runs `SaveChanged` without its dialog in the script mode and writes the modified tables, which its message log names (`Saving: Strings\.save.