--- name: writing-bgs-load-order description: "Use when reading, editing, or generating a Bethesda Game Studio load-order file (plugins.txt / loadorder.txt) for Fallout 4, Skyrim SE/AE/VR, Fallout 76, Starfield, or when launching xEdit with a custom plugin list. Authoritative reference for the asterisk-prefix format, official masters detection, ESL handling, and the routing rules between plugins.txt editing and xEdit daemon commands." --- # Writing a BGS load order (plugins.txt) > **Harness status (2026-07-29):** The local prototype harness was destroyed by an unattributed operation and will not be rebuilt. The historical `.artifacts/mo2` example below is **DEFUNCT**. Testing now uses externally-configured Starfield or Fallout 4 MO2 instances; machine-specific details and the forensic record are private. > Sources for every claim in this skill: `Ortham/libloadorder` (the library > behind LOOT), `ModOrganizer2/modorganizer/src/profile.cpp`, > `ModOrganizer2/modorganizer-basic_games/.../game_plugins.py`, and xEdit's > official docs Section 2.8.1. ## Two file formats — know which one applies | Format | Games | File | Activation marker | |---|---|---|---| | **Asterisk format** (modern) | Fallout 4, Skyrim SE/AE/VR, Fallout 4 VR, Starfield | `plugins.txt` | leading `*` = active | | **Textfile-only** (legacy) | Skyrim LE, Oblivion, Fallout 3, Fallout NV | `plugins.txt` (active only) + sibling `loadorder.txt` (full order) | presence in `plugins.txt` = active | This skill covers the **asterisk format** in depth. If your target is Skyrim LE / Oblivion / FO3 / FNV, the rules are different — see the "Legacy format" section at the bottom. ## File location | Context | Path | |---|---| | Vanilla install (no MO2) | `%LOCALAPPDATA%\\plugins.txt` (e.g. `Fallout4`, `Skyrim Special Edition`, `Starfield`) | | MO2-managed | `\profiles\\plugins.txt` | | MO2-managed sibling | `\profiles\\loadorder.txt` (full order including inactive; MO2 keeps both) | | Agent-authored (experiments) | `an agent-owned artifacts path` — generate your own, pass to `xedit_start({ pluginsFile })` | **Ownership in MO2 use**: MO2 writes `plugins.txt` whenever the user toggles plugin checkboxes in the GUI. LOOT can also write it. If you mutate it under MO2, do it while MO2 is closed OR via mobase's `IPluginList` API — live edits while MO2 is open get overwritten on its next save. ## Asterisk format reference ``` # This file was automatically generated by Mod Organizer. *Unofficial Fallout 4 Patch.esp ← * = ACTIVE; loads first among non-vanilla ArmorKeywords.esm ← no * = present-but-INACTIVE; position preserved *HUDFramework.esm SomeDisabledMod.esp *MyMod.esp ← bottom = loads LAST = wins conflicts ``` Verbatim parser rules (from `libloadorder/src/load_order/asterisk_based.rs`): - Blank lines → ignored. - Lines starting with `#` (column 0) → comment, ignored. - Lines starting with `*` → plugin name follows; active. - Lines starting with any other character → plugin name; inactive but tracked. - **No inline comments.** `#` must be at column 0. - Encoding: Windows-1252. Plugin names with non-Win1252 characters cannot be activated until renamed. - Line endings: LF and CRLF both accepted on read. Prefer LF on write (libloadorder's default). - Order: first line = lowest load-order index = loaded first. Last line = wins conflicts. ## 排序判断 / Ordering judgment Ordering is not list housekeeping. It decides which plugin record or archive payload the game actually sees when two mods touch the same thing: later wins. For loose files, remember the separate asset layer: loose files can win over BA2/BSA archive contents even when their plugin loads earlier. Use ordering to choose whole-record / whole-archive winners; use a patch when you need one final record stitched from multiple mods. Iron law: **if the desired result cannot be expressed as "one later plugin wins over one earlier plugin," stop sorting and audit/patch the conflict.** The source-mined principle comes from BB84's Vortex-era sorting lesson and the later xEdit "终极奥义": order is theoretically replaceable by a final compatibility patch, but patching every conflicting FormID in a real pack is not realistic. A sane order reduces the patch surface; it does not eliminate patching. ```dot digraph sort_or_patch { rankdir=TB; start [label="Conflict or ordering question", shape=doublecircle]; exists [label="Do the involved plugins actually load and have valid masters?", shape=diamond]; fixfile [label="Fix plugins.txt/loadorder.txt validity first", shape=box]; author [label="Does the mod author specify order or patch requirement?", shape=diamond]; obey [label="Follow author instruction; verify with xEdit/readback", shape=box]; winner [label="Can one plugin/archive win wholesale without losing needed data?", shape=diamond]; sort [label="Solve by order: later winner goes after earlier loser; mirror loadorder.txt in MO2", shape=box]; stitch [label="Need fields/data from multiple plugins in one final state?", shape=diamond]; patch [label="Hand off to xedit-conflict-audit / xedit-automation and build or inspect a patch", shape=box]; group [label="Is this about a per-game group template (lighting/weather/major compatibility-fix style)?", shape=diamond]; kb [label="Query KB domain=load-order for the target game; if silent, mark [GAP]", shape=box]; done [label="Verify winning override / loaded file chain", shape=doublecircle]; start -> exists; exists -> fixfile [label="no"]; exists -> author [label="yes"]; fixfile -> done; author -> obey [label="yes"]; author -> winner [label="no"]; obey -> done; winner -> sort [label="yes"]; winner -> stitch [label="no"]; sort -> done; stitch -> patch [label="yes"]; stitch -> group [label="not sure"]; patch -> done; group -> kb [label="yes"]; group -> patch [label="no"]; kb -> done; } ``` ### Judgment rules - Sort solves **priority**: which complete plugin record or BA2/BSA archive wins after a later-wins cascade. - Patch solves **composition**: which fields from multiple plugins must be preserved together in one final winning record. - Group ordering is a scale tool, not magic. Put mods into purpose-based groups so order can be reasoned about in batches; same-group conflicts still need a specific winner or a patch. - Masters and official early-loading plugins are not discretionary. Infer them from the current runtime as described below; user-managed patches generally live late because they intentionally carry final stitched decisions. - Prefer the later winner that expresses the pack's system rule over incidental edits from a content mod. Example principle: if one mod imposes the pack-wide equipment-slot rule and another content mod casually edits one vanilla slot, the system-rule mod usually belongs later. Verify the actual records. - Do **not** inline per-game group templates here. For lighting, weather, major compatibility-fix placement, major overhauls, or game-specific community group layouts, query the KB first: ```text bgs_kb_query({ query: "load order group template lighting weather patch", games: [""], domains: ["load-order"] }) ``` If the KB is silent, mark `[GAP]` instead of inventing a generic position. ### LOOT / automatic sort vs manual sort | Situation | Default | |---|---| | Beginner / small pack, no author instruction, no known conflict | Use LOOT or the manager's automatic sort as a baseline, then validate. | | Author specifies order, dependency, or required compatibility patch | Follow the author first; verify with xEdit/readback. | | xEdit shows the automatic result chose the wrong winner | Manually reorder if one winner is enough; patch if fields must be stitched. | | Per-game group placement question | Query KB `load-order` records; do not hardcode a cross-game template in this skill. | | Recurring conflict family across many mods | Create/adjust a group rule, then patch only the records the group rule cannot express. | ### Red flags | Thought | Reality | |---|---| | "排序无所谓,xEdit能补一切." | True only in principle; exhaustive FormID stitching is not realistic at pack scale. Sort first to reduce the patch surface. | | "LOOT / auto-sort ran, so ordering judgment is done." | Auto-sort is a baseline. It cannot know every pack-specific winner or every author-required exception. | | "Same group means no conflict." | Same group means conflicts should be rare. When two same-group mods touch the same record, decide winner or patch. | | "Manager UI shows no conflict, so there is no conflict." | Record conflicts require xEdit readback. Asset/archive conflicts require asset-precedence inspection. | | "Just put every patch last and forget the rest." | Patches late is correct, but a patch can only express decisions you actually audited. | | "Lighting/weather/major compatibility-fix placement is universal." | Those are per-game template facts. Query KB and mark `[GAP]` if the pack has no record. | ### Rationalizations | Excuse | Reality | |---|---| | "I'll patch everything later." | That is the impossible endgame. A sane order is how you avoid patching irrelevant conflicts one by one. | | "Manual sorting is just vibes." | Manual sorting is only legitimate when tied to a known winner, author instruction, group rule, or xEdit readback. | | "The content mod should win because it adds new things." | Content mods often carry incidental edits. A systemic-rule mod may be the right later winner. | | "If two mods are incompatible, sorting harder will fix it." | Some conflicts are ordering conflicts; some are real incompatibilities. Escalate to audit instead of looping order changes. | | "Archive conflicts are invisible, so ignore them." | Invisibility is why BA2/BSA precedence must be inspected rather than guessed. | If sorting does not produce the intended winning override, hand off to `xedit-conflict-audit`: inspect the record, name the winning override, decide whether the current winner is safe, and only then route to patch authoring. ## Official / vanilla masters — infer them from the current game, do not hardcode The engine loads each game's official plugins at fixed positions before reading the user-managed `plugins.txt`. The exact set depends on the target game, installed DLC, and any Creation Club / verified-content bundles. **Do not hardcode a per-game vanilla-master list in your workflow.** Instead, derive the official masters from the user's actual MO2-managed runtime: 1. Read `\ModOrganizer.ini` to learn `gameName` and `gamePath`. 2. Once xEdit is ready, call `xedit_call({ command: "files.list", args: {} })` and look at the earliest, auto-loaded official plugins. Treat those as the immutable official master set. 3. If xEdit is not ready yet, inspect the MO2-managed game root (`\Data`) and known official DLC/content files there. Do not add, remove, or reorder them in your generated `plugins.txt`. libloadorder's writer skips these early-loading official plugins on save; MO2 re-injects them as active at read time. So whether they appear in the file or not, they load first. **Practical rule for agent-authored plugins.txt**: omit the official masters you inferred from the actual target runtime. Start your file with user-managed plugins instead. ## Six operations the agent does most often ### 1. Enable a currently-disabled plugin Prefix the line with `*`. Position unchanged. Before: ``` ArmorKeywords.esm ``` After: ``` *ArmorKeywords.esm ``` ### 2. Disable without removing Strip the leading `*`. Position preserved (the plugin stays in the file). Before: ``` *HUDFramework.esm ``` After: ``` HUDFramework.esm ``` ### 3. Remove a plugin entirely Delete the whole line. Position is dropped. (MO2 will re-append it as inactive at the end of `loadorder.txt` on next read — `plugins.txt` itself stays clean.) ### 4. Reorder (move X to load AFTER Y) Cut the line, paste it after Y. **Mirror the change in `loadorder.txt` if it exists** (MO2 maintains both files; out-of-sync state confuses MO2). ### 5. Add a brand-new plugin Append at the bottom with `*` if you want it active. ``` *MyExistingMod.esp *JustInstalledMod.esp ← new line at the end ``` The bottom = loads last = wins conflicts with everything above. If you don't want it to win conflicts, move it higher. ### 6. ESL conversion (ESP → light ESP) **Do NOT do this by editing `plugins.txt`.** The light-plugin flag lives in the plugin's file header, not in the load-order file. Route through the xEdit daemon: 1. `xedit_call({ command: "plugin.esl.analyze", args: { file: "MyMod.esp" } })` — checks whether the plugin's FormIDs fit in the ESL FE-slot range. 2. If verdict says yes: `xedit_call({ command: "plugin.esl.apply", args: { file: "MyMod.esp" } })` — sets the light-plugin flag in the file header. After flagging, the file extension stays `.esp`. The engine reads the new flag at load time and remaps the plugin into the shared FE slot. `plugins.txt` does NOT change as part of ESL conversion. ## Routing matrix: plugins.txt edit vs xEdit daemon command | Operation | Right path | Why | |---|---|---| | Activate / deactivate a plugin | **Edit `plugins.txt`** (toggle `*`) | No daemon verb exists for activation; the daemon manipulates plugin *contents*, not the activation file. | | Reorder plugins | **Edit `plugins.txt`** + mirror in `loadorder.txt` | xEdit docs 2.3 (verbatim): "Load order cannot be changed with xEdit." | | Remove a plugin entry | **Edit `plugins.txt`** | Same reason. | | Add new plugin to load order | **Edit `plugins.txt`** (append) | Daemon doesn't write the activation file. MO2 will project the mod's `.esp` via VFS once the file references it. | | Check a plugin's masters | **`xedit_call({command:"files.get_masters", args:{file:"..."}})`** | Reads the plugin header. | | Add a missing master to a plugin | **`xedit_call({command:"files.add_required_masters", args:{...}})`** | Mutates the file header. | | Convert ESP → ESL | **`plugin.esl.analyze` → `plugin.esl.apply`** | Sets header flag; load-order file unchanged. | | Clean ITM / UDR | **xEdit launcher with `-quickAutoClean`** OR daemon `files.clean_masters` | Both work; CLI faster for batch. | | Sort the load order | **External LOOT** | Neither xEdit nor MO2 sorts; LOOT does, then writes `plugins.txt`. | | Verify a load order is valid | **Combine**: read `plugins.txt`, then daemon `files.list` to confirm each `*`-prefixed file exists with required masters present. | Two-sided: file presence (libloadorder) + header consistency (xEdit). | The rule of thumb: **`plugins.txt` is an activation surface owned by MO2 (or LOOT, or the agent). The xEdit daemon never touches it.** The daemon's domain ends at the file header of individual plugins. ## Generating a custom plugins.txt for xEdit experiments When you want xEdit to load only a subset of mods (e.g., to isolate a conflict between two specific plugins), generate a custom `plugins.txt` under an **agent-owned artifacts path** and pass it to `xedit_start`: ```typescript // 1. Write the file const customPlugins = `# Agent-generated for conflict isolation between PluginA + PluginB *.esm *.esp *PluginA.esp *PluginB.esp `; await writeFile("/foo-vs-bar/plugins.txt", customPlugins); // 2. Start xEdit pointing at the custom file + the right Data dir await xedit_start({ pluginsFile: "/foo-vs-bar/plugins.txt", dataPath: "//Data", gameMode: "", }); ``` Notes for the experimental file: - You MAY include the target game's primary / official masters here if you want to make the experiment self-describing, but you do not need to. xEdit auto- loads the official masters from the Data dir regardless. - Order matters even for a 3-plugin file. Top = loads first. - The file must be Windows-1252 encodable (UTF-8 works as long as you stay in ASCII). ## plugins.txt vs modlist.txt — common confusion | File | What it lists | Prefix convention | Where | |---|---|---|---| | `plugins.txt` | `.esp/.esm/.esl` plugin filenames | `*` = active, no prefix = inactive | `/plugins.txt` | | `modlist.txt` | MO2 **mod folder names** (under `mods/`) | `+` = enabled, `-` = disabled, `*` = foreign | `/modlist.txt` | **The `*` in `modlist.txt` means "foreign mod" (MO2-managed but externally owned), NOT "active."** Mixing the two formats is the #1 way to corrupt a profile. Never copy/paste between them. ## MO2 path inspection (so you know which Data dir xEdit should target) Before launching xEdit via the agent, read MO2's config to learn the **managed game path**: ```powershell # \ModOrganizer.ini contains lines like: gameName=Fallout 4 gamePath=@ByteArray(D:\\awesome-bgs-mod-master\\.artifacts\\mo2\\Stock Game\\Fallout 4) ``` The `gamePath` value (strip the `@ByteArray(...)` wrapper) is where the agent's `xedit_start({ dataPath: ... })` should point. Specifically, the Data directory is `\Data`. Without an explicit `dataPath`, xEdit falls back to the Windows registry — which points at the platform install path, not necessarily the MO2-managed game root. That's the source of "wrong Data" bugs. ## Validation Three options for catching a malformed `plugins.txt` before launching xEdit into a broken state: 1. **xEdit one-shot smoke**: launch xEdit with `-quickAutoClean` against the file. Non-zero exit = a master is missing or a `*`-prefixed plugin doesn't resolve. Cheap and authoritative. ``` xEdit.exe -fo4 -D:"...\Data\" -P:"...\plugins.txt" -veryquickshowconflicts ``` 2. **LOOT CLI**: uses libloadorder under the hood and surfaces validation errors in its GUI / log. 3. **Manual checks**: each `*`-prefixed plugin must exist as a file in the Data dir; no duplicate lines; under 255 active full plugins + under 4096 active light plugins (`.esl` + flagged-ESP). What makes a file invalid: - More than 255 active full plugins (light plugins counted separately, see next bullet). - More than 4096 active light plugins. - Duplicate plugin lines. - A `*`-prefixed line whose file does not exist in the Data dir. - A line with a Windows-1252-unencodable filename. ## Legacy format (Skyrim LE / Oblivion / FO3 / FNV) The OLD format used by pre-asterisk games: - **No `*` prefix.** Presence of a plugin line in `plugins.txt` means it's active. Absence means it's not loaded. - **`loadorder.txt` is the canonical full-order file** (active + inactive). `plugins.txt` contains only active plugins. - `plugins.txt` uses **Windows-1252** encoding; `loadorder.txt` uses **UTF-8 without BOM**. - Max 255 active plugins (no ESL concept in these engines). - CRLF line endings standard. This format does NOT apply to FO4 / SSE / Starfield. If you're targeting one of these older games, use this section's rules; for everything else, use the asterisk format above. ## See also - `xedit-automation` — hub skill for all xEdit work; routing doctrine, anti-patterns, sub-agent recipes. - BGS KB records under `knowledge/bgs-kb/packs/core/records/` — daemon command, error-code, save-semantics, and glossary facts formerly kept in the retired `xedit-automation/xedit-knowledgebase.md` redirect. - `setting-up-bgs-modding-environment` — explains how to inspect MO2's `ModOrganizer.ini` for `gamePath` before launching xEdit. - xEdit official docs Section 2.8.1 — launcher flag reference for `-P:`, `-D:`, and friends. - `Ortham/libloadorder` documentation — the authoritative source for the asterisk format semantics.