Skip to content

fix(settings-override): rework override .json order #1729

Merged
davo0411 merged 5 commits into
community-shaders:devfrom
Dlizzio:fix-overwrite-system-fix
Jan 19, 2026
Merged

fix(settings-override): rework override .json order #1729
davo0411 merged 5 commits into
community-shaders:devfrom
Dlizzio:fix-overwrite-system-fix

Conversation

@Dlizzio
Copy link
Copy Markdown
Contributor

@Dlizzio Dlizzio commented Jan 17, 2026

Changes flow of overwrite system to Default → User → Overrides → User Overrides (.user files)

It is now very unlikely that the overwrite system will fail. It allows for user customization that is preserved and takes priority, while retaining the original overwrite files so they can be reverted if needed.

Summary by CodeRabbit

  • New Features

    • Per-feature user override system: save, load, delete, view merged user/mod overrides; dedicated user-overrides directory.
  • Bug Fixes

    • Failed-load messages display using error color and include required version details.
    • Reapply overrides now reliably restores mod-recommended defaults and discards local customizations.
  • Improvements

    • Unified override application flow and revised settings load/save order to better preserve user customizations.
    • Updated help text for the override reapply action.

✏️ Tip: You can customize this high-level summary in your review settings.

Changes flow of overwrite system to Default → User → Overrides → User Overrides (.user files)

It is now very unlikely that the overwrite system will fail. It allows for user customization that is preserved and takes priority, while retaining the original overwrite files so they can be reverted if needed.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Introduces a User Override System and changes override application flow: adds per-feature .user.json handling, consolidates override application via ApplyOverrides, adjusts State load/save order to apply user settings before/after overrides, and exposes a User overrides filesystem path.

Changes

Cohort / File(s) Summary
Override Management System
src/SettingsOverrideManager.h, src/SettingsOverrideManager.cpp
Added user-override public API (GetUserOverridesDirectory, LoadUserOverride, SaveUserOverride, HasUserOverride, DeleteUserOverride, GetCombinedOverrideHash, CleanupStaleUserOverrides, GetMergedOverrideSettings). Removed ApplyNewOverrides / ApplyNewFeatureOverrides. Consolidated application via ApplyOverrides, added JSON merge and combined-hash tracking, and relaxed tracking format parsing.
Settings Loading & Persistence
src/State.cpp
Reordered Load to Default → User → Overrides → User Overrides; discover/clean overrides later; per-feature override + user-override application and reload; Save now persists user override deltas relative to merged overrides.
Feature Rendering & UI
src/Feature.cpp, src/Menu/FeatureListRenderer.cpp
Feature::ReapplyOverrideSettings caches short name, deletes user override file before reapplying, reapplies to base settings and reloads merged result. DrawUnloadedUI renders failedLoadedMessage with error color or constructs missing-file/version message. Updated tooltip text clarifying "restore original overrides" semantics.
File System Utilities
src/Utils/FileSystem.h, src/Utils/FileSystem.cpp
Added Util::PathHelpers::GetUserOverridesPath() returning GetOverridesPath() / "User" and exposed it in the header.

Sequence Diagram(s)

sequenceDiagram
    participant State
    participant OverrideManager as SettingsOverrideManager
    participant Feature
    participant FS as FileSystem

    rect rgba(100, 200, 150, 0.5)
    Note over State: State::Load() — new flow with user overrides
    State->>State: Load Default Settings
    State->>State: Load User Settings
    State->>OverrideManager: DiscoverOverrides()
    OverrideManager->>FS: Scan Overrides + User Overrides dirs
    FS-->>OverrideManager: Override file list
    OverrideManager-->>State: Overrides metadata
    State->>OverrideManager: CleanupStaleUserOverrides()
    OverrideManager->>OverrideManager: Remove stale .user.json
    loop per loaded feature
        State->>OverrideManager: ApplyOverrides(featureName, baseSettings)
        OverrideManager->>OverrideManager: Merge global & feature override JSON
        OverrideManager-->>State: appliedOverrides JSON
        State->>OverrideManager: LoadUserOverride(featureName)
        OverrideManager->>FS: Read feature.user.json (if exists)
        FS-->>OverrideManager: User override JSON
        OverrideManager-->>State: Merged settings (base + overrides + user)
        State->>Feature: Reload feature with merged settings
        Feature-->>State: Feature ready
    end
    end
