Concept: The Settings-Provider Bridge

The four input modifiers need per-player values (deadzone, sensitivity, inversion). Rather than importing a settings class, they read a small interface - ICrimsonInputSettingsProvider - that your ULocalPlayer implements. Input stays ignorant of where settings come from; settings stay ignorant of input.

How a modifier finds the provider

Every modifier resolves the provider the same way, at evaluation time:

cpp
// UEnhancedPlayerInput -> owning APlayerController -> its ULocalPlayer -> the interface
APlayerController* PC = Cast<APlayerController>(PlayerInput->GetOuter());
ICrimsonInputSettingsProvider* Provider = PC ? Cast<ICrimsonInputSettingsProvider>(PC->GetLocalPlayer()) : nullptr;

If the cast fails (no provider), each modifier returns the input unchanged - so a project that never implements the interface simply gets vanilla behavior, never a crash.

C++ virtuals vs K2_ events

The interface exposes two parallel surfaces. The C++ virtuals (GetGamepadLookStickDeadZone, GetInvertVerticalAxis, ...) are what the modifiers call; their default implementations forward to the matching K2_ BlueprintNativeEvent. Override the virtuals in a C++ local player for zero overhead, or the K2_ events in a Blueprint local player - both reach the same modifier.

Modifier readsProvider method
Deadzone (move / look)GetGamepadMoveStickDeadZone / GetGamepadLookStickDeadZone
Sensitivity (normal / ADS)GetGamepadLookSensitivityPreset / GetGamepadTargetingSensitivityPreset
Invert axesGetInvertVerticalAxis / GetInvertHorizontalAxis
Named scalar propertiesGetSharedSettingsObject (C++ only; null by default)
GetSharedSettingsObject is opt-in
UCrimsonSettingBasedScalar reads named float/double properties off GetSharedSettingsObject() by reflection (caching the FProperty*). That method returns null by default, so the scalar modifier is inert until you return your settings object from it.
Filled on the full stack, your job standalone
(CrimsonCore - requires all Crimson plugins.) CrimsonCore's UCrimsonCoreLocalPlayer implements this interface and forwards each value to its saved settings object, so the modifiers are driven by the player's settings out of the box. Standalone it is your job: implement it on your own local player subclass and point it at your settings object. See How-To: Settings-Driven Modifiers for the full walkthrough. The split keeps CrimsonInput free of any settings-backend dependency.