import 'package:rimworld_modman/mod_list.dart'; /// A class that helps find the minimum set of mods that exhibit a bug. /// /// Provides two main algorithms: /// - Binary search / bisect: Divides mods into smaller subsets to find problematic ones quickly. /// - Linear search / batching: Tests mods in small groups to systematically identify issues. /// /// These approaches help RimWorld mod users identify which mods are causing problems /// when many mods are installed. class Move { final int startIndex; final int endIndex; Move({required this.startIndex, required this.endIndex}); } class ModListTroubleshooter { final ModList originalModList; ModList currentModList; // These indices should ALWAYS represent the CURRENT selection of mods int _startIndex = 0; int _endIndex = 0; ModListTroubleshooter(ModList modList) : originalModList = modList, currentModList = modList.copyWith(), _startIndex = 0, _endIndex = modList.activeMods.length; Move binaryForwardMove() { final midIndex = (_startIndex + _endIndex) ~/ 2; return Move(startIndex: midIndex, endIndex: _endIndex); } Move binaryBackwardMove() { final midIndex = ((_startIndex + _endIndex) / 2).ceil(); return Move(startIndex: _startIndex, endIndex: midIndex); } ModList binaryForward() { final move = binaryForwardMove(); final subset = originalModList.activeMods.keys.toList().sublist( move.startIndex, move.endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _startIndex = move.startIndex; _endIndex = move.endIndex; return currentModList; } ModList binaryBackward() { final move = binaryBackwardMove(); final subset = originalModList.activeMods.keys.toList().sublist( move.startIndex, move.endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _startIndex = move.startIndex; _endIndex = move.endIndex; return currentModList; } // If the current selection is not equal to our proposed step size // We do not MOVE but instead just return the correct amount of mods from the start Move linearForwardMove({int stepSize = 20}) { var start = _startIndex; var end = _endIndex; // If we are not "in step" if (end - start == stepSize) { // Move the indices forward by the step size, step forward start += stepSize; end += stepSize; } else { end = start + stepSize; } if (end > originalModList.activeMods.length) { // If we are at the end of the list, move the start index such that we return // At most the step size amount of mods end = originalModList.activeMods.length; start = (end - stepSize).clamp(0, end); } return Move(startIndex: start, endIndex: end); } Move linearBackwardMove({int stepSize = 20}) { var start = _startIndex; var end = _endIndex; if (end - start == stepSize) { start -= stepSize; end -= stepSize; } else { start = end - stepSize; } if (start < 0) { start = 0; end = stepSize.clamp(0, originalModList.activeMods.length); } return Move(startIndex: start, endIndex: end); } ModList linearForward({int stepSize = 20}) { final move = linearForwardMove(stepSize: stepSize); final subset = originalModList.activeMods.keys.toList().sublist( move.startIndex, move.endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _startIndex = move.startIndex; _endIndex = move.endIndex; return currentModList; } ModList linearBackward({int stepSize = 20}) { final move = linearBackwardMove(stepSize: stepSize); final subset = originalModList.activeMods.keys.toList().sublist( move.startIndex, move.endIndex, ); currentModList.disableAll(); currentModList.enableMods(subset); _startIndex = move.startIndex; _endIndex = move.endIndex; return currentModList; } void reset() { currentModList = originalModList.copyWith(); } }