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:
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 = []