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:
parent
fa257fb87b
commit
af723c944b
@ -131,75 +131,71 @@ def simulate_adaptive(frames: List[Frame], sensitivity: float) -> List[Tuple[flo
|
|||||||
pos_errors = []
|
pos_errors = []
|
||||||
aim_errors = []
|
aim_errors = []
|
||||||
|
|
||||||
# Sliding window for velocity estimation
|
# Sliding window for velocity estimation (matches C++ safe window ~18 samples)
|
||||||
window_size = 12
|
window_size = 12
|
||||||
|
|
||||||
SMALL = 1e-10
|
SMALL = 1e-10
|
||||||
|
|
||||||
for i in range(window_size + 1, len(frames) - 1):
|
for i in range(window_size + 1, len(frames) - 1):
|
||||||
# Build velocity pairs from recent real positions and aims
|
# Build velocity pairs from recent real positions and aims
|
||||||
pos_velocities = []
|
pos_vels = []
|
||||||
aim_velocities = []
|
aim_vels = []
|
||||||
weights = []
|
|
||||||
for j in range(1, min(window_size, i)):
|
for j in range(1, min(window_size, i)):
|
||||||
dt = frames[i - j].timestamp - frames[i - j - 1].timestamp
|
dt = frames[i - j].timestamp - frames[i - j - 1].timestamp
|
||||||
if dt > 1e-6:
|
if dt > 1e-6:
|
||||||
pos_vel = vec_scale(vec_sub(frames[i - j].real_pos, frames[i - j - 1].real_pos), 1.0 / dt)
|
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)
|
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)
|
pos_vels.append(pos_vel)
|
||||||
aim_velocities.append(aim_vel)
|
aim_vels.append(aim_vel)
|
||||||
w = (window_size - j) ** 2
|
|
||||||
weights.append(w)
|
|
||||||
|
|
||||||
if len(pos_velocities) < 2:
|
if len(pos_vels) < 4:
|
||||||
continue
|
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_pos_vel = (0.0, 0.0, 0.0)
|
||||||
avg_aim_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):
|
for k in range(n):
|
||||||
avg_pos_vel = vec_add(avg_pos_vel, vec_scale(pv, w / total_w))
|
w = (k + 1) ** 2
|
||||||
avg_aim_vel = vec_add(avg_aim_vel, vec_scale(av, w / total_w))
|
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)
|
# Deceleration detection: recent speed (last 25%) vs average speed
|
||||||
pos_var_sum = 0.0
|
recent_start = max(0, n - max(1, n // 4))
|
||||||
aim_var_sum = 0.0
|
recent_count = n - recent_start
|
||||||
for pv, av, w in zip(pos_velocities, aim_velocities, weights):
|
recent_pos_vel = (0.0, 0.0, 0.0)
|
||||||
pd = vec_sub(pv, avg_pos_vel)
|
recent_aim_vel = (0.0, 0.0, 0.0)
|
||||||
ad = vec_sub(av, avg_aim_vel)
|
for k in range(recent_start, n):
|
||||||
pos_var_sum += (pd[0]**2 + pd[1]**2 + pd[2]**2) * w
|
recent_pos_vel = vec_add(recent_pos_vel, pos_vels[k])
|
||||||
aim_var_sum += (ad[0]**2 + ad[1]**2 + ad[2]**2) * w
|
recent_aim_vel = vec_add(recent_aim_vel, aim_vels[k])
|
||||||
pos_variance = pos_var_sum / total_w
|
recent_pos_vel = vec_scale(recent_pos_vel, 1.0 / recent_count)
|
||||||
aim_variance = aim_var_sum / total_w
|
recent_aim_vel = vec_scale(recent_aim_vel, 1.0 / recent_count)
|
||||||
|
|
||||||
# Separate confidences with RELATIVE variance (matching C++ code)
|
avg_pos_speed = vec_len(avg_pos_vel)
|
||||||
pos_speed2 = sum(v**2 for v in avg_pos_vel)
|
avg_aim_speed = vec_len(avg_aim_vel)
|
||||||
aim_speed2 = sum(v**2 for v in 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
|
# Extrapolation time
|
||||||
extrap_dt = frames[i].extrap_time if frames[i].extrap_time > 0 else 0.011
|
extrap_dt = frames[i].extrap_time if frames[i].extrap_time > 0 else 0.011
|
||||||
|
|
||||||
pos_confidence = 1.0
|
# Predict from last safe position
|
||||||
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
|
|
||||||
pred_pos = vec_add(frames[i - 1].real_pos, vec_scale(avg_pos_vel, extrap_dt * pos_confidence))
|
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_raw = vec_add(frames[i - 1].real_aim, vec_scale(avg_aim_vel, extrap_dt * aim_confidence))
|
||||||
pred_aim = vec_normalize(pred_aim_raw)
|
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:
|
def find_optimal_parameters(frames: List[Frame]) -> dict:
|
||||||
"""Search for optimal AdaptiveSensitivity (no scaling factor needed)."""
|
"""Search for optimal AdaptiveSensitivity (power curve exponent for deceleration detection)."""
|
||||||
print("\nSearching for optimal AdaptiveSensitivity...")
|
print("\nSearching for optimal AdaptiveSensitivity (power exponent)...")
|
||||||
print("-" * 60)
|
print("-" * 60)
|
||||||
|
|
||||||
best_score = float('inf')
|
best_score = float('inf')
|
||||||
best_sensitivity = 1.0
|
best_sensitivity = 1.0
|
||||||
|
|
||||||
# Search grid — only sensitivity, no more scaling factor
|
# Search grid — exponent range 0.1 to 5.0
|
||||||
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]
|
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 = []
|
results = []
|
||||||
|
|
||||||
|
|||||||
@ -717,107 +717,112 @@ void UEBBarrel::PredictAdaptiveExtrapolation(double CurrentTime, FVector& OutLoc
|
|||||||
// Compute weighted velocities from consecutive safe sample pairs
|
// Compute weighted velocities from consecutive safe sample pairs
|
||||||
// Weight recent pairs more heavily
|
// Weight recent pairs more heavily
|
||||||
TArray<FVector> PosVelocities;
|
TArray<FVector> PosVelocities;
|
||||||
TArray<FVector> AimVelocities;
|
// Compute per-pair velocities
|
||||||
TArray<double> Weights;
|
TArray<FVector> PosVels;
|
||||||
PosVelocities.Reserve(SafeN - 1);
|
TArray<FVector> AimVels;
|
||||||
AimVelocities.Reserve(SafeN - 1);
|
PosVels.Reserve(SafeN - 1);
|
||||||
Weights.Reserve(SafeN - 1);
|
AimVels.Reserve(SafeN - 1);
|
||||||
|
|
||||||
for (int32 i = 1; i < SafeN; i++)
|
for (int32 i = 1; i < SafeN; i++)
|
||||||
{
|
{
|
||||||
double dt = TransformHistory[i].Timestamp - TransformHistory[i - 1].Timestamp;
|
double dt = TransformHistory[i].Timestamp - TransformHistory[i - 1].Timestamp;
|
||||||
if (dt > SMALL_NUMBER)
|
if (dt > SMALL_NUMBER)
|
||||||
{
|
{
|
||||||
FVector PosVel = (TransformHistory[i].Location - TransformHistory[i - 1].Location) / dt;
|
PosVels.Add((TransformHistory[i].Location - TransformHistory[i - 1].Location) / dt);
|
||||||
FVector AimVel = (TransformHistory[i].Aim - TransformHistory[i - 1].Aim) / dt;
|
AimVels.Add((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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PosVelocities.Num() == 0)
|
if (PosVels.Num() == 0)
|
||||||
{
|
{
|
||||||
OutLocation = TransformHistory[SafeN - 1].Location;
|
OutLocation = TransformHistory[SafeN - 1].Location;
|
||||||
OutAim = TransformHistory[SafeN - 1].Aim;
|
OutAim = TransformHistory[SafeN - 1].Aim;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute weighted average velocity
|
// Compute overall weighted average velocity (recent samples weighted more)
|
||||||
double TotalWeight = 0.0;
|
double TotalWeight = 0.0;
|
||||||
FVector AvgPosVel = FVector::ZeroVector;
|
FVector AvgPosVel = FVector::ZeroVector;
|
||||||
FVector AvgAimVel = 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];
|
double w = FMath::Pow((double)(i + 1), 2.0);
|
||||||
AvgAimVel += AimVelocities[i] * Weights[i];
|
AvgPosVel += PosVels[i] * w;
|
||||||
TotalWeight += Weights[i];
|
AvgAimVel += AimVels[i] * w;
|
||||||
|
TotalWeight += w;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvgPosVel /= TotalWeight;
|
AvgPosVel /= TotalWeight;
|
||||||
AvgAimVel /= TotalWeight;
|
AvgAimVel /= TotalWeight;
|
||||||
|
|
||||||
// Compute weighted variance of velocity (how consistent is the velocity?)
|
// Deceleration detection: compare recent speed (last 25% of pairs) vs overall speed.
|
||||||
FVector PosVelVariance = FVector::ZeroVector;
|
// If the user is stopping, recent speed will drop toward 0 while avg is still high.
|
||||||
FVector AimVelVariance = FVector::ZeroVector;
|
// 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;
|
RecentPosVel += PosVels[i];
|
||||||
FVector AimDiff = AimVelocities[i] - AvgAimVel;
|
RecentAimVel += AimVels[i];
|
||||||
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 /= (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;
|
float AimRatio = 1.0f;
|
||||||
AimVelVariance /= TotalWeight;
|
float AimConfidence = 1.0f;
|
||||||
|
if (AvgAimSpeed > SMALL_NUMBER)
|
||||||
// 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).
|
AimRatio = FMath::Clamp(RecentAimSpeed / AvgAimSpeed, 0.0f, 1.0f);
|
||||||
// Using relative variance (normalized by speed^2) so both are scale-independent.
|
float AimRemapped = (AdaptiveDeadZone < 1.0f)
|
||||||
double PosSpeed2 = AvgPosVel.SizeSquared();
|
? FMath::Clamp((AimRatio - AdaptiveDeadZone) / (1.0f - AdaptiveDeadZone), 0.0f, 1.0f)
|
||||||
double AimSpeed2 = AvgAimVel.SizeSquared();
|
: (AimRatio >= 1.0f ? 1.0f : 0.0f);
|
||||||
|
AimConfidence = FMath::Pow(AimRemapped, AdaptiveSensitivity);
|
||||||
double PosAbsVariance = PosVelVariance.X + PosVelVariance.Y + PosVelVariance.Z;
|
}
|
||||||
double AimAbsVariance = AimVelVariance.X + AimVelVariance.Y + AimVelVariance.Z;
|
|
||||||
|
|
||||||
// Extrapolate from last safe sample
|
// Extrapolate from last safe sample
|
||||||
const FTimestampedTransform& LastSafe = TransformHistory[SafeN - 1];
|
const FTimestampedTransform& LastSafe = TransformHistory[SafeN - 1];
|
||||||
double ExtrapolationTime = CurrentTime - LastSafe.Timestamp;
|
double ExtrapolationTime = CurrentTime - LastSafe.Timestamp;
|
||||||
|
|
||||||
// Relative variance = variance / speed^2. When speed is ~0, extrapolation offset
|
// Write debug values for HUD display
|
||||||
// is negligible anyway so confidence doesn't matter — set to 1.
|
DbgPosRatio = PosRatio;
|
||||||
// Low-speed protection: at low speed the extrapolation offset (speed * dt) is tiny,
|
DbgAimRatio = AimRatio;
|
||||||
// so there's no overshoot to prevent. We blend confidence back toward 1.0 when the
|
DbgPosConfidence = PosConfidence;
|
||||||
// potential extrapolation offset is small (< 1cm for position, < 0.01 for aim direction).
|
DbgAimConfidence = AimConfidence;
|
||||||
float PosConfidence = 1.0f;
|
DbgAvgPosSpeed = AvgPosSpeed;
|
||||||
if (PosSpeed2 > SMALL_NUMBER)
|
DbgAvgAimSpeed = AvgAimSpeed;
|
||||||
{
|
DbgRecentPosSpeed = RecentPosSpeed;
|
||||||
double PosRelVar = PosAbsVariance / PosSpeed2;
|
DbgRecentAimSpeed = RecentAimSpeed;
|
||||||
PosConfidence = 1.0f / (1.0f + (float)PosRelVar * AdaptiveSensitivity);
|
DbgExtrapolationTime = (float)ExtrapolationTime;
|
||||||
|
|
||||||
// Low-speed protection: if extrapolation offset < 1cm, blend confidence toward 1
|
// Apply optional damping
|
||||||
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
|
|
||||||
float DampingScale = 1.0f;
|
float DampingScale = 1.0f;
|
||||||
if (ExtrapolationDamping > 0.0f)
|
if (ExtrapolationDamping > 0.0f)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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)
|
void UEBBarrel::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
{
|
{
|
||||||
Super::TickComponent(DeltaTime, TickType, 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
|
// Calibration HUD
|
||||||
if (CalibrateAntiRecoil && GEngine)
|
if (CalibrateAntiRecoil && GEngine)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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_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_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_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()
|
USTRUCT()
|
||||||
@ -82,6 +82,7 @@ public:
|
|||||||
UEBBarrel();
|
UEBBarrel();
|
||||||
|
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
// Called every frame
|
// Called every frame
|
||||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
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"))
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Calibration", meta = (ToolTip = "Number of shots per calibration sequence.", EditCondition = "CalibrateAntiRecoil", ClampMin = "3", ClampMax = "50"))
|
||||||
int32 CalibrationShotCount = 10;
|
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."))
|
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;
|
bool RecordPredictionCSV = false;
|
||||||
|
|
||||||
@ -113,6 +117,19 @@ public:
|
|||||||
FString CSVFilePath;
|
FString CSVFilePath;
|
||||||
IFileHandle* CSVFileHandle = nullptr;
|
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"))
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Enable IMU shock simulation for testing anti-recoil prediction without firing"))
|
||||||
bool DebugSimulateIMUShock = false;
|
bool DebugSimulateIMUShock = false;
|
||||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|IMU Shock Simulation", meta = (ToolTip = "Angular perturbation intensity in degrees", EditCondition = "DebugSimulateIMUShock", ClampMin = "0.0"))
|
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"))
|
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;
|
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"))
|
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 = 100.0f;
|
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"))
|
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;
|
float ExtrapolationDamping = 0.0f;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user