Fix size sorting and add regressive test

This commit is contained in:
2025-03-17 22:29:13 +01:00
parent 878244ead0
commit 179bebf188
3 changed files with 441 additions and 24 deletions

View File

@@ -310,7 +310,14 @@ class ModList {
}
// Optimize for soft constraints
return _optimizeSoftConstraints(loadOrder: loadOrder);
_optimizeSoftConstraints(loadOrder: loadOrder);
for (final modId in loadOrder.loadOrder) {
final mod = mods[modId]!;
print(
'Mod ID: ${mod.id}, Name: ${mod.name}, Enabled: ${mod.enabled}, Size: ${mod.size}, Dependencies: ${mod.dependencies}, Load After: ${mod.loadAfter}, Load Before: ${mod.loadBefore}, Incompatibilities: ${mod.incompatibilities}',
);
}
return loadOrder;
}
/// Calculate how many soft constraints are satisfied
@@ -356,34 +363,75 @@ class ModList {
LoadOrder? loadOrder,
}) {
loadOrder ??= LoadOrder();
Map<String, int> scoreInfo = _calculateSoftConstraintsScore(
loadOrder.loadOrder,
);
// First, ensure base game and expansions are at the beginning in the correct order
List<String> baseAndExpansions = [];
List<String> harmony = [];
List<String> otherMods = [];
// Separate mods into categories
for (final modId in loadOrder.loadOrder) {
final mod = mods[modId]!;
if (modId == 'brrainz.harmony') {
harmony.add(modId);
} else if (mod.isBaseGame) {
baseAndExpansions.add(modId);
} else if (mod.isExpansion) {
baseAndExpansions.add(modId);
} else {
otherMods.add(modId);
}
}
// Sort expansions to ensure correct order
baseAndExpansions.sort((a, b) {
final modA = mods[a]!;
final modB = mods[b]!;
// Base game always first
if (modA.isBaseGame) return -1;
if (modB.isBaseGame) return 1;
// Sort expansions alphabetically by ID (which should work for Ludeon expansions)
return a.compareTo(b);
});
// Combine the lists with harmony first, then base game and expansions, then other mods
loadOrder.loadOrder.clear();
loadOrder.loadOrder.addAll(harmony);
loadOrder.loadOrder.addAll(baseAndExpansions);
// Now apply the normal optimization for the remaining mods
List<String> remainingMods = otherMods;
Map<String, int> scoreInfo = _calculateSoftConstraintsScore(remainingMods);
int bestScore = scoreInfo['satisfied']!;
int total = scoreInfo['total']!;
if (total == 0 || bestScore == total) {
// All constraints satisfied or no constraints, sort by size where possible
return _sortSizeWithinConstraints(loadOrder: loadOrder);
// All constraints satisfied or no constraints for remaining mods, sort by size where possible
_sortSizeWithinConstraints(loadOrder: loadOrder, modList: remainingMods);
loadOrder.loadOrder.addAll(remainingMods);
return loadOrder;
}
// Use a limited number of improvement passes
// Use a limited number of improvement passes for the remaining mods
for (int iteration = 0; iteration < maxIterations; iteration++) {
bool improved = false;
// Try moving each mod to improve score
for (int i = 0; i < loadOrder.loadOrder.length; i++) {
String modId = loadOrder.loadOrder[i];
for (int i = 0; i < remainingMods.length; i++) {
String modId = remainingMods[i];
Mod mod = mods[modId]!;
// Calculate current local score for this mod
Map<String, int> currentPositions = {};
for (int idx = 0; idx < loadOrder.loadOrder.length; idx++) {
currentPositions[loadOrder.loadOrder[idx]] = idx;
for (int idx = 0; idx < remainingMods.length; idx++) {
currentPositions[remainingMods[idx]] = idx;
}
// Try moving this mod to different positions
for (int newPos = 0; newPos < loadOrder.loadOrder.length; newPos++) {
for (int newPos = 0; newPos < remainingMods.length; newPos++) {
if (newPos == i) continue;
// Skip if move would break hard dependencies
@@ -392,7 +440,7 @@ class ModList {
// Moving earlier
// Check if any mod between newPos and i depends on this mod
for (int j = newPos; j < i; j++) {
String depModId = loadOrder.loadOrder[j];
String depModId = remainingMods[j];
if (mods[depModId]!.dependencies.contains(modId)) {
skip = true;
break;
@@ -402,7 +450,7 @@ class ModList {
// Moving later
// Check if this mod depends on any mod between i and newPos
for (int j = i + 1; j <= newPos; j++) {
String depModId = loadOrder.loadOrder[j];
String depModId = remainingMods[j];
if (mod.dependencies.contains(depModId)) {
skip = true;
break;
@@ -413,7 +461,7 @@ class ModList {
if (skip) continue;
// Create a new order with the mod moved
List<String> newOrder = List.from(loadOrder.loadOrder);
List<String> newOrder = List.from(remainingMods);
newOrder.removeAt(i);
newOrder.insert(newPos, modId);
@@ -425,8 +473,8 @@ class ModList {
if (newScore > bestScore) {
bestScore = newScore;
loadOrder.loadOrder.clear();
loadOrder.loadOrder.addAll(newOrder);
remainingMods.clear();
remainingMods.addAll(newOrder);
improved = true;
break; // Break inner loop, move to next mod
}
@@ -438,19 +486,27 @@ class ModList {
if (!improved) break; // If no improvements in this pass, stop
}
// After optimizing for soft constraints, sort by size where possible
return _sortSizeWithinConstraints(loadOrder: loadOrder);
// Sort by size where possible for the remaining mods
_sortSizeWithinConstraints(loadOrder: loadOrder, modList: remainingMods);
loadOrder.loadOrder.addAll(remainingMods);
return loadOrder;
}
/// Sort mods by size within compatible groups
LoadOrder _sortSizeWithinConstraints({LoadOrder? loadOrder}) {
LoadOrder _sortSizeWithinConstraints({
LoadOrder? loadOrder,
List<String>? modList,
}) {
loadOrder ??= LoadOrder();
List<String> modsToSort = modList ?? loadOrder.loadOrder;
// Find groups of mods that can be reordered without breaking constraints
List<List<String>> groups = [];
List<String> currentGroup = [];
for (int i = 0; i < loadOrder.loadOrder.length; i++) {
String modId = loadOrder.loadOrder[i];
for (int i = 0; i < modsToSort.length; i++) {
String modId = modsToSort[i];
Mod mod = mods[modId]!;
if (currentGroup.isEmpty) {
@@ -504,9 +560,15 @@ class ModList {
}
// Reconstruct the order
loadOrder.loadOrder.clear();
modsToSort.clear();
for (List<String> group in groups) {
loadOrder.loadOrder.addAll(group);
modsToSort.addAll(group);
}
// If we were given the loadOrder directly, update it
if (modList == null) {
loadOrder.loadOrder.clear();
loadOrder.loadOrder.addAll(modsToSort);
}
return loadOrder;