feat: racing line analysis, frame trimming, persistence fix

- Add analyze_racing_line(): lap-by-lap path extraction with speed data,
  downsampled to 400 pts/lap. Spline-sorted in spline mode for clean paths.
- Add complete_lap_frames() + split_by_lap() utilities: filters to laps
  whose spline spans >= 0.9 of [0,1], discarding partial out/in laps.
- Fix complete_lap_frames logic: was checking if segment STARTS at
  spline~0, but a full lap starting mid-track (joined before start/finish)
  also qualifies. Now checks spline span coverage instead.
- WOT window trimming in analyze_shift_points(): trims to first/last
  frame with throttle >= 0.9, filters rev-limiter plateau (acc_g < 0.01).
- Fix frame persistence race condition: persist_session now writes frames
  synchronously instead of spawning a thread. Multiple concurrent calls
  could race on the same temp file, with earlier calls (empty frames)
  overwriting later calls with correct data.
- atomic_write uses UUID-based temp file names as defense-in-depth.
- PositionMode now derives Deserialize for RacingLineAnalysis.
- Empty game/track_name validation in build_track() endpoint.
- Wired RacingLine use case into analysis dispatch.
This commit is contained in:
Jan-Peter von Hunnius 2026-07-04 11:17:06 +02:00
parent d86a87ab7d
commit bcacd4b8a7
3 changed files with 315 additions and 16 deletions

View file

@ -636,6 +636,10 @@ async fn build_track(
let game = req.game.unwrap_or_else(|| manifest.game.to_track_dir_name().to_string());
let track_name = req.track_name.unwrap_or(manifest.track);
if game.is_empty() || track_name.is_empty() {
return err(StatusCode::BAD_REQUEST, "Game and track name are required. Specify them in the request or set them when starting the recording.");
}
let boundaries = {
let mgr = lock_sessions(&state);
let session = match mgr.get_session(&req.session_id) {