Rework adaptive extrapolation: deceleration detection + dead zone + debug HUD

Replace variance-based confidence (caused constant lag) with targeted
deceleration detection: compares recent speed (last 25% of safe window)
to average speed. During steady movement ratio≈1 → zero lag.
Only reduces extrapolation when actual braking is detected.

- AdaptiveSensitivity: now a power exponent (0.1-5.0, default 1.0)
- AdaptiveDeadZone: new parameter (default 0.8) to ignore normal
  speed fluctuations and only react to real deceleration
- DebugAntiRecoilHUD: real-time display of ratio, confidence, speeds, errors
- EndPlay: auto-close CSV file when stopping play (no more locked files)
- Python script updated to match new deceleration-based algorithm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
j.foucher 2026-03-16 18:25:54 +01:00
parent fa257fb87b
commit af723c944b
4 changed files with 197 additions and 119 deletions

View File

@ -131,75 +131,71 @@ def simulate_adaptive(frames: List[Frame], sensitivity: float) -> List[Tuple[flo
pos_errors = []
aim_errors = []
# Sliding window for velocity estimation
# Sliding window for velocity estimation (matches C++ safe window ~18 samples)
window_size = 12
SMALL = 1e-10
for i in range(window_size + 1, len(frames) - 1):
# Build velocity pairs from recent real positions and aims
pos_velocities = []
aim_velocities = []
weights = []
pos_vels = []
aim_vels = []
for j in range(1, min(window_size, i)):
dt = frames[i - j].timestamp - frames[i - j - 1].timestamp
if dt > 1e-6:
pos_vel = vec_scale(vec_sub(frames[i - j].real_pos, frames[i - j - 1].real_pos), 1.0 / dt)
aim_vel = vec_scale(vec_sub(frames[i - j].real_aim, frames[i - j - 1].real_aim), 1.0 / dt)
pos_velocities.append(pos_vel)
aim_velocities.append(aim_vel)
w = (window_size - j) ** 2
weights.append(w)
pos_vels.append(pos_vel)
aim_vels.append(aim_vel)
if len(pos_velocities) < 2:
if len(pos_vels) < 4:
continue
total_w = sum(weights)
n = len(pos_vels)
# Weighted average velocity (position)
# Weighted average velocity (recent samples weighted more, matching C++)
total_w = 0.0
avg_pos_vel = (0.0, 0.0, 0.0)
avg_aim_vel = (0.0, 0.0, 0.0)
for pv, av, w in zip(pos_velocities, aim_velocities, weights):
avg_pos_vel = vec_add(avg_pos_vel, vec_scale(pv, w / total_w))
avg_aim_vel = vec_add(avg_aim_vel, vec_scale(av, w / total_w))
for k in range(n):
w = (k + 1) ** 2
avg_pos_vel = vec_add(avg_pos_vel, vec_scale(pos_vels[k], w))
avg_aim_vel = vec_add(avg_aim_vel, vec_scale(aim_vels[k], w))
total_w += w
avg_pos_vel = vec_scale(avg_pos_vel, 1.0 / total_w)
avg_aim_vel = vec_scale(avg_aim_vel, 1.0 / total_w)
# Weighted variance (position and aim separately)
pos_var_sum = 0.0
aim_var_sum = 0.0
for pv, av, w in zip(pos_velocities, aim_velocities, weights):
pd = vec_sub(pv, avg_pos_vel)
ad = vec_sub(av, avg_aim_vel)
pos_var_sum += (pd[0]**2 + pd[1]**2 + pd[2]**2) * w
aim_var_sum += (ad[0]**2 + ad[1]**2 + ad[2]**2) * w
pos_variance = pos_var_sum / total_w
aim_variance = aim_var_sum / total_w
# Deceleration detection: recent speed (last 25%) vs average speed
recent_start = max(0, n - max(1, n // 4))
recent_count = n - recent_start
recent_pos_vel = (0.0, 0.0, 0.0)
recent_aim_vel = (0.0, 0.0, 0.0)
for k in range(recent_start, n):
recent_pos_vel = vec_add(recent_pos_vel, pos_vels[k])
recent_aim_vel = vec_add(recent_aim_vel, aim_vels[k])
recent_pos_vel = vec_scale(recent_pos_vel, 1.0 / recent_count)
recent_aim_vel = vec_scale(recent_aim_vel, 1.0 / recent_count)
# Separate confidences with RELATIVE variance (matching C++ code)
pos_speed2 = sum(v**2 for v in avg_pos_vel)
aim_speed2 = sum(v**2 for v in avg_aim_vel)
avg_pos_speed = vec_len(avg_pos_vel)
avg_aim_speed = vec_len(avg_aim_vel)
recent_pos_speed = vec_len(recent_pos_vel)
recent_aim_speed = vec_len(recent_aim_vel)
# Confidence = (recentSpeed / avgSpeed) ^ sensitivity
pos_confidence = 1.0
if avg_pos_speed > SMALL:
pos_ratio = min(recent_pos_speed / avg_pos_speed, 1.0)
pos_confidence = pos_ratio ** sensitivity
aim_confidence = 1.0
if avg_aim_speed > SMALL:
aim_ratio = min(recent_aim_speed / avg_aim_speed, 1.0)
aim_confidence = aim_ratio ** sensitivity
# Extrapolation time
extrap_dt = frames[i].extrap_time if frames[i].extrap_time > 0 else 0.011
pos_confidence = 1.0
if pos_speed2 > SMALL:
pos_rel_var = pos_variance / pos_speed2
pos_confidence = 1.0 / (1.0 + pos_rel_var * sensitivity)
# Low-speed protection: if extrapolation offset < 1cm, blend confidence toward 1
pos_extrap_offset = math.sqrt(pos_speed2) * extrap_dt
pos_offset_significance = max(0.0, min(pos_extrap_offset / 1.0, 1.0)) # 1cm threshold
pos_confidence = 1.0 + (pos_confidence - 1.0) * pos_offset_significance
aim_confidence = 1.0
if aim_speed2 > SMALL:
aim_rel_var = aim_variance / aim_speed2
aim_confidence = 1.0 / (1.0 + aim_rel_var * sensitivity)
# Low-speed protection: if aim extrapolation offset is tiny, blend toward 1
aim_extrap_offset = math.sqrt(aim_speed2) * extrap_dt
aim_offset_significance = max(0.0, min(aim_extrap_offset / 0.01, 1.0)) # 0.01 unit threshold
aim_confidence = 1.0 + (aim_confidence - 1.0) * aim_offset_significance
# Predict
# Predict from last safe position
pred_pos = vec_add(frames[i - 1].real_pos, vec_scale(avg_pos_vel, extrap_dt * pos_confidence))
pred_aim_raw = vec_add(frames[i - 1].real_aim, vec_scale(avg_aim_vel, extrap_dt * aim_confidence))
pred_aim = vec_normalize(pred_aim_raw)
@ -214,15 +210,15 @@ def simulate_adaptive(frames: List[Frame], sensitivity: float) -> List[Tuple[flo
def find_optimal_parameters(frames: List[Frame]) -> dict:
"""Search for optimal AdaptiveSensitivity (no scaling factor needed)."""
print("\nSearching for optimal AdaptiveSensitivity...")
"""Search for optimal AdaptiveSensitivity (power curve exponent for deceleration detection)."""
print("\nSearching for optimal AdaptiveSensitivity (power exponent)...")
print("-" * 60)
best_score = float('inf')
best_sensitivity = 1.0
# Search grid — only sensitivity, no more scaling factor
sensitivities = [0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 50.0, 75.0, 100.0, 150.0, 200.0]
# Search grid — exponent range 0.1 to 5.0
sensitivities = [0.1, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0]
results = []

View File

@ -717,107 +717,112 @@ void UEBBarrel::PredictAdaptiveExtrapolation(double CurrentTime, FVector& OutLoc
// Compute weighted velocities from consecutive safe sample pairs
// Weight recent pairs more heavily
TArray<FVector> PosVelocities;
TArray<FVector> AimVelocities;
TArray<double> Weights;
PosVelocities.Reserve(SafeN - 1);
AimVelocities.Reserve(SafeN - 1);
Weights.Reserve(SafeN - 1);
// Compute per-pair velocities
TArray<FVector> PosVels;
TArray<FVector> AimVels;
PosVels.Reserve(SafeN - 1);
AimVels.Reserve(SafeN - 1);
for (int32 i = 1; i < SafeN; i++)
{
double dt = TransformHistory[i].Timestamp - TransformHistory[i - 1].Timestamp;
if (dt > SMALL_NUMBER)
{
FVector PosVel = (TransformHistory[i].Location - TransformHistory[i - 1].Location) / dt;
FVector AimVel = (TransformHistory[i].Aim - TransformHistory[i - 1].Aim) / dt;
// Weight: recent pairs get exponentially more weight
double w = FMath::Pow((double)i, 2.0);
PosVelocities.Add(PosVel);
AimVelocities.Add(AimVel);
Weights.Add(w);
PosVels.Add((TransformHistory[i].Location - TransformHistory[i - 1].Location) / dt);
AimVels.Add((TransformHistory[i].Aim - TransformHistory[i - 1].Aim) / dt);
}
}
if (PosVelocities.Num() == 0)
if (PosVels.Num() == 0)
{
OutLocation = TransformHistory[SafeN - 1].Location;
OutAim = TransformHistory[SafeN - 1].Aim;
return;
}
// Compute weighted average velocity
// Compute overall weighted average velocity (recent samples weighted more)
double TotalWeight = 0.0;
FVector AvgPosVel = FVector::ZeroVector;
FVector AvgAimVel = FVector::ZeroVector;
for (int32 i = 0; i < PosVelocities.Num(); i++)
for (int32 i = 0; i < PosVels.Num(); i++)
{
AvgPosVel += PosVelocities[i] * Weights[i];
AvgAimVel += AimVelocities[i] * Weights[i];
TotalWeight += Weights[i];
double w = FMath::Pow((double)(i + 1), 2.0);
AvgPosVel += PosVels[i] * w;
AvgAimVel += AimVels[i] * w;
TotalWeight += w;
}
AvgPosVel /= TotalWeight;
AvgAimVel /= TotalWeight;
// Compute weighted variance of velocity (how consistent is the velocity?)
FVector PosVelVariance = FVector::ZeroVector;
FVector AimVelVariance = FVector::ZeroVector;
// Deceleration detection: compare recent speed (last 25% of pairs) vs overall speed.
// If the user is stopping, recent speed will drop toward 0 while avg is still high.
// Confidence = recentSpeed / avgSpeed, clamped to [0, 1].
// During steady movement: ratio ≈ 1 → full extrapolation, zero lag.
// During deceleration: ratio < 1 → reduced extrapolation, prevents overshoot.
int32 RecentStart = FMath::Max(0, PosVels.Num() - FMath::Max(1, PosVels.Num() / 4));
int32 RecentCount = PosVels.Num() - RecentStart;
for (int32 i = 0; i < PosVelocities.Num(); i++)
// Recent average velocity (unweighted, just the latest samples)
FVector RecentPosVel = FVector::ZeroVector;
FVector RecentAimVel = FVector::ZeroVector;
for (int32 i = RecentStart; i < PosVels.Num(); i++)
{
FVector PosDiff = PosVelocities[i] - AvgPosVel;
FVector AimDiff = AimVelocities[i] - AvgAimVel;
PosVelVariance += FVector(PosDiff.X * PosDiff.X, PosDiff.Y * PosDiff.Y, PosDiff.Z * PosDiff.Z) * Weights[i];
AimVelVariance += FVector(AimDiff.X * AimDiff.X, AimDiff.Y * AimDiff.Y, AimDiff.Z * AimDiff.Z) * Weights[i];
RecentPosVel += PosVels[i];
RecentAimVel += AimVels[i];
}
RecentPosVel /= (double)RecentCount;
RecentAimVel /= (double)RecentCount;
// Compute speed ratio: recent / average
float AvgPosSpeed = AvgPosVel.Size();
float AvgAimSpeed = AvgAimVel.Size();
float RecentPosSpeed = RecentPosVel.Size();
float RecentAimSpeed = RecentAimVel.Size();
// Confidence: ratio of recent speed to average speed.
// Dead zone: ratios above AdaptiveDeadZone are treated as 1.0 (normal fluctuations).
// Remapped ratio: (ratio - deadzone) / (1 - deadzone), clamped to [0, 1].
// AdaptiveSensitivity is the power exponent on the remapped ratio.
float PosRatio = 1.0f;
float PosConfidence = 1.0f;
if (AvgPosSpeed > SMALL_NUMBER)
{
PosRatio = FMath::Clamp(RecentPosSpeed / AvgPosSpeed, 0.0f, 1.0f);
float PosRemapped = (AdaptiveDeadZone < 1.0f)
? FMath::Clamp((PosRatio - AdaptiveDeadZone) / (1.0f - AdaptiveDeadZone), 0.0f, 1.0f)
: (PosRatio >= 1.0f ? 1.0f : 0.0f);
PosConfidence = FMath::Pow(PosRemapped, AdaptiveSensitivity);
}
PosVelVariance /= TotalWeight;
AimVelVariance /= TotalWeight;
// Compute separate confidence for position and aim.
// Position variance is in cm^2/s^2 (large values), aim variance is in unit^2/s^2 (small values).
// Using relative variance (normalized by speed^2) so both are scale-independent.
double PosSpeed2 = AvgPosVel.SizeSquared();
double AimSpeed2 = AvgAimVel.SizeSquared();
double PosAbsVariance = PosVelVariance.X + PosVelVariance.Y + PosVelVariance.Z;
double AimAbsVariance = AimVelVariance.X + AimVelVariance.Y + AimVelVariance.Z;
float AimRatio = 1.0f;
float AimConfidence = 1.0f;
if (AvgAimSpeed > SMALL_NUMBER)
{
AimRatio = FMath::Clamp(RecentAimSpeed / AvgAimSpeed, 0.0f, 1.0f);
float AimRemapped = (AdaptiveDeadZone < 1.0f)
? FMath::Clamp((AimRatio - AdaptiveDeadZone) / (1.0f - AdaptiveDeadZone), 0.0f, 1.0f)
: (AimRatio >= 1.0f ? 1.0f : 0.0f);
AimConfidence = FMath::Pow(AimRemapped, AdaptiveSensitivity);
}
// Extrapolate from last safe sample
const FTimestampedTransform& LastSafe = TransformHistory[SafeN - 1];
double ExtrapolationTime = CurrentTime - LastSafe.Timestamp;
// Relative variance = variance / speed^2. When speed is ~0, extrapolation offset
// is negligible anyway so confidence doesn't matter — set to 1.
// Low-speed protection: at low speed the extrapolation offset (speed * dt) is tiny,
// so there's no overshoot to prevent. We blend confidence back toward 1.0 when the
// potential extrapolation offset is small (< 1cm for position, < 0.01 for aim direction).
float PosConfidence = 1.0f;
if (PosSpeed2 > SMALL_NUMBER)
{
double PosRelVar = PosAbsVariance / PosSpeed2;
PosConfidence = 1.0f / (1.0f + (float)PosRelVar * AdaptiveSensitivity);
// Write debug values for HUD display
DbgPosRatio = PosRatio;
DbgAimRatio = AimRatio;
DbgPosConfidence = PosConfidence;
DbgAimConfidence = AimConfidence;
DbgAvgPosSpeed = AvgPosSpeed;
DbgAvgAimSpeed = AvgAimSpeed;
DbgRecentPosSpeed = RecentPosSpeed;
DbgRecentAimSpeed = RecentAimSpeed;
DbgExtrapolationTime = (float)ExtrapolationTime;
// Low-speed protection: if extrapolation offset < 1cm, blend confidence toward 1
float PosExtrapOffset = FMath::Sqrt((float)PosSpeed2) * (float)ExtrapolationTime;
float PosOffsetSignificance = FMath::Clamp(PosExtrapOffset / 1.0f, 0.0f, 1.0f); // 1cm threshold
PosConfidence = FMath::Lerp(1.0f, PosConfidence, PosOffsetSignificance);
}
float AimConfidence = 1.0f;
if (AimSpeed2 > SMALL_NUMBER)
{
double AimRelVar = AimAbsVariance / AimSpeed2;
AimConfidence = 1.0f / (1.0f + (float)AimRelVar * AdaptiveSensitivity);
// Low-speed protection: if aim extrapolation offset is tiny, blend toward 1
float AimExtrapOffset = FMath::Sqrt((float)AimSpeed2) * (float)ExtrapolationTime;
float AimOffsetSignificance = FMath::Clamp(AimExtrapOffset / 0.01f, 0.0f, 1.0f); // 0.01 unit threshold
AimConfidence = FMath::Lerp(1.0f, AimConfidence, AimOffsetSignificance);
}
// Apply optional damping on top of confidence
// Apply optional damping
float DampingScale = 1.0f;
if (ExtrapolationDamping > 0.0f)
{

View File

@ -32,6 +32,19 @@ void UEBBarrel::BeginPlay()
}
}
void UEBBarrel::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// Close CSV file on stop/exit so the file isn't left locked
if (bCSVFileOpen && CSVFileHandle)
{
delete CSVFileHandle;
CSVFileHandle = nullptr;
bCSVFileOpen = false;
}
Super::EndPlay(EndPlayReason);
}
void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
@ -208,6 +221,50 @@ void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon
}
}
// Anti-Recoil Debug HUD
if (DebugAntiRecoilHUD && GEngine && AntiRecoilMode == EAntiRecoilMode::ARM_AdaptiveExtrapolation)
{
// Compute live errors
FVector RealPos = GetComponentTransform().GetLocation();
FVector RealAim = GetComponentTransform().GetUnitAxis(EAxis::X);
DbgPosError = FVector::Dist(Location, RealPos);
float AimDot = FMath::Clamp(FVector::DotProduct(Aim, RealAim), -1.0f, 1.0f);
DbgAimError = FMath::RadiansToDegrees(FMath::Acos(AimDot));
int32 HudKey = -500;
FColor HudTitle = FColor::Yellow;
FColor HudVal = FColor::White;
FColor HudGood = FColor::Green;
FColor HudWarn = FColor::Orange;
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, HudTitle,
TEXT("====== ADAPTIVE EXTRAPOLATION ======"));
// Speed ratio + confidence (position)
FColor PosColor = (DbgPosConfidence > 0.9f) ? HudGood : HudWarn;
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, PosColor,
FString::Printf(TEXT(" Pos: ratio=%.2f conf=%.2f speed=%.1f/%.1f cm/s"),
DbgPosRatio, DbgPosConfidence, DbgRecentPosSpeed, DbgAvgPosSpeed));
// Speed ratio + confidence (aim)
FColor AimColor = (DbgAimConfidence > 0.9f) ? HudGood : HudWarn;
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, AimColor,
FString::Printf(TEXT(" Aim: ratio=%.2f conf=%.2f speed=%.4f/%.4f /s"),
DbgAimRatio, DbgAimConfidence, DbgRecentAimSpeed, DbgAvgAimSpeed));
// Extrapolation time
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, HudVal,
FString::Printf(TEXT(" Extrap: %.0f ms"), DbgExtrapolationTime * 1000.0f));
// Errors
FColor PosErrColor = (DbgPosError < 2.0f) ? HudGood : (DbgPosError < 5.0f) ? HudVal : HudWarn;
FColor AimErrColor = (DbgAimError < 2.0f) ? HudGood : (DbgAimError < 5.0f) ? HudVal : HudWarn;
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, PosErrColor,
FString::Printf(TEXT(" Pos error: %.2f cm"), DbgPosError));
GEngine->AddOnScreenDebugMessage(HudKey--, 0.0f, AimErrColor,
FString::Printf(TEXT(" Aim error: %.2f deg"), DbgAimError));
}
// Calibration HUD
if (CalibrateAntiRecoil && GEngine)
{

View File

@ -31,7 +31,7 @@ enum class EAntiRecoilMode : uint8
ARM_WeightedLinearRegression UMETA(DisplayName = "Weighted Linear Regression", ToolTip = "Fits a weighted least-squares line (y=a+bt) through safe samples. Recent samples weighted higher (controlled by RegressionWeightExponent). Simple, stable, no oscillation. May overshoot on sudden stops since it assumes constant velocity."),
ARM_WeightedRegression UMETA(DisplayName = "Weighted Quadratic Regression", ToolTip = "Fits a weighted quadratic curve (y=a+bt+ct^2) through safe samples, capturing deceleration naturally. Blends smoothly between linear and quadratic based on acceleration significance. Falls back to linear with < 3 samples. Includes velocity-reversal clamping."),
ARM_KalmanFilter UMETA(DisplayName = "Kalman Filter", ToolTip = "Maintains an internal state model (position + velocity, aim + angular velocity) updated only with safe samples. Predicts forward using the estimated dynamics. Best for smooth continuous tracking with optimal noise rejection. Requires tuning ProcessNoise and MeasurementNoise for best results."),
ARM_AdaptiveExtrapolation UMETA(DisplayName = "Adaptive Extrapolation", ToolTip = "Computes weighted average velocity from safe samples, then scales extrapolation by a confidence factor based on velocity consistency. When velocity is steady (low variance), full extrapolation. When velocity changes rapidly (deceleration/direction change), extrapolation is reduced automatically. One tuning parameter: AdaptiveSensitivity.")
ARM_AdaptiveExtrapolation UMETA(DisplayName = "Adaptive Extrapolation", ToolTip = "Deceleration-aware linear extrapolation. Compares recent speed (last 25% of safe window) to average speed. During steady movement: full extrapolation (zero lag). During deceleration/stop: extrapolation is reduced proportionally. Prevents overshoot on fast draw-aim-fire sequences without adding lag during normal tracking. Tuning: AdaptiveSensitivity controls the power curve (1=linear, 2=aggressive, 0.5=gentle).")
};
USTRUCT()
@ -82,6 +82,7 @@ public:
UEBBarrel();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
@ -105,6 +106,9 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Calibration", meta = (ToolTip = "Number of shots per calibration sequence.", EditCondition = "CalibrateAntiRecoil", ClampMin = "3", ClampMax = "50"))
int32 CalibrationShotCount = 10;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug", meta = (ToolTip = "Show real-time anti-recoil prediction HUD: speed ratio, confidence, position/aim errors, extrapolation time."))
bool DebugAntiRecoilHUD = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|CSV Recording", meta = (ToolTip = "Record per-frame prediction data to CSV for offline analysis. File saved to project Saved/Logs/ folder. Toggle off to stop and close the file."))
bool RecordPredictionCSV = false;
@ -113,6 +117,19 @@ public:
FString CSVFilePath;
IFileHandle* CSVFileHandle = nullptr;
// Debug HUD state (written by const prediction functions, read by TickComponent)
mutable float DbgPosRatio = 0.0f;
mutable float DbgAimRatio = 0.0f;
mutable float DbgPosConfidence = 0.0f;
mutable float DbgAimConfidence = 0.0f;
mutable float DbgAvgPosSpeed = 0.0f;
mutable float DbgAvgAimSpeed = 0.0f;
mutable float DbgRecentPosSpeed = 0.0f;
mutable float DbgRecentAimSpeed = 0.0f;
mutable float DbgExtrapolationTime = 0.0f;
float DbgPosError = 0.0f;
float DbgAimError = 0.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Enable IMU shock simulation for testing anti-recoil prediction without firing"))
bool DebugSimulateIMUShock = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Angular perturbation intensity in degrees", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.0"))
@ -142,8 +159,11 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Kalman filter measurement noise (higher = trusts model over measurements, lower = trusts measurements). Should be low since safe samples are clean.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_KalmanFilter", ClampMin = "0.001"))
float KalmanMeasurementNoise = 0.01f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Controls how sensitive the adaptive extrapolation is to velocity changes. Higher = more aggressive reduction during velocity changes (less overshoot but more latency). Lower = more permissive extrapolation. Uses relative variance so values are scale-independent. Typical range: 50-200. Use the analyze_antirecoil.py tool to find the optimal value for your setup.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_AdaptiveExtrapolation", ClampMin = "0.01", ClampMax = "500.0"))
float AdaptiveSensitivity = 100.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Power curve exponent for deceleration detection. Controls how aggressively slowing down reduces extrapolation. confidence = (remappedRatio)^sensitivity. 1.0 = linear (gentle). 2.0 = quadratic (aggressive). 0.5 = square root (very gentle). During steady movement, ratio is ~1 so confidence is always 1 regardless of this value.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_AdaptiveExtrapolation", ClampMin = "0.1", ClampMax = "5.0"))
float AdaptiveSensitivity = 1.0f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Dead zone for deceleration detection. Speed ratios (recent/avg) above this value are treated as 1.0 (no correction). Only ratios below trigger extrapolation reduction. Higher = more tolerant to natural speed fluctuations (less false positives). Lower = more sensitive to deceleration. 0.8 = ignore normal jitter, only react to real braking.", EditCondition = "AntiRecoilMode == EAntiRecoilMode::ARM_AdaptiveExtrapolation", ClampMin = "0.0", ClampMax = "0.95"))
float AdaptiveDeadZone = 0.8f;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "AntiRecoil", meta = (ToolTip = "Velocity damping during extrapolation. 0 = disabled (default). Higher values cause extrapolated velocity to decay exponentially toward zero over the discard window. Reduces overshoot on fast draw-aim-fire sequences where the user stops moving before firing. Applies to all prediction modes except Buffer. Typical range: 5-15.", ClampMin = "0.0", ClampMax = "50.0"))
float ExtrapolationDamping = 0.0f;