fix: preserve independent mod toggles

This commit is contained in:
Matthew Stanley
2026-07-23 08:39:32 -07:00
parent 9ff287c1fa
commit 50d4f456bf
3 changed files with 13 additions and 22 deletions
+8
View File
@@ -135,10 +135,18 @@ derived-disc recipe is rejected after option resolution; packages should use a
single owner package for structural transforms and dependencies/conflicts for
external ownership.
Do not split one structural option system into many mutually exclusive
full-disc packages. For example, a game-wide Tweaks system should be one package
with launcher options and a conditional recipe matrix. Choices that are
mutually exclusive by design belong inside that package as option values, while
unrelated mods should remain separate packages and compose normally.
## Resolution rules
- Installed versions are side-by-side. The launcher can select an older version
to roll back.
- Enabling or disabling one package changes only that package. It does not
silently toggle other packages.
- Enabled packages are topologically ordered by dependencies, then by stable
package/patch order.
- Missing dependencies, version mismatches, declared conflicts, dependency
-20
View File
@@ -1088,26 +1088,6 @@ bool ModPackageManager::set_enabled(const std::string& id, bool enabled, std::st
return false;
}
selections_[id].enabled = enabled;
if (!enabled) return true;
const ModPackage* package = find_selected(packages_, id, selections_[id]);
if (!package) return true;
auto conflicts_with_enabled = [&](const std::string& other_id,
const ModPackage& other) {
const auto declared = std::find(package->conflicts.begin(),
package->conflicts.end(), other_id);
if (declared != package->conflicts.end()) return true;
const auto reverse = std::find(other.conflicts.begin(), other.conflicts.end(), id);
return reverse != other.conflicts.end();
};
for (auto& [other_id, selection] : selections_) {
if (other_id == id || !selection.enabled) continue;
const ModPackage* other = find_selected(packages_, other_id, selection);
if (other && conflicts_with_enabled(other_id, *other))
selection.enabled = false;
}
return true;
}
+5 -2
View File
@@ -191,9 +191,11 @@ int main() {
check(reload.scan(&error), error.c_str());
check(reload.set_enabled("conflict.a", true, &error), error.c_str());
check(reload.set_enabled("conflict.b", true, &error), error.c_str());
check(!reload.selections().at("conflict.a").enabled &&
check(reload.selections().at("conflict.a").enabled &&
reload.selections().at("conflict.b").enabled,
"enabling a conflicting package must disable the previous package");
"enabling a package must not silently disable another package");
check(!reload.resolve("SLUS-TEST").ok,
"declared conflicts must fail resolution");
write_text(root / "packages/matrix.mod/1.0.0/manifest.toml",
manifest("matrix.mod", "1.0.0",
@@ -228,6 +230,7 @@ int main() {
"when = { title = \"rockman\", script = \"retranslation\" }\n"));
write_text(root / "packages/matrix.mod/1.0.0/assets/matrix.xdelta3", "test");
check(reload.scan(&error), error.c_str());
check(reload.set_enabled("conflict.a", false, &error), error.c_str());
check(reload.set_enabled("conflict.b", false, &error), error.c_str());
check(reload.set_enabled("matrix.mod", true, &error), error.c_str());
check(reload.set_option("matrix.mod", "title", "rockman", &error), error.c_str());