Loading
sequenceDiagram
    participant State
    participant Feature
    participant OverrideManager as SettingsOverrideManager
    participant FS as FileSystem

    rect rgba(150, 150, 200, 0.5)
    Note over State: State::Save() — persisting user override delta
    State->>Feature: Get current feature settings
    Feature-->>State: currentSettings JSON
    alt feature has overrides
        State->>OverrideManager: GetMergedOverrideSettings(featureName, baseSettings)
        OverrideManager->>OverrideManager: Compute combined hash & merged overrides
        OverrideManager-->>State: mergedOverrides JSON
        State->>OverrideManager: SaveUserOverride(featureName, currentSettings, mergedOverrides)
        OverrideManager->>OverrideManager: Compute user-delta (current vs merged)
        OverrideManager->>FS: Write/Update feature.user.json
        FS-->>OverrideManager: Write OK
        OverrideManager-->>State: Tracking updated
    end
    State->>State: Continue saving other features
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • doodlum
  • alandtse

Poem

🐇 I nibbled code beneath the moon,

User tweaks now hum in tune,
Overrides stacked, then softly sewn,
Deltas saved — the garden grown,
A tiny hop, settings safe and strewn.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title describes a specific technical change (reworking override JSON order), which aligns with the PR's core objective of restructuring the settings override flow from Default→Overrides→User to Default→User→Overrides→User Overrides.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Using provided base ref: fde3f5a
Using base ref: fde3f5a
Base commit date: 2026-01-17T15:30:44Z (Saturday, January 17, 2026 03:30 PM)
No actionable suggestions for changed features.

Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/SettingsOverrideManager.cpp`:
- Around line 1181-1198: The code calls tracking[trackingKey].get<std::string>()
without validating the JSON type, which can throw if the stored value is
corrupted; before calling get<std::string>(), check that
tracking[trackingKey].is_string() (or otherwise validate the type) and only
assign to storedHash when true; if the value is missing or not a string, log a
warning (using logger::warn/info) about the corrupted tracking value for
featureName, replace tracking[trackingKey] with currentHash and continue, and
then proceed with the existing comparison and update logic so
GetCombinedOverrideHash, trackingKey, storedHash and the std::filesystem::remove
path are only used when the stored hash is a valid string.
- Around line 1046-1062: The code updates the applied-overrides tracking even if
writing the user override file may have failed; after writing currentSettings to
the ofstream (variable file) and before calling
LoadAppliedOverridesTracking/SaveAppliedOverridesTracking/GetCombinedOverrideHash,
ensure the write succeeded by flushing and checking the stream state (e.g.,
file.flush() and if (file.fail() || !file.good()) ), log an error including
userFilePath and return false instead of updating tracking; only proceed to call
LoadAppliedOverridesTracking, set tracking[featureName + "_hash"] via
GetCombinedOverrideHash(featureName), and call
SaveAppliedOverridesTracking(tracking) when the file write was confirmed
successful.
🧹 Nitpick comments (1)
src/SettingsOverrideManager.cpp (1)

1016-1032: Consider extracting tolerance constants for numeric comparison.

The float comparison logic with relative/absolute tolerance is well-implemented. For better maintainability, consider extracting the magic numbers to named constants.

♻️ Optional improvement
+namespace {
+	constexpr double FLOAT_ABS_TOLERANCE = 1e-6;
+	constexpr double FLOAT_REL_TOLERANCE = 1e-5;
+}
+
 		// For numeric values, compare with tolerance to handle float32/float64 precision differences
 		// JSON stores floats as float64, but C++ features often use float32, causing precision loss
 		if (currentValue.is_number() && overrideValue.is_number()) {
 			double current = currentValue.get<double>();
 			double override = overrideValue.get<double>();
 			double diff = std::abs(current - override);
 			// Use relative tolerance for larger values, absolute for small values
-			double tolerance = std::max(1e-6, std::abs(override) * 1e-5);
+			double tolerance = std::max(FLOAT_ABS_TOLERANCE, std::abs(override) * FLOAT_REL_TOLERANCE);
 			if (diff > tolerance) {

Comment thread src/SettingsOverrideManager.cpp
Comment thread src/SettingsOverrideManager.cpp
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jan 17, 2026

✅ A pre-release build is available for this PR:
Download

Copy link
Copy Markdown
Collaborator

@davo0411 davo0411 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my approach was to load the overrides into the user settings once on initial startup
but it led to issues of them not getting applied i guess
so like default -> override -> saved into user, then gets modified
i still think that approach is cleaner

my only design concern with this pr change is we now have 4 levels of settings
default -> user -> override -> user overriding the override
its clunky

all that being said, just give me some reasoning toward the new tiered system and I am happy to move to this. Just generally, the less files the better.

Comment thread src/SettingsOverrideManager.cpp Outdated
appliedOverrides = json::object();
} else {
// Validate each tracking entry
// Validate each tracking entry - allow both old format (objects) and new format (strings)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are changing the format, don't have backwards compatibility. AI likes to do this, to "Let the old system work". we should not be supporting an arbitrary legacy format under any circumstances. old overrides will need to be changed to fit one system.

Will likely need (Human) assessment more than just AI. Use AI to understand logic flow of code segments if you need.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was initiated by me. Only refers to the tracking file which is generated by the user. Wouldn't want settings to break for the end user when they update CS.


// Invalid entry
logger::info("Invalid tracking entry for '{}', removing", key);
it = appliedOverrides.erase(it);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this we should not support, just force authors to use new system.

until we have lots of presets on nexus, we should not be supporting legacy systems.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just applies to tracking file. The actual overwrite files authors use stay unchanged.

Comment thread src/SettingsOverrideManager.cpp Outdated
const std::string jsonExt = ".json";
if (nameWithoutExt.length() >= jsonExt.length() &&
nameWithoutExt.substr(nameWithoutExt.length() - jsonExt.length()) == jsonExt) {
if (nameWithoutExt.ends_with(jsonExt)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good change


if (!userJson.is_object()) {
logger::info("User override file is not a JSON object: {}", userFilePath.string());
return false;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need handling for junk .jsons -- if a json is correctly named but has bad values, fake sliders/settings, malformed layout, etc.

it checks the object (that the file is a correctly named .json) but doesn't check the contents from what i can see

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed from the user generated overwrite files?

Tested and it looks like normal overwrite files with junk .jsons will not apply.

logger::info("Error deleting user override for {}: {}", featureName, e.what());
return false;
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check this only occurs once and not every time the override is overwritten by the user. we do not want to be writing/deleting from the disk every time a user changes a value. this would be significant concern for performance. some form of caching/doing it only after all settings are iterated, etc, would be needed.

Claude seems to think so.

Per Claude:
Current Behavior:
Every time a user adjusts a slider, SaveUserOverride likely gets called, which:
Iterates through all override settings to check for differences
Performs numeric comparisons with tolerance calculations for every number value
Either writes a complete JSON file (with directory creation, file I/O, flush, hash computation, tracking update) OR deletes the file if values match defaults
Does full filesystem operations each time

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only runs when the user intentionally saves their settings.

Comment thread src/SettingsOverrideManager.h Outdated
*/
void ReportOverrideFailure(const std::string& modName, const std::string& featureName, const std::string& errorMessage);

// === NEW: User Override System ===
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before, no new

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the whole thing should just be consolidated, not arbitrarily split (in terms of these new functions being below the old ones) should be integrated in an order that makes sense, ideally by functionality.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should consolation come when this system is overhauled with the GUI stuff? seems complex

@davo0411 davo0411 changed the title fix: overwrite system fix feat: override system logic flow rework & better error handling Jan 18, 2026
@davo0411 davo0411 changed the title feat: override system logic flow rework & better error handling fix: override system logic flow rework & better outlier handling Jan 18, 2026
@davo0411 davo0411 changed the title fix: override system logic flow rework & better outlier handling feat(overrides): logic flow rework & better outlier handling Jan 18, 2026
@davo0411 davo0411 changed the title feat(overrides): logic flow rework & better outlier handling feat(overrides): logic flow rework & better outlier handling Jan 18, 2026
@davo0411 davo0411 changed the title feat(overrides): logic flow rework & better outlier handling fix(overrides): logic flow rework & better outlier handling Jan 18, 2026
@Dlizzio Dlizzio requested a review from davo0411 January 18, 2026 15:52
@davo0411 davo0411 changed the title fix(overrides): logic flow rework & better outlier handling fix(settings-override): rework override .json order Jan 19, 2026
@davo0411 davo0411 merged commit 257857c into community-shaders:dev Jan 19, 2026
18 checks passed
@Dlizzio Dlizzio deleted the fix-overwrite-system-fix branch March 27, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants