skypatcher-authoring · diff
git:20261009.2a4250f to git:20261103.a61e12e
10 added, 41 removed. Audit A to A.
---
name: skypatcher-authoring
description: >-
Authors and interprets SkyPatcher INI patches — runtime, no-ESP edits that filter Bethesda records and set, add or remove those records' own fields (SkyPatcher 6.4.1 grammar). Load before any SkyPatcher line — the `Plugin.esp|FormID` addressing, the per-type subfolder and the filename gate are non-obvious, and a wrong token fails silently. Use when writing or auditing a SkyPatcher `.ini`, rebalancing weapons, armor, NPCs or leveled lists without an ESP, putting items into a container or chest, or asking why a patch line isn't applying. Not for distribution — distribution to NPCs by group (faction, race, level, trait) is SPID, keywords onto item records is KID.
compatibility: Requires the houseCARL MCP server and a configured Mod Organizer 2 instance.
---
# SkyPatcher Authoring
SkyPatcher is an SKSE plugin that edits Bethesda records at runtime from plain-text INI files — no
ESP or ESL is produced for the edit itself. This skill composes and reads those patch strings and
places the file where SkyPatcher will read it. SkyPatcher edits a record's own fields, an individual
NPC's included; it does not distribute. Two neighbouring jobs are someone else's: what fields a
- record has at the Mutagen/xEdit level, with their types and writability, is
- `housecarl:mutagen-reference`; distributing a form to NPCs is `housecarl:spid-authoring`, which is
- *best* for groups — faction, race, level, trait — and can also name one NPC, though for a single NPC
- SkyPatcher is usually the *better* fit; a keyword onto item records is `housecarl:kid-authoring`. On
- a Codex flat install these siblings are the bare folder names — `mutagen-reference`, `spid-authoring`,
- `kid-authoring`.
+ record has at the Mutagen/xEdit level, with their types and writability, is `mutagen-reference`;
+ distributing a form to NPCs is `spid-authoring`, which is *best* for groups — faction, race, level,
+ trait — and can also name one NPC, though for a single NPC SkyPatcher is usually the *better* fit; a
+ keyword onto item records is `kid-authoring`.
## Route to the record type
Find the record type in the table, then read `references/<file>.md` — that type's own filters,
operations and worked examples. Filter and operation availability varies per type, so the record
file is authoritative; do not carry one across from another type. Read one record file, not the set.
| Record type (xEdit sig) | Subfolder | Primary filter | Reference file |
|---|---|---|---|
| NPC (NPC_) | `npc` | `filterByNpcs` | `references/npc.md` |
| Weapon (WEAP) | `weapon` | `filterByWeapons` | `references/weapon.md` |
| Armor (ARMO) | `armor` | `filterByArmors` | `references/armor.md` |
| Ammo (AMMO) | `ammo` | `filterByAmmos` | `references/ammo.md` |
| Spell (SPEL) | `spell` | `filterBySpells` | `references/spell.md` |
| Scroll (SCRL) | `scroll` | `filterByScrolls` | `references/scroll.md` |
| Enchantment (ENCH) | `enchantment` | `filterByEnchs` | `references/enchantment.md` |
| Magic Effect (MGEF) | `magicEffect` | `filterByMgefs` | `references/magic-effect.md` |
| Alchemy / Ingestible (ALCH) | `ingestible` | `filterByAlchs` | `references/alchemy-ingestible.md` |
| Ingredient (INGR) | `ingredient` | `filterByIngs` | `references/ingredient.md` |
| Book (BOOK) | `book` | `filterByBooks` | `references/book.md` |
| Misc Item (MISC) | `misc` | `filterByMiscs` | `references/misc.md` |
| Soul Gem (SLGM) | `soulGem` | `filterBySoulGems` | `references/soul-gem.md` |
| Outfit (OTFT) | `outfit` | `filterByOutfits` | `references/outfit.md` |
| FormList (FLST) | `formList` | `filterByFormLists` | `references/formlist.md` |
| Leveled List (LVLI / LVLN) | `leveledList` | `filterByLLs` / `filterByLLNPCs` | `references/leveled-list.md` |
| Container (CONT) | `container` | `filterByContainers` | `references/container.md` |
| Constructible Object (COBJ) | `constructibleObject` | `filterByCobjs` | `references/constructible-object.md` |
| Cell (CELL) | `cell` | `filterByCells` | `references/cell.md` |
| Location (LCTN) | `location` | `filterByLocations` | `references/location.md` |
| Encounter Zone (ECZN) | `encounterzone` | `filterByEncounterZones` | `references/encounter-zone.md` |
| Reference (REFR) | `reference` | `filterByRefs` | `references/placed-reference.md` |
| Faction (FACT) | `faction` | `filterByFactions` | `references/faction.md` |
| Movement Type (MOVT) | `movementType` | `filterByMovementTypes` | `references/movement-type.md` |
| Projectile (PROJ) | `projectile` | `filterByProjectiles` | `references/projectile.md` |
| Race (RACE) | `race` | `filterByRaces` | `references/race.md` |
| Race Hook (RACE) | `raceHook` | `filterByRaces` | `references/race-hook.md` |
| Object Modification (OMOD) | `objectModification` *(unconfirmed)* | *(undocumented)* | `references/object-modification.md` |
Read `references/grammar-core.md` only when the record file leaves a syntax question open — the
shared patch-string structure, the addressing rules and the operation conventions live there.
Shared enums — cast types, actor values, soul types, the biped slot index — are in
`references/value-tables.md`; open it when a record file names one.
## Compose the patch
1. **Pick the filter.** The primary filter by form from the table, or a cross-cutting one the record
file lists (`filterByKeywords`, `filterByEditorIdContains`, `filterByModNames`, `hasPlugins`).
A line with no filter patches every record of that type.
2. **Address the forms** as `Plugin.esp|FormID`, copied whole from xEdit or the Creation Kit, or by
EditorID — except on the FormID-only operations, which the record file and `grammar-core.md` name.
3. **Build the string:** chain segments with `:`, list values with `,`, pack compound values with
`~` (`mgefsToAdd=Plugin.esp|id~Magnitude~Duration~Area`). Rename with `fullName=~New Name~`;
clear a form field with `null`.
4. **Pick the operation from the record file**, preferring a relative `…Mult` or `…ToAdd` over an
absolute set wherever the current winner is generated or the order is tiered — an absolute set
flattens a whole balance ladder to one number, a multiply preserves it.
Worked pair. Ask: "double the damage of every iron weapon in my load order, no ESP." Line:
```ini
filterByKeywords=WeapMaterialIron:attackDamageMult=2
```
The keyword is addressed by EditorID here; `Plugin.esp|FormID`, copied whole from xEdit, is the
other legal form and the only one on a FormID-only operation.
File: `Data/SKSE/Plugins/SkyPatcher/weapon/MyBalance/ironWeapons.ini` — a plain (always-loading)
name, nested under a mod-specific folder, in the `weapon` subfolder the table gives.
## Place the INI
The INI goes at `Data/SKSE/Plugins/SkyPatcher/<subfolder>/<name>.ini`, with `<subfolder>` taken from
the table. A plain filename always loads; a filename that matches a plugin (`Plugin.esp.ini`) loads
only when that plugin is active, so it self-gates — the right shape for a patch against one mod, the
wrong shape when the targets span many plugins. Nest a plugin-named INI in a mod-specific subfolder
so another mod shipping the same filename cannot overwrite it. Comments start with `;`.
Read `references/placement-and-conflicts.md` before naming the file — the exact subfolder casing,
the per-type toggles, the global `SkyPatcher.ini` switches and the filename-order conflict rule.
The job needs three things from the user: which records to hit, what the field change is, and
whether the INI may be installed into the live setup or only written to a working folder.
## Check before you install
- Prove the target set, every address, and the draft file itself before anything is placed in a mod.
-
- - The set: `housecarl_records` with `types=`, `plugins=` and `where=` for the set the filter means to
- hit, plus `counts_only=true` for the cheap census. Record the count and the epoch stamp beside it.
- - Each address: `housecarl_records` with `formids=["012EB7:Skyrim.esm"]` and the default summary
- rows — a FormID that resolves to nothing here resolves to nothing in
- game, silently.
- - The draft: `housecarl_records` with `formids=` a target and `source={"overlay": "skypatcher",
- "state": "post", "ini": "<absolute path to the draft .ini>", "subfolder": "weapon"}` reads the
- record as the game would see it once that draft is placed — the live layer plus the draft, sorted
- into its type folder by filename. Drop `subfolder` when the draft already sits in a folder of that
- name. Keep that pole on `source=` and pass the plain post pole,
- `versus={"overlay": "skypatcher", "state": "post"}`, with `project={"form": "delta"}` for the
- draft's own effect and nothing else. Warnings the replay produces for a draft line — an unknown
- key, an op with no field mapping, a filter it cannot evaluate — render beside the answer under
- the draft's path.
-
- Stop when the count is what you meant, every address resolves, the delta is the change you intended,
- and the replay warned on none of your lines.
-
- ## Check after you install
+ Prove the target set and every address before anything is placed in a mod.
- Once the INI is installed, three reads prove it landed.
+ - The set: count the records the filter means to hit, and record the count beside the line.
+ - Each address: resolve each `Plugin.esp|FormID` against the load order — a FormID that resolves to
+ nothing here resolves to nothing in game, silently.
- - `housecarl_skypatcher_layer` with `filter=` the type folder or the INI filename, for the
- file-level verdict — whether the file is read at all, where it sorts, what it conflicts with, and
- which of its ITM findings (an intra-file dead write, a cross-INI duplicate, a no-op write) name
- your file. The first two are authoring slips to fix at the source, and a dead write hides from the
- delta below, which shows only the value that survived it.
- - `housecarl_records` with `formids=` a target, `source={"overlay": "skypatcher", "state": "post"}`,
- `versus={"overlay": "skypatcher", "state": "pre"}` and `project={"form": "delta"}`, for the
- before → after on that record, with the replay's warnings for the placed file's lines beside it.
- - `housecarl_skse` with `findings='config'` and `filter=` the INI filename, which resolves every
- `Plugin.esp|FormID` the file contains to OK, PLUGIN MISSING, DANGLING or UNPARSEABLE — the machine
- check for a truncated or wrong FormID.
+ Stop when the count is what you meant and every address resolves.
## Never invent a token
Every filter, operation and value comes from the bundled reference. If a token is not there, say so
rather than writing a plausible-sounding one: in game SkyPatcher skips a line it cannot parse and
skips a FormID it cannot resolve, both silently and with no log line, so a fabricated token costs a
- debugging session and produces nothing to debug. The replay warns where the game does not, which is
- a net under a mistake, not a licence to guess. Object Modification (OMOD) is enabled in SkyPatcher
+ debugging session and produces nothing to debug. Object Modification (OMOD) is enabled in SkyPatcher
but has no documented grammar — hand the user the gap and the leads in its row above, never a guess.
Two soft spots in the corpus, and how to read them. Where a worked example disagrees with its own
file's filter list, the primary filter in the table above wins and the example is a carried-over
article typo. A token the corpus marks unverified against the DLL, or names without a grammar, is a
warn case, not a use case.
## Common mistakes, and the rule that replaces each
- **Wrong subfolder.** A weapon patch under `npc/` is read by the wrong patcher and does nothing.
Take the subfolder from the table.
- **A filter or operation assumed by analogy.** `filterByNameContains` exists for armor and not for
every type. Read it out of that record's own file.
- **A truncated or wrong FormID.** Copy it whole; an unresolvable form is skipped in silence and
looks exactly like a syntax bug.
- **Forgetting the player exception.** Race and keyword filters always exclude the player. Patch the
player with `filterByNpcs=Skyrim.esm|7` alone, no other filter on the line.
- **An EditorID on a FormID-only operation** (NPC `objectsToAdd` / `factionsToAdd`, Outfit /
FormList / Leveled List `formsToReplace`) — these take `Plugin.esp|FormID` only.
## Notes
- **Provenance and floor.** The reference corpus is reconstructed from SkyPatcher's official Nexus
documentation at **v6.4.1**, covering 27 record types plus one documented gap (OMOD). On a
SkyPatcher version bump, re-derive from the updated articles before trusting it for new
operations.
- **Conflict model.** SkyPatcher's low-conflict property is real but not magic: same-field set
operations still resolve by filename order, and only add/remove operations truly accumulate.
Say so when a user layers several patches on one record.
- **Lookup without authoring.** The same corpus answers "what filters does this record type
support" or "what are the legal cast types" with no patch written at all.