x4-mod-interaction
Analyze how a mod interacts with the installed X4 modlist — mechanical patch collisions, behavioral event/action interactions, advisory balance fit (e.g. a vanilla-balanced weapon mod in a game runnin
Files1
View on GitHubSKILL.md.claude/skills/x4-mod-interaction/SKILL.md
GitHub<!-- GENERATED by tools/x4validate/scripts/gen-agent-trees.py from agent/. Do not edit: regenerate. -->
Produce an interaction brief for a mod against the installed set. Three tools do the heavy lifting so only structured findings + targeted reads enter context — never a whole mod's XML.
Run tools via uv from the tool dir:
cd $CLAUDE_PROJECT_DIR/tools/x4validate && uv run <tool> <args>
(tools: x4compat, x4xref, x4stats, x4similar; all read-only, no game changes.)
Pipeline (run in order; stop early if the question is narrow)
Mechanical —
x4compat check <mod-folder>(candidate mode). Reports HARD (same node replaced/removed → one silently wins), UNION-KEY (two mods define the same ware/macro id), FULL-OVERRIDE (same asset file), SOFT (benign coexisting adds). Non-union file/node overlaps only; union dirs (t/, libraries/, index/) are handled semantically. Winner is by load order (case-insensitive folder order,_sorting after letters, dependencies loaded in repeated passes -- MEASURED against the engine's own log, and re-checked bygates/load_order_oracle.py). An existing folder PATH is the copy analysed -- a same-named enabled copy is left out, so a staged update is checked as itself -- while a bare NAME means the copy in the extensions dir; the output'sCandidate analysed:line names which. A separate section, PATCHES A NODE ONLY A LATER MOD ADDS, lists<diff>ops whose target exists only once a mod loading AFTER them has run -- the engine skips those ops, so the mod silently changes less than it says. An op whoseif=guard is false at its load position changes nothing, so it is not a collision: it is listed under GUARDED OFF (guarded_offin--json). A row involving a TRUE guard says the result depends on this load order; an invalid guard keeps its row, labelled UNEVALUATED. Selectors resolve at each op's own load position: a replace/remove landing on content ANOTHER mod added is a HARD row that names the adder (and says when it overwrites only some of the elements that mod inserted) -- UNLESS the overwriting mod declares a DIRECT dependency (required or optional, by the adder's manifestid, not its folder name) on that adder: then it is an intended patch, advisory, one line per mod pair under OVERWRITES OF A DECLARED DEPENDENCY'S ADDED CONTENT (dependent_overwritesin--json). A transitive dependency does not count, and two mods overwriting the same added node still fail; an<add>into another mod's added content is the compatibility-patch pattern -- advisory, listed under ADDS INTO CONTENT ANOTHER MOD ADDED (added_intoin--json), never failing. Zero hard collisions is common and means "no structural clash" — NOT "no interaction" (see step 2).Behavioral —
x4xref(build once withx4xref build, then query). The conflicts that matter most in X4 are behavioral, not structural: two mods reacting to the same event, or one disabling an engine feature another needs. For each event/action the mod hooks:x4xref who-listens <event>— who else reacts to it (e.g.event_player_ejected)x4xref who-calls <action>— who else calls a state-changing action (e.g.set_emergency_eject_active,set_object_min_hull)x4xref cue <name>— where a cue is defined / signalled / cancelled Find the mod's own hooks first (grep its md/aiscripts forevent_/signal_cue/distinctiveset_/create_actions), then ask x4xref who ELSE touches those. Overlap here = a real behavioral interaction even when x4compat is clean.
Balance (advisory) —
x4stats wares <mod-folder>andx4stats macro <file>. For a content mod (weapons, ships, wares), shows its numbers against the EFFECTIVE tree's same-group peers (including any rebalance overhaul's rescaled prices). This is advisory grounding, not a verdict — e.g. "this turret sits at the 98th percentile of all turret prices" flags a likely balance mismatch to investigate, it does not decide it. For weapon DPS,x4stats macrogives one file's numbers + its<bullet class=>ref; chase the peer's bullet macro for a full compare.Redundancy (advisory) —
x4similar --candidate <mod-folder>for a mod adding ships. Flags fuzzy same-entity matches against base+DLC+every installed mod's ships (hull/crew/ storage/handling stat similarity at the EFFECTIVE, patched values, hard-filtered by ship class+purpose so an S fighter never matches an XL destroyer). Its header counts the ships with too few scored stats to be paired at all -- a "no near-duplicate" does not cover them. A same-registry-KEY duplicate is x4compat's UNION-KEY, not this — this catches a DIFFERENT id/name describing essentially the same ship (the "an overhaul's ship vs an independently-named clone" case). Score is a distance metric over shared numeric stats, not a power model — always eyeball flagged pairs, and note how many stats were actually compared (few shared keys = a weaker claim).Context — read the mod's README FIRST, then targeted cue reads of ONLY the colliding files x4compat/x4xref named (not the whole mod). READMEs are the highest-value artifact — authors often state compatibility and mechanism in plain English. Then the Nexus API (description/changelog) per the Nexus API-first rule in your project instructions — never scrape Nexus pages.
The interaction brief (what to hand the user)
Synthesize into: (a) mechanical collisions (with winner + whether a change is silently
dead), (b) behavioral interactions (shared events/actions/cues + predicted combined
behavior), (c) balance fit (the advisory comparison + your reasoning), (d) per-claim
confidence. Label engine-side unknowns and balance judgments as needing an in-game test —
static analysis finds the hooks and the numbers; whether it "feels right" or how the engine
sequences two same-tick reactions is a playtest. Cite file:line from x4xref/x4compat so the
user can jump to the source.
Honest limits
- Load order is MEASURED (case-insensitive folder order,
_after letters, dependencies in repeated passes; in-game probe 2026-09-26), and so are non-ASCII folder names (ßsorts aftersz) and missing REQUIRED dependencies and cycles:mods("active")leaves such a mod out, as the engine does, and every tool names it and why. Still unmeasured, and disclosed as a note when it applies: the Steam Workshop root, dependency ids that differ only in case, and where a game-root mod that waits a dependency pass falls relative to profile-root mods. - Behavioral coverage is only as good as the hooks you feed x4xref — a mod can interact via Lua or engine features that leave no MD/aiscript token (e.g. a mod that disables an engine feature another depends on; the effect is inferable but the race is a playtest).
x4statsis a distribution comparison, not a power model. Same price ≠ same effectiveness.- An empty result is not automatically a negative. Each tool indexes a defined slice; content
outside it returns nothing that looks identical to "nothing matched". Before reporting "no
interaction found", confirm the area is in scope — the toolkit's
docs/BLIND-SPOTS.mdis the register of what each tool does and does not see, with measured denominators. - Known structural exclusions to state rather than silently absorb: Lua is not analysed by any tool;
galaxy-map and character macros are outside the effective store;
x4similarscores on a fixed numeric whitelist, so two ships differing only in handling can still read as near-duplicates.