Skip to content

Play a tone on every gate crossing — including holeshot and rejected (too-short) passes #397

Description

@ryan-johnson2

What

Grid must play a tone on every gate crossing, whatever becomes of it:

  • the holeshot (first crossing, starts the lap)
  • a counted lap
  • a crossing rejected as too short (under the round's min-lap)
  • any other detected-but-not-counted crossing

Distinct enough per case that an RD can tell them apart without looking at the screen.

Why

An RD watches the track, not the laptop. The tone is how they know the timer saw the pilot — and
right now "the gate detected nothing" and "the gate detected something that didn't count" are the
same silence.

That is exactly the hole hit during the 2026-08-24 field session: pilots flying, nothing appearing,
and no way to tell from Grid whether the gate was firing at all. It sent the whole session toward
the ingest path when the timer's own detection was the variable. A tone on every crossing answers
"is it seeing me?" instantly, from across the track, with no screen.

It is also the cheapest possible version of the tuning feedback #355 exists to provide — and unlike
a live RSSI graph it needs no new telemetry stream, only the crossings the Director already knows
about.

Blocker — the information does not reach the console today

LiveRaceState (crates/server/src/live_state.rs) carries current_heat, phase,
active_pilots, progress, running_order, on_deck and the race clock. It carries nothing
about a crossing that was rejected.
A sub-min-lap pass is auto-suppressed in the projection
(crates/projection/src/lib.rs, VoidReason::UnderMinLap) and is only visible after the fact via
the marshaling lap list's removal record — not live.

So this cannot be built purely in the console. The Director has to surface, live:

  • that a crossing happened, on which seat, at what time
  • its disposition — holeshot / counted / rejected-too-short / other
  • something to make it idempotent, so a re-pushed live state does not re-fire the tone

Whether that is a small live "crossing feed" alongside progress, or rejected passes surfaced in
LiveRaceState, is the design call. Additive on the wire either way.

Build on what exists

lib/raceAudio.ts, lib/callouts.ts, lib/lapCallouts.svelte.ts, lib/endTones.svelte.ts and
lib/raceDayAudio.svelte.ts already handle the audio path, the mute/enable state, and TTS
cold-start. This should extend that, not start a second audio system. Note #193 (sounds settings
panel) is open and would be where per-tone enable/volume lives.

Order this AFTER #396

#396 must be fixed first, or this makes it audible. In open practice every stream wake-up
currently re-pushes a "changed" LiveRaceState, which is why lap callouts repeat. Hang a tone off
crossings on top of that and every crossing tone repeats too — turning a visual annoyance into a
continuous noise an RD cannot work through.

Hence the idempotency requirement above: the tone must fire on crossing identity, not on
receipt of a live-state frame.

Done when

  • Every crossing produces a tone, including ones that record no lap
  • Holeshot / counted / too-short are audibly distinguishable
  • A re-pushed live state never re-fires a tone for a crossing already announced
  • It respects the existing mute / race-day audio state
  • An RD can tell "the gate is seeing me" without looking at the screen

Requested 2026-08-24 after the first physical-timer session.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/clientsFrontends: RD console, racer/spectator PWAarea/protocolProtocol server (axum, snapshot + WS, auth)type/featureNew user-facing capability

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions