Let a mod ranking decide whose version of a function wins - #43
Open
TheValiantOne wants to merge 2 commits into
Open
Let a mod ranking decide whose version of a function wins#43TheValiantOne wants to merge 2 commits into
TheValiantOne wants to merge 2 commits into
Conversation
Adds an opt-in, user-supplied mod ranking that resolves the whole-function tiebreak. Until now that tiebreak was "most distinct from vanilla wins" - a guess at which side did more work, with no way for a user to say "I want this mod's version of this function." Deliberately a different lever from the existing per-file order override (FileMerger.ResolveMergeOrder): that sets the chain ORDER and must name every mod for the file it covers. This one reorders nothing. It only answers "whose code survives" at the exact point FunctionLevelMergeEngine was already going to pick a whole side - never overriding a clean, genuinely-merged result. Partial by design: rank only the mods you care about. A pair where neither side is ranked returns PreferredSide.None and behaves exactly as before, so adding a ranking can never change the outcome of a conflict it doesn't mention. A ranked mod beats an unranked one. The accumulated side of a chain step carries every mod merged into it so far, so it takes the BEST rank among them - without that, a highly-ranked mod would stop winning the moment one more mod merged on top of it, which is the opposite of what ranking it means. Configured through the existing order file's reserved "*" entry, alongside its ordinary per-path entries. That reuses all the existing plumbing at once - both hosts' --order-file, the MCP merge_conflicts orderOverrides argument, and the Vortex extension - with no new config surface. "*" is safe as a sentinel because it's a reserved character in Windows paths, so it can never collide with a real relative path; MergeConflictsHeadless strips it before ResolveMergeOrder can ever see it as a path override. Every ranking-driven decision is recorded in the same Decisions audit trail as every other non-mechanical choice, naming the ranking as the reason. 18 new tests (193 total): rank comparison in both directions, ranked-beats- unranked, the accumulated side taking its best rank, case-insensitivity, no-opinion cases (no ranking, neither side ranked, same mod both sides), and the order-file plumbing (extraction, blank trimming, and that the reserved key never reaches ResolveMergeOrder). dotnet format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FP8H6rBLCGPBFRSVsF3Kgw
A plain ToDictionary rebuilds with the default ordinal comparer, silently undoing the StringComparer.OrdinalIgnoreCase that WsmMcpTools.MergeConflicts deliberately installs (alongside separator normalization) so a differently-cased but otherwise correct path key isn't ignored. Dropping it turned those keys back into silent no-ops - but only for callers that supply a ranking, so the bug would have stayed hidden until the feature was actually used. Now reads the source dictionary's own comparer and reuses it, falling back to the default only when it can't be read - so it neither drops a deliberately-installed comparer nor imposes one on the CLI hosts, which build their order dictionary with the default and must stay exactly as case-sensitive as before. Found by an independent review pass over this branch, not by the tests - nothing exercised the interaction. 2 new tests (195 total) cover both directions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FP8H6rBLCGPBFRSVsF3Kgw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First increment of ranked overrides: an opt-in mod ranking that resolves the whole-function tiebreak.
Why this lever
Reading the code surfaced a distinction worth stating up front: merge order and tiebreak-winner are different things.
orderOverridessets the chain order, and demands you name every mod for the file it covers."Ranked overrides by originating mod" targets that second lever, which didn't exist. This adds it without touching ordering.
Behaviour
FunctionLevelMergeEnginewas already going to pick a whole side. A ranking never overrides a clean, genuinely-merged result.PreferredSide.Noneand behaves exactly as before. A ranked mod beats an unranked one.Decisionsaudit trail as every other non-mechanical decision, naming the ranking as the reason.Configuration
Through the existing order file's reserved
"*"entry:{ "*": ["modIWantToWin", "modSecondChoice"], "game\actor.ws": ["modA", "modB"] }That reuses every existing path at once — both hosts'
--order-file, the MCPmerge_conflictsorderOverridesargument, and the Vortex extension — with no new config surface.*is safe as a sentinel because it's a reserved character in Windows paths, so it can't collide with a real relative path;MergeConflictsHeadlessstrips it beforeResolveMergeOrdercan see it as a path override.Verification
18 new tests, 193 total, all passing.
dotnet format whitespace --verify-no-changesclean.Rank comparison both directions, ranked-beats-unranked on either side, the accumulated side taking its best rank, case-insensitivity, all three no-opinion cases, and the order-file plumbing (extraction, blank trimming, and that the reserved key never reaches
ResolveMergeOrder).Next
This is the by-mod increment. By-function overrides (
{"path.ws": {"CR4Player.OnSpawned": "modX"}}) are the natural follow-on and fit the same seam —ResolveUnitalready has the unit name in hand where it consultspreferredSide.AI-assisted development
Produced by Claude Code (Opus 5). Design shape (which lever, where configured, how strict) was chosen by the repo owner from options; implementation and tests are mine. Test and format results are from real runs.