💥 Mod Patch Conflict Model¶
This page describes how patch-based mods are checked before they are applied.
The goal is to catch the most common ways mods break each other:
- two mods changing the same value
- one mod removing data another mod needs
- a patch written for an older game database version
- a DLC/package patch being applied without the required package
- duplicate GUIDs in newly added entities
- risky changes that need manual review
The repository ships the Pagonia Land Patcher, which implements the v0.1 of this conflict model: it detects duplicate write targets, mismatched expected values, missing packages, and unsupported operations, and it blocks apply until the combined plan is clean.
Conflict Levels¶
| Level | Meaning | Suggested Result |
|---|---|---|
| Info | The patch is independent and expected values match | Apply allowed |
| Warning | The patch is probably valid, but touches related data | Apply only after review |
| Conflict | Two or more mods write incompatible changes | Do not apply automatically |
| Error | Target missing, old value mismatch, invalid XML, duplicate GUID, or broken reference | Do not apply |
Target Conflicts¶
A target conflict happens when two operations write the same target:
Example:
Mod A:
Sawmill construction Softwood Trunk amount: 4 -> 3
Mod B:
Sawmill construction Softwood Trunk amount: 4 -> 2
This should be a blocking conflict unless one mod explicitly declares it should load after the other and expects the changed value.
Safe chained example:
That is still worth reporting, but it is no longer accidental.
Expected Value Mismatch¶
An expected value mismatch is the most important safety check.
Example:
If the current database value is already 5, the patch should stop.
Possible reasons:
- the game updated
- another mod already changed the value
- the mod was written against a different snapshot
- the patch target is too broad or wrong
The safe behavior is to fail closed and ask for review.
Remove Conflicts¶
Remove operations are dangerous because other mods may depend on the removed node.
Example:
This should be a blocking conflict if both mods are enabled.
For this reason, first-generation mod patches should avoid removeEntity and use removeListItem only with very specific targets.
Add Conflicts¶
Adding data can conflict too.
Common add conflicts:
| Conflict | Example |
|---|---|
| Duplicate GUID | Two mods add different entities with the same GUID |
| Duplicate technical name | Two mods add MyNewDecoration |
| Duplicate recipe identifier | Two mods add the same RecipeIdentifier |
| Duplicate build menu slot or sort order | Two mods insert content into the same UI location |
| Missing dependency | A mod adds a recipe that references a resource from another disabled mod |
GUID conflicts must be blocking errors.
Name or sort-order conflicts may be warnings unless the game requires uniqueness.
Semantic Conflicts¶
Some conflicts are not direct writes to the same XML path.
Examples:
- Mod A changes
WineRecipeoutput. - Mod B changes
Wineryproduction links. - Mod C changes
Wineresource category or carry type.
These mods do not necessarily conflict, but they touch the same gameplay chain.
A future validator can detect this through generated catalogs:
Suggested result: warning, not automatic failure.
Version Drift¶
Every mod should declare the snapshot it was authored against:
The validator should compare this with the local current snapshot. Prefer exact program versions such as 1.4.0-12032+195221 over descriptive labels.
If the snapshot differs:
- continue only in dry-run mode
- require all
expectedOldValuechecks to pass - show a clear warning that the mod has not been reviewed for this database version
Package And DLC Conflicts¶
Mods should declare required and optional packages:
Patch sets can also have package requirements:
Suggested behavior:
| Situation | Result |
|---|---|
| Required package missing | Error, do not apply |
| Optional package missing | Skip optional patch set and report info |
| Patch file targets missing package folder | Error unless it belongs to skipped optional patch set |
Core patch references DLC entity but dlc1 is not required |
Warning or error depending on operation |
| DLC patch writes core entities | Allowed, but report as cross-package patch |
This matters because dlc1 extends the base database with new resources, buildings, recipes, objectives, units, NPC data, map data, and cross-package references. A mod can be valid for a DLC-enabled setup and invalid for a core-only setup.
Load Order¶
Load order should be explicit when needed and irrelevant when possible.
Good mod design:
- independent patches
- exact targets
- expected old values
- minimal writes
Load order fields:
If two mods require incompatible ordering, the validator should report a load-order cycle.
GameDatabase Overlay Conflicts (Authoring Advisor)¶
The conflicts above are about two patches fighting over the same shipped XML. A different class of conflict appears when two mods ship their own GameDatabase overlay (*.gd.xml) and both touch the same inherited entity via the engine's InheritanceMode primitives. The engine resolves these by load order — for Replace / Unload the last-loaded mod wins; Incremental is additive and stacks (see What We Know About the Engine). So the destructive modes are where competing mods silently clobber each other.
pagonia-patcher validate-mod runs a conflict-minimising authoring advisor over a mod's own overlay to flag this before it ships:
| Finding | Severity | Meaning |
|---|---|---|
usesDestructiveInheritanceMode |
info | The entity uses Replace / Unload (last-loaded-wins). If the change only adds to the inherited entity, prefer Incremental / Template so it stacks with other mods. |
unloadsReferencedEntity |
warning | An Unload whose target is still referenced elsewhere in the same overlay — the reference will dangle. Unload the dependents too, or don't. |
inheritanceConflictRisk |
info | A per-mod score (low / medium / high) from how many entities the mod Replaces or Unloads. |
Point the advisor at an unpacked game database (validate-mod --game-root <path>) and two more checks switch on, comparing your overlay against the shipped data:
| Finding | Severity | Meaning |
|---|---|---|
unloadsReferencedEntity (widened) |
warning | The unload target is still referenced anywhere in core/dlc, not just in your overlay — the dangling-reference case. |
replaceCouldBeIncremental |
warning | Your Replace keeps the inherited entity verbatim and only adds to it, so it could be an Incremental that stacks. (Conservative: any modified value means a genuine rewrite and isn't flagged.) |
Rule of thumb: the fewer entities your mod Replaces or Unloads, the more cleanly it co-exists in a player's mod set. Reach for the additive modes first.
Across mods. The advisor above lints one mod in isolation. The manager adds the cross-mod view: when you plan or deploy a profile, it checks the enabled mods in load order and warns (manager.crossModOverlayConflict) when two of them Replace/Unload the same inherited entity — naming the load-order winner and the silently-overridden mods, so you can reorder, disable one, or ask the authors to switch to additive modes.
Risk Score¶
Each operation can declare a risk level:
Suggested defaults (the values shown are the literal schema enum — note the
hyphen in very-high):
| Operation | Default risk: |
|---|---|
replaceValue |
low |
multiplyValue |
low |
addValue |
low |
replaceAttribute |
medium |
addListItem |
medium |
mergeComponent |
medium |
removeListItem |
high |
replaceNode |
high |
addEntity |
high |
removeEntity |
very-high |
The risk: field accepts only low, medium, high, or very-high. Risk is not validation. It is a review hint for humans.
Recommended First Rule Set¶
For early community experiments:
- Allow
replaceValue. - Require
expectedOldValue. - Require
entityGuid. - Require dry-run output.
- Require exact
gameDatabaseVersion. - Check required packages.
- Skip optional patch sets when their package is missing.
- Block duplicate write targets.
- Block duplicate added GUIDs.
- Warn on snapshot mismatch.
- Warn on semantic chain overlap.
- Run
scripts\validate_database.ps1after applying. - Keep backups or work from a copied test game directory.
This keeps the first patch format practical without making it too clever too early.