Skip to content

Architecture

ShotTrainer is a Cargo workspace with a Svelte and TypeScript frontend hosted by Tauri 2. Application processing and persistence run in Rust.

Component Responsibility
crates/tracking Frames, transforms, circle detection, tracking and optional OpenCV capture
crates/audio CPAL audio input and RMS shot detection
crates/core Scoring, trace buffering, recording, replay lookup, SQLite repository and session file import/export
crates/settings Preferences, JSON stores, platform paths and target-face catalogue
crates/controller Device backends, runtime coordination, sessions, previews and event production
crates/testkit Shared test fixture support
src-tauri Desktop lifecycle, permissions, updates, commands, events and frame transport
frontend Svelte views, state reducers, typed bridge and browser mock

The frontend sends typed commands through its bridge. The Tauri shell converts wire commands to controller operations and emits state events back to the frontend. Camera pixels use a separate binary frame channel. The frontend pairs frame packets with metadata for display. Views render state and dispatch commands rather than accessing devices or SQLite directly.

The controller owns device lifecycles, recording and replay. Tracking and shot events share a monotonic time base so recorded trace windows align with shots. The core crate handles scoring and storage without a UI dependency. Settings and session schemas have golden fixtures and versioned schema tests.

Without the opencv feature the desktop shell selects synthetic backends. With it, system devices are available, while --fake-devices or SHOTTRAINER_FAKE_DEVICES=1 selects synthetic mode explicitly. Synthetic data uses a separate temporary directory. The browser mock is independent of this Rust device mode and does not use the desktop database. Both modes generate automatic shots only while a session is recording, with the first shot ten seconds after starting. Camera previews and quiet audio levels continue while idle or reviewing a session. The synthetic camera stays centred outside recording. During recording, approaches enter from below the target face and follow a broad loop below the face over five and a half seconds before settling into a compact hold. Recoil starts within 25 ms and follows an irregular path that varies in direction and extent between shots, returning towards the hold area after 300 ms. The first approach begins immediately. Later approaches begin after one second of follow-through and half a second of recovery. The browser and desktop generators use the same deterministic motion, with varied shot centres and smoothly interpolated tremor. Trace rendering uses cubic curves through the samples to avoid angular joins. Both dev cameras sample at about 60 Hz. The synthetic circle moves opposite to the aim coordinates to account for the tracker’s camera-to-rifle conversion. Starting recording resets centroid smoothing so the trace begins at the first recorded aim. Camera movement and automatic triggers share elapsed recording time, so slow camera updates do not move the trigger along the approach. Live and replay traces show the configured release window in yellow and the post-shot window in red. Live samples are recoloured when the shot is detected.