Fabric Mod Bisect Tool
—Anyone who has run a large Minecraft modpack has been there. The game crashes, the log is unhelpful, and you have a hundred mods to blame. The usual approach is to manually disable half your mods, try again, and repeat until you find the culprit. It works, but it is tedious and easy to get wrong. I wrote this tool to automate that process properly.
The source and prebuilt binaries for Windows, Linux, and macOS are on GitHub.
How It Works
The core of the tool is a bisection search. It splits the pool of candidate mods in half, asks you to run the game and report whether the issue occurred, then eliminates whichever half is clean. Each round cuts the search space in half, so even a modpack with hundreds of mods converges to a result in a handful of steps. The user interface walks you through each test one at a time, so there is no guesswork involved.
One thing that makes this more than a simple binary search is dependency handling. Mods often require other mods to be present, and naively enabling a subset of mods can produce crashes that are unrelated to the actual conflict. The tool includes a dependency resolver that automatically activates any required dependencies when a mod is selected for testing, keeping each test valid. It also reads bundled JARs so that if a conflict is caused by a library shipped inside another mod, the tool still points to the right culprit.
Some conflicts only surface when two or more mods are active together. The bisection engine handles this by continuing to narrow down the candidate set until it isolates the exact combination responsible. After a conflict is found, the tool can set those mods aside and resume searching the rest of the pool, finding any number of separate, unrelated conflicts in one session.
The algorithm described in the appendix (IMCS) is my own approach, designed specifically for this use case. It is optimized for situations where only a small number of conflicts exist within a large set of components, focusing on minimizing the number of required test runs by isolating one conflicting element at a time. In contrast, delta debugging (ddmin) and QuickXplain (QXP) have a lot of overhead in such scenarios, but perform better when the number of conflicts is large.
Managing the Search
The tool gives you fine-grained control over which mods participate in the search. A mod can be force-enabled if it must always be present for the issue to reproduce, force-disabled if you already know it is safe to exclude, or omitted to remove it from the candidate pool while still allowing the resolver to activate it as a dependency for other mods. A history page logs every test and its outcome so you can review how the tool arrived at its conclusion, and a live log page exposes internal diagnostics for bug reports.
The tool also supports a dependency override file for cases where a mod's metadata is missing or incorrect. Overrides can be placed next to the executable, in the Minecraft config folder, or left to the tool's own built-in list, with a clear priority order between them.
Future Work
One limitation is that a test result is not always simply "Good" or "Fail". In practice, other issues can mask the real problem and lead to indeterminate outcomes, for example due to missing or incorrect dependency metadata. The tool currently does not handle such cases, but I have a potential approach in mind to address this in the future.
Appendix
Algorithm: Iterative Minimal Conflict Search (IMCS)
The Iterative Minimal Conflict Search (IMCS) algorithm is a novel, highly efficient method for identifying a 1-minimal conflict set from a larger collection
of components. Building upon the core principles of binary search and iterative component isolation, IMCS significantly refines traditional bisection
techniques. Unlike the classic ddmin algorithm, which struggles with multi-component conflicts due to its exponential increase in test calls when
faced with union issues, IMCS maintains stable and predictable O(p log n) performance. While sharing the same optimal theoretical complexity as QuickXplain (QXP), IMCS distinguishes itself by adopting a "lean start" strategy, precisely targeting individual conflict elements and avoiding QXP's
upfront speculative tests. This results in superior practical performance and significantly lower variance for sparse problems, making IMCS ideally suited for real-world
troubleshooting scenarios.
1. Objective
To efficiently identify a 1-minimal conflict set of size p from a larger superset of n components. A conflict set is defined as the smallest
subset of components that causes a system failure (or a designated undesirable outcome) when tested together.
2. Core Principle
The IMCS algorithm operates on a "lean start, iterative isolation" principle. It fundamentally avoids the high overhead of speculative testing on large
component sets. Instead, it executes a series of highly efficient, independent binary searches. Each search is tasked with identifying exactly one new conflict element that contributes to the system's failure. This iterative process guarantees stable, predictable performance and is mathematically
optimized for sparse conflicts (where p is much smaller than n), which is the common scenario in troubleshooting complex systems.
3. Algorithm Description
The algorithm consists of a main procedure, FindConflictSet, and a recursive helper, FindNextConflictElement.
Definitions:
- InitialCandidates
- The initial superset of all
ncomponents. - ConflictSet
- The set of components confirmed to be part of the minimal conflict set.
- CandidateSet
- The set of components currently being considered for inclusion in the
ConflictSet. - StableSet
- A set of components that has been tested together in the current search context and found to be stable (i.e., does not cause a failure). This set serves as the baseline for subsequent tests.
- test(S)
- A black-box function that returns
FAILif the system exhibits the undesirable outcome when configured with setSof components, andGOODotherwise.
Implicit Definitions:
- ClearedSet
- An implicit subset of StableSet (specifically,
StableSet\ConflictSet) comprising components that have been tested and found not to contribute to the current system failure in their respective search contexts.
Main Procedure: FindConflictSet
function FindConflictSet(InitialCandidates):
ConflictSet ← {}
CandidateSet ← InitialCandidates
loop indefinitely:
// Find the next single component that, in conjunction with the current ConflictSet, contributes to the failure.
nextElement ← FindNextConflictElement(StableSet=ConflictSet, CandidateSet=CandidateSet)
// If no additional conflict element can be found, the process is complete.
if nextElement is null:
break
// Add the found element to the confirmed ConflictSet and remove it from the candidate pool.
ConflictSet ← ConflictSet ∪ {nextElement}
CandidateSet ← CandidateSet \ {nextElement}
// Optimization: Test if the current ConflictSet is already a complete, minimal set.
// If it causes failure, we can terminate early without searching for more components.
if test(ConflictSet) is FAIL:
break
return ConflictSet Helper Procedure: FindNextConflictElement
function FindNextConflictElement(StableSet, CandidateSet):
// Base Case 1: No more candidates to test.
if CandidateSet is empty:
return null
// Base Case 2: Handles the initial call if CandidateSet has only one element.
if size(CandidateSet) = 1:
let c be the single element in CandidateSet
if test(StableSet ∪ {c}) is FAIL:
return c
else:
return null
// Recursive Step: Divide and conquer.
Split CandidateSet into two halves, C₁ and C₂.
// Test the first half in conjunction with the current StableSet.
if test(StableSet ∪ C₁) is FAIL:
// The next conflict element is in C₁.
// Optimization: If C₁ is a single element, it must be the one.
if size(C₁) = 1:
return the single element in C₁
else:
return FindNextConflictElement(StableSet, C₁)
// Otherwise, the first half is safe. Add it to the StableSet and search C₂.
else:
newStableSet ← StableSet ∪ C₁
// Optimization: The next conflict element might be in C₂.
// If C₂ is a single element, test it directly.
if size(C₂) = 1:
let c be the single element in C₂
if test(newStableSet ∪ {c}) is FAIL:
return c
else:
return null // This was the last possible conflict element.
else:
return FindNextConflictElement(newStableSet, C₂) 4. Complexity Analysis
Time Complexity: O(p log n)
The algorithm's total cost is dominated by the p calls to the FindNextConflictElement procedure. Each call performs a binary search
on a diminishing set of candidates (from n down to n-p+1), with a cost of O(log n). Therefore, the total time
complexity is O(p log n).
Space Complexity: O(n)
The algorithm requires storing the set of candidates, which is initially of size n. The recursion depth of the helper function is O(log n).
5. Extension: Finding All Independent Conflicts (IMCS_Enumerator)
The core IMCS algorithm finds a single conflict set. The IMCS_Enumerator is a meta-procedure that extends this to discover all independent minimal
conflict sets in a system that may have multiple unrelated faults.
A persistent, cross-iteration test cache ("knowledge base") is not used. Such a cache is unworkable in practice for two fundamental reasons. First, a FAIL result is only relevant to its specific set of components; once a conflict element from that set is found and removed, that exact test can never be run again, rendering
the cached result useless. Second, a GOOD result is context-dependent on the user's current focus; caching it could incorrectly mask a different, independent
issue in a subsequent search. Therefore, the only knowledge that can be safely and usefully persisted between iterations is the reduction of the candidate pool
itself.
Meta-Procedure: IMCS_Enumerator
function IMCS_Enumerator(InitialCandidates):
AllConflictSets ← []
CandidateSet ← InitialCandidates
loop indefinitely:
// Find the next conflict set using a fresh IMCS run. This ensures that
// knowledge from previous runs does not incorrectly influence the current search.
newConflictSet ← FindConflictSet(CandidateSet)
// If IMCS returns an empty set, no more conflicts exist among the candidates.
if newConflictSet is empty:
break
// A new independent conflict has been found.
add newConflictSet to AllConflictSets
// The only safe and persistent knowledge transfer is shrinking the problem space
// by removing the components of the just-found conflict.
CandidateSet ← CandidateSet \ newConflictSet
return AllConflictSets 6. Capabilities and Limitations
The IMCS algorithm suite is highly optimized for a specific class of diagnostic problems.
Capabilities & Strengths
- Optimized for Sparse Conflicts: The algorithm's primary strength is its exceptional
O(p log n)performance and low variance when finding a small number (p) of conflict elements within a large set (n). This makes it ideal for real-world troubleshooting. - Black-Box Operation: IMCS requires no internal knowledge of the system being tested. It operates purely on the
GOOD/FAILoutcome of tests, making it universally applicable. - Low Overhead & High Stability: The "lean start" strategy avoids wasteful speculative tests, and the iterative nature results in extremely stable, predictable performance with minimal variance, as confirmed by benchmarks.
- Complete Conflict Enumeration:The
IMCS_Enumeratorextension provides a state-of-the-art method for enumerating all separate, unrelated conflict sets efficiently.
Limitations
- Finding Non-Minimal Supersets: IMCS is designed to find only the smallest set that causes a failure (
1-minimal). It will not report larger sets that also fail but contain non-essential components. - Conflict Prioritization: The algorithm finds conflict sets in an order determined by the binary search path, not by any measure of severity or probability.
- Dense Conflicts: As demonstrated by benchmarks against
QuickXplain, IMCS is less efficient for "dense" problems wherepis a large fraction ofn. In such scenarios, algorithms that leverage information reuse more aggressively may perform better. - Non-Deterministic Systems: The algorithm relies on the system behaving deterministically. If a test on the same set of components can produce both
GOODandFAILresults, IMCS may fail to find a consistent conflict set or may terminate with an incorrect result.