Starfield Backdrop Only

 We train chest-compression cadence with moving “beat balls” that pass through a fixed hit window. The player taps on the beat (like a rhythm game). A Generator blueprint spawns beats at the chosen BPM and manages UI hints; each Sphere blueprint travels along a spline, detects whether it is in the hit window, and resolves a hit or miss with feedback (VFX + camera shake).
Actors
BP_BeatVisGenerator
binds player input, paces spawns, enlarges the hit window on press, and cleans up all active spheres.
BP_Sphere
one beat instance that moves at constant speed along a spline, marks when it enters/leaves the target area, and handles hit logic.

Data Flow
  1. Setup & Input Binding
          Cache player on BeginPlay, bind OnPlayerPressed to a window-enlarge timeline and OnPlayerEndInteraction to cleanup.    
  2. Beat Pacing & Spawning
    SetTimerByEvent loops at BeatInterval. On tick, spawn BP_Sphere with the lane spline and TravelTime = BeatInterval.    
  3. Spline Motion at Constant Velocity
          Each sphere computes MovementSpeed = SplineLength / TravelTime, advances CurrentDistance, and updates world position; an optional material scalar shows progress.    
  4. Window Overlap Gates
          Overlap begin/end with the target window flip IsSphereInTargetArea, forming the timing gate for judgement.    
  5. Press-Time Check & Feedback
          On press, if the sphere is in the window: play VFX, short camera shake, run a pop timeline, then destroy after a brief delay.    
  6. Cleanup & Session Control
    OnPlayerEndInteraction calls DestroyAllSpheres to wipe leftovers and stop the round.    
  7. Tuning Knobs
    BPM/BeatInterval set cadence and travel time. Judgement leniency = window radius × enlarge scale. Readability = sphere progress + window pulse. Feedback = VFX & camera-shake curve. Cleanup = destroy delay + lane clear.    
▼ Pseudocode — Generator
// CONFIG
BPM = 110
BeatInterval = 60.0 / BPM
TargetWindow = AreaVisualizerSphere

on BeginPlay():
    player = GetPlayerCharacter()
    bind(player.OnPlayerPressed, onPlayerPressed)
    bind(player.OnPlayerEndInteraction, onInteractionEnd)
    startTimer(tickBeat, BeatInterval, loop = true)

function tickBeat():
    if SpawningEnabled:
        spawn BP_Sphere { TargetSpline, TravelTime = BeatInterval }

function onPlayerPressed():
    playTimeline(EnlargeArea)   // scales TargetWindow for feedback
    // optional: player.HitCount++ or analytics

function onInteractionEnd():
    DestroyAllSpheres()
▼ Pseudocode — Sphere
// INPUTS on spawn
TargetSpline : USplineComponent
TravelTime   : float

// STATE
CurrentDistance     = 0
IsSphereInTargetArea = false
MovementSpeed       = 0

on BeginPlay():
    L = TargetSpline.GetSplineLength()
    MovementSpeed = L / max(TravelTime, 0.001)

on Tick(dt):
    CurrentDistance += MovementSpeed * dt
    pos = TargetSpline.GetLocationAtDistanceAlongSpline(CurrentDistance, World)
    SetActorLocation(pos)
    SetMaterialScalar("Progress", CurrentDistance / L)
    if CurrentDistance >= L + KillBuffer:
        DestroyActor()

on BeginOverlap(TargetWindow):
    IsSphereInTargetArea = true

on EndOverlap(TargetWindow):
    IsSphereInTargetArea = false

function CheckTiming(PlayerIsPressing):
    if IsSphereInTargetArea and PlayerIsPressing:
        SpawnVFXAt(GetActorLocation())
        CameraShake()
        playTimeline(ShrinkPop)
        delay(1.0)
        DestroyActor()
    // else: optional miss cue
    
Outcome
 Constant-velocity spline motion makes arrivals stable across frame rates; a fixed window with moving cues makes rhythm legible and maps BPM directly to spawn cadence. Overlap-based checks are robust and cheap, and widening the window tunes accessibility without changing core input logic.