From d1ab5cb3916d83aa2c1f926c451ca7409986a88a Mon Sep 17 00:00:00 2001 From: "Ryan Johnson (ntninja)" Date: Sun, 5 Jul 2026 12:43:07 +0000 Subject: [PATCH] docs(dev): full reality-reconciliation pass at v0.4.0-alpha.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four parallel audits (narrative, timer/integration, pipeline/format, core/process) checked every dev doc against the shipped code; this applies their findings. - roadmap: GitLab planning apparatus (§4/§5 + masthead/lead) replaced with the GitHub/GitFlow reality; status callout records the RELEASED v0.4.0-alpha.1 and the between-v0.3-and-alpha work (marshaling, race audio, audit, versioning); v0.3/v0.4 bodies annotated done/cut (Slice 5 → tournaments-snapshot); a new 'in flight' block (alpha hardening, tournament rebuild, RH plugin slices); open decisions resolved (branch flow, per-format placement); diagram marks v0.1-v0.4 complete. - vision: dated status callout (shipped / deferred-by-decision / future); table stakes re-scoped — marshaling is now a shipped differentiator, incumbents lead on structures/seasons/notifications; §7 defensible-results marked shipped. - timer-adapters: the as-built staging flow (Stage-time prepare+seat, pre-stage re-prepare, 5s stage retry, per-arming pass gate, failover model, plugin handshake probe); Pass gains the D20 heat-tag field; plugin Direction callout gains an as-shipped status; signal reality (chunks/thresholds/dense history); harness = RH 4.4.0 built from source. - rotorhazard-plugin: slice statuses (S0/S1/S2 shipped, S3 partial — clean start/stop NOT landed, socket path still production; S4 delivered Grid-side); protocol marked events-not-acks with shipped/unshipped channel markers; risks 2/3/4/5 resolved/answered; install-steps reality. - protocol: Tauri packaging SHIPPED; /about, /time, /plugin/gridfpv.zip, /audit, /rounds/standings routes added; loopback source-IP middleware honestly marked not-built (#80, dev/local-trust posture today). - clients: workspace/wizard Planned→as-built (real tab set incl. Audit/Timers); Windows/macOS portables shipped; stale test counts dropped; app-wide race audio noted. - race-engine + heat-lifecycle: Discard legal from Unofficial AND Final (the one outright FSM error, both diagrams+tables); SkipCountdown wire-only / ForceEnd = the console's Stop; IMD-aware channel selection (not first-fit); RD-driven heat production (no round-robin interleaver); one StartProcedure mode. - live-state-clock: the real current_heat rule (HeatStateChanged / CurrentHeatSelected win; fill-no-steal) — manual selection is built. - pipeline docs: wizard step order; event-level format-config superseded by rounds; track = decided-not-built; 'Practice' rename; qualifying gains 0=open-ended, D24 freeze, D26 min-lap, #326 cite, multi-source FromRanking; mains as-built callout re-labeled pre-carve and the carry corrected to FromHeatWinners; format-model rollout + knobs current. - testing-strategy: plugin-era harness inventory (race-day autopilot, RH 4.4.0 image); LapRF/manual goldens marked aspirational; ZippyQ note; :8080. - code-conventions: GitHub Actions CI reality + the one-version scheme; the orphaned .gitlab-ci.yml deleted. - marshaling: §3.1/§3.2/§5 brought to the as-built vocabulary (lap-list editor, Throw out vs Remove, D26 floor instead of smart-minimum, Tune Detection SHIPPED); marshaling-plan gets a SUPERSEDED banner (kept as the build record) and is labeled so on the index. - index: release-state = the tagged alpha; user docs live at docs.gridfpv.com; streaming scene map re-keyed to the real lifecycle names; integrations tense; 'coming soon' boilerplate cleared everywhere. Co-Authored-By: Claude Fable 5 --- .gitlab-ci.yml | 27 ------ docs/architecture.html | 15 +-- docs/clients.html | 20 ++-- docs/code-conventions.html | 16 ++- docs/event-pipeline.html | 12 ++- docs/format-model.html | 9 +- docs/heat-lifecycle.html | 16 +-- docs/index.html | 19 ++-- docs/integrations.html | 2 +- docs/live-state-clock.html | 9 +- docs/marshaling-plan.html | 13 +++ docs/marshaling.html | 32 +++--- docs/mock-race-day.md | 6 +- docs/pipeline-mains.html | 10 +- docs/pipeline-practice.html | 8 +- docs/pipeline-qualifying.html | 15 +-- docs/pipeline-season.html | 2 +- docs/pipeline-setup.html | 12 +-- docs/protocol.html | 18 +++- docs/race-engine.html | 20 ++-- docs/roadmap.html | 177 +++++++++++++++++----------------- docs/rotorhazard-plugin.html | 114 +++++++++++++--------- docs/streaming.html | 17 ++-- docs/testing-strategy.html | 21 +++- docs/timer-adapters.html | 50 +++++++--- docs/vision.html | 35 +++++-- 26 files changed, 402 insertions(+), 293 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index c439c85..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,27 +0,0 @@ -# GridFPV CI — runs the SAME check suite as local dev: `cargo xtask ci`. -# Keep this file thin. All check logic lives in xtask/ (issue #18) so the -# pipeline and a developer's machine can never run different checks. -# This shared pipeline runs only the core, deterministic check suite. The -# container-dependent live integration tests (the `live` feature, dockerized -# RotorHazard) are deliberately NOT here — they're flaky and resource-heavy on -# shared runners. They run as a separate local class: -# -# cargo xtask live # brings up dockerized RH, runs the live test, tears down -# -# (Requires Docker. Add a Docker-capable self-hosted runner later if we want it in -# CI.) See docker/rotorhazard/README.md and crates/adapters/tests/rh_live.rs. -stages: - - check - -ci: - stage: check - image: rust:latest - before_script: - - rustup component add rustfmt clippy - script: - - cargo xtask ci - cache: - key: "$CI_COMMIT_REF_SLUG" - paths: - - .cargo/registry/ - - target/ diff --git a/docs/architecture.html b/docs/architecture.html index 3e80b66..3845027 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -145,7 +145,7 @@

Why these

runner-up (excellent single-binary story, gentler curve) — Rust wins on hardware integration and race-day robustness. Electron is easiest for a JS-only team but loses the single-binary/no-deps goal. The frontend framework - (React vs Svelte) is intentionally still open (see §11). + is Svelte (resolved — §11); the shell is Tauri, shipped as the three-OS portables.

@@ -232,7 +232,7 @@

4. Conceptual data model

A field within an event (e.g. Mini, Micro, a sim class) with its own format and bracket — phases run per class. Classes are selected from an app-level class registry (nine locked built-ins with fixed ids for stable cross-event identity, plus Custom); - a class is identified by a ClassId (planned: promoted into the events crate, so + a class is identified by a ClassId (promoted into the events crate, so log entries can be tagged by class).
Track
The layout flown — a physical track for IRL, or a Velocidrone spec track @@ -248,10 +248,10 @@

4. Conceptual data model

Scheduling structures the race engine produces — who flies together, when, in which round. Generated by the format (or filled manually) and adjustable before they fly. Rounds are event-level and class-tagged, identified by a RoundId - (planned: promoted into the events crate alongside ClassId). The + (promoted into the events crate alongside ClassId). The HeatScheduled log entry that creates a heat is tagged with its class, round, - and per-pilot channel/frequency assignments (planned, v0.4 Slice 0 — the - log-tagging slice).
+ and per-pilot channel/frequency assignments (built — the log-tagging slice; passes are + additionally bridge-stamped with the running heat, D20).
Timing & race events (the log)
The append-only entries: raw observations from adapters, race-state events from the engine, and adjudications from marshaling (see §3). The only source of truth.
@@ -413,8 +413,9 @@

Why the scoping lives in storage, not the fact model

5. Components

Timer adapter layer
-
One interface in front of every timing source (Velocidrone first, then - RotorHazard, LapRF, manual). Adapters are pure translators: source-specific input in, +
One interface in front of every timing source (RotorHazard — the primary, via the + required GridFPV RH plugin, D16 — and Velocidrone; + LapRF/manual are future adapters). Adapters are pure translators: source-specific input in, canonical timing events out. The interface internals are specified in Timer Adapters.
diff --git a/docs/clients.html b/docs/clients.html index 24b25bb..f60d856 100644 --- a/docs/clients.html +++ b/docs/clients.html @@ -115,15 +115,16 @@

The RD console — control-oriented, dense, loopback-trusted

- Planned — the event workspace is a sequence of editable stage-pages, not a + As built — the event workspace is a sequence of editable stage-pages, not a “Setup” tab. Inside an event the workspace is a left-to-right sequence of stage-pages you can revisit and edit at any time: - Classes · Roster · Rounds+Heats · Live control · - Marshaling · Results (Rounds and Heats are combined on one page). There + Timers · Classes & Roster · Rounds & Heats · + Race control · Marshaling · Results · Audit. There is no separate “Setup” phase or tab — setting an event up is just visiting its early stage-pages. A guided setup wizard is a thin first-pass over - those same pages for the common path, and is built last (v0.4 Slice 7) so - the pages exist before the wizard that walks them. One event flies one track. + those same pages, auto-opened once when an event is created (the manual relaunch button was + retired in the v1 chrome trim). Race audio — procedure tones + spoken lap + callouts — is an app-shell controller that follows the live stream on every page. One event flies one track. (The race-running redesign; Setup, Mains, Roadmap v0.4.)

@@ -206,9 +207,9 @@

The native desktop app — the release form (as built; PRs #195, #204)cargo build -p gridfpv-app --features live serves the API + RD console at :8080. The desktop crate is excluded from the root Cargo workspace, so cargo xtask ci never compiles it (CI runners - have no webkit2gtk/GTK); the Tauri build is a separate, deliberate command. Windows packaging - is deferred — the crate is Windows-ready in principle, but no Windows artifact is built and no - cross-compile is attempted. + have no webkit2gtk/GTK); the Tauri build is a separate, deliberate command. The gated + release-builds workflow builds the portables for Linux, Windows, and + macOS (all three shipped with v0.4.0-alpha.1, live RH adapter included).

@@ -383,8 +384,7 @@

3. Shared component library & generated types

every surface shows the same data the same way unless it deliberately chooses otherwise.

- As built (v0.4): the frontend uses vitest; the suite is green (78 tests — 20 component, - 7 protocol-client, 51 RD console). + As built (v0.4): the frontend uses vitest; the suite is green (the counts grow with every slice — see CI).

4. PWA mechanics

diff --git a/docs/code-conventions.html b/docs/code-conventions.html index 6dd544d..31d8771 100644 --- a/docs/code-conventions.html +++ b/docs/code-conventions.html @@ -46,9 +46,19 @@

2. Checks — one source of truth

cargo xtask defines every check. Local and CI run the identical suite: cargo xtask ci = fmt --check && clippy --all-targets -D warnings && test --all && - the Rust→TS bindings drift-check. The GitLab pipeline is thin — it only calls - cargo xtask ci. Never put check logic in .gitlab-ci.yml; add it - to xtask so local and CI can't drift.

+ the Rust→TS bindings drift-check. CI is GitHub Actions + (.github/workflows/ci.yml): the Rust job calls cargo xtask ci + verbatim, alongside a cargo audit job and a frontend job + (build / type-check / lint / test / Director build / contract suite); + the gated release-builds.yml builds the Tauri portables. Never put check + logic in the workflow files; add it to xtask so local and CI can't drift.

+

One product version, bumped by cargo xtask version. + The version lives in [workspace.package].version + (0.x milestones, -alpha.N/-beta.N pre-releases) and is + kept in lock-step with src-tauri, tauri.conf.json, and the console + package.json by cargo xtask version <x.y.z[-pre]>. It is + surfaced by GET /about and the console build-stamp; + CONTRACT_VERSION is the separate wire-compat integer.

3. Generated wire types

diff --git a/docs/event-pipeline.html b/docs/event-pipeline.html index a6d6558..351d0bf 100644 --- a/docs/event-pipeline.html +++ b/docs/event-pipeline.html @@ -25,8 +25,8 @@

Event Pipeline

This doc defines the pipeline at the level of intent and capability — the ideas we must hit. It deliberately leaves the data model, the timer-adapter spec, protocol message shapes, and the Cloud warehouse - to their own docs (see Architecture, Timer Adapters — - coming soon). When this doc and the vision disagree, the + to their own docs (see Architecture and + Timer Adapters). When this doc and the vision disagree, the Vision wins.

@@ -136,6 +136,12 @@

Phase 3 — Qualifying

  • Skip it — seed from registration order or a manual seed, and go straight to the mains.
  • Formats (when run live)

    +

    + Intent-level list, superseded by the shipped model: the as-built format set is the + three primitives (Practice / Time Trials / Head-to-Head) — see + Format Model; ZippyQ was built then shelved (D10) and the + structure formats live on tournaments-snapshot for the post-v1 rebuild. +

    • Timed — best single lap, or fastest N consecutive laps (commonly 2 or 3).
    • Rounds-based — a fixed number of rounds, best result kept.
    • @@ -193,7 +199,7 @@

      Phase 5 — Championship / Season

      This phase leans on the optional Cloud — the analytics warehouse and season linking — so it is intentionally light here and will get its own treatment alongside the Cloud - design (Architecture, coming soon). The single-event pipeline above stands on its own + design (Architecture). The single-event pipeline above stands on its own without it.

      diff --git a/docs/format-model.html b/docs/format-model.html index da57e0c..15af7d2 100644 --- a/docs/format-model.html +++ b/docs/format-model.html @@ -28,8 +28,7 @@

      Format Model

      This is the target model (decision D17). It supersedes the per-format framing in the earlier heat-gen/formats decisions (D10/D11/D14): the engine still has generators per mechanic, but the RD-facing vocabulary is the three - round types + scoring + structures. It rolls out in stages (see Rollout); - until a stage ships, the console still exposes the legacy format names. + round types + scoring + structures. Every planned stage has shipped; the console exposes exactly the three round types.

      Release state (2026-06-30, PR #327) — this release is primitives-first. The @@ -73,13 +72,13 @@

      1 · Three round types

      Practice Fly, no competition. Pick channels; pilots hop on and coordinate. none - channels; optional time limit; named "Open Practice" or "‹Class› Practice" (a label only — no mechanical difference) + channels; optional time limit; friendly name "Practice" (class-free — practice rounds attach no class) Time Trial Qualify against the clock on fixed channels; your best result ranks you. best result ranks the field - Best of N laps (N configurable; N = 1 is "best lap") + a race time + Best of N laps (N configurable; N = 1 is "best lap") or Timed — most laps, + a race time; "Heats per pilot" (rounds, default 3; 0 = open-ended — generate the next rotation on demand) Head-to-Head @@ -164,7 +163,7 @@

      5 · Engine refactor (structures compose the building block)

      6 · Rollout

        -
      1. Bracket win-condition picker — the "Advance to bracket" modal picks one win condition for all the bracket's heats (default First-to-N; option Most-laps). Shipped.
      2. +
      3. Bracket win-condition picker — the "Advance to bracket" modal picks one win condition for all the bracket's heats (default First-to-N; option Most-laps). Shipped, then carved to tournaments-snapshot with the bracket machinery (PR #327) — not in the current console.
      4. Head-to-Head building-block generator — the atomic racing format: group size, per-heat win condition, scoring (Points with a custom table, or Placement). Built first, because the structures compose it.
      5. Recompose Round Robin over Head-to-Head (all-play-all + Points), retiring the bespoke round_robin generator. Deferred — carved to tournaments-snapshot (PR #327); rebuilt post-v1.
      6. Recompose the Bracket over Head-to-Head (Placement + seeding + the existing FromHeatWinners chain), folding away single_elim. Deferred — carved to tournaments-snapshot (PR #327); rebuilt post-v1.
      7. diff --git a/docs/heat-lifecycle.html b/docs/heat-lifecycle.html index 939d967..64b5972 100644 --- a/docs/heat-lifecycle.html +++ b/docs/heat-lifecycle.html @@ -101,14 +101,14 @@

        2. Commands & the legality table

        StageScheduledStagedStaged StartStagedArmedArmed - SkipCountdown (override)ArmedRunningRunning - ForceEnd (override)RunningFinishedUnofficial + SkipCountdown (wire-only — retired from the console)ArmedRunningRunning + ForceEnd (the console's Stop button)RunningFinishedUnofficial FinalizeUnofficialFinalizedFinal AdvanceFinalAdvancedFinal (terminal) RevertFinalRevertedUnofficial AbortStaged / Armed / RunningAbortedScheduled RestartArmed / Running / UnofficialRestartedScheduled - DiscardFinalDiscardedScheduled + DiscardUnofficial, FinalDiscardedScheduled

        @@ -118,9 +118,11 @@

        2. Commands & the legality table

        backend and UI (release hardening, #330) — so a result cannot be locked with an unresolved protest. The two middle forward stepsArmed → Running and Running → Unofficial — have no everyday - command: they are appended by the runtime clock (§4). The override commands + command: they are appended by the runtime clock (§4). The manual commands SkipCountdown and ForceEnd record exactly the same transitions - the auto-path would, as race-day escape hatches. + the auto-path would, as race-day escape hatches. (Console reality: ForceEnd + surfaces as the plain Stop button; SkipCountdown is + wire-only — the console retired it, and the “override” styling concept with it.)

        Abort and Restart both reset to Scheduled

        @@ -194,7 +196,7 @@

        4.1 Start procedure — Armed → Running

        If the clock can't be trusted — a stuck countdown, or a race that must go now — the RD's - SkipCountdown override forces Armed → Running immediately, + SkipCountdown command (wire-only) forces Armed → Running immediately, recording the same Running transition the driver would.

        @@ -221,7 +223,7 @@

        4.2 Completion — Running → Unofficial

        Win condition, then the grace window, then auto-Unofficial. Once the race-end criterion is met the driver holds the heat in Running for the round's grace window — so a trailing pilot's final lap still counts — - and only then appends Running → Unofficial. The ForceEnd override + and only then appends Running → Unofficial. The ForceEnd command (the console's Stop) forces the same step now when the completion clock must be bypassed.

        diff --git a/docs/index.html b/docs/index.html index 53af3af..509c82b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -27,11 +27,14 @@

        Internal Design & Developer Docs

        User-facing documentation (guides for race directors, pilots, and - self-hosters) is a separate concern and will live in its own repository - later. Keep that material out of this repo. + self-hosters) is a separate concern and lives in its own repository — + published at docs.gridfpv.com. Keep that + material out of this repo.

        - Release state (2026-06-30): this is a primitives-first release — + Release state: v0.4.0-alpha.1 released 2026-07-05 + (tagged on main; Linux/Windows/macOS desktop portables with the live RH + adapter). It is a primitives-first release — three round types (Practice / Time Trials / Head-to-Head) ship; the tournament structures are carved to the tournaments-snapshot branch (PR #327) for a post-v1 rebuild.

        @@ -187,11 +190,11 @@

        Reference

      8. - +
        Marshaling Build Plan (superseded — fully shipped)
        - The sliced implementation plan for marshaling — the headline finding (the correction model - and deterministic fold already exist), the refinements that shrink the design, the six - slices in recommended order, and the load-bearing risks (RSSI fidelity, throw-out + The historical sliced build plan for marshaling — every slice shipped in + v0.4.0-alpha.1 (see Marshaling for the as-built design). + Kept as the record of the headline finding, the refinements, and the risks (RSSI fidelity, throw-out determinism, the offset convention).
      9. @@ -257,7 +260,7 @@

        Reference

        The release-versioned phase plan (v0.1 → v0.8) from foundations through the Director, - Cloud, PWA, and integrations — and how it maps onto GitLab's free-tier planning features. + Cloud, PWA, and integrations — and how it is tracked on GitHub.
    diff --git a/docs/integrations.html b/docs/integrations.html index 68e10e0..2b3cfaa 100644 --- a/docs/integrations.html +++ b/docs/integrations.html @@ -163,7 +163,7 @@

    Pushing results — during and after the event

    3. Imports are ingestion — not timer adapters

    - Two imports matter today: the MultiGP chapter roster (at setup) and + Two imports matter first (neither built yet, as of v0.4.0-alpha.1): the MultiGP chapter roster (at setup) and the Velocidrone leaderboard (for qualifying seeding). Both bring data in from outside, and both deliberately live here / with qualifying — not in the timer-adapter layer. diff --git a/docs/live-state-clock.html b/docs/live-state-clock.html index 915a03a..b00c709 100644 --- a/docs/live-state-clock.html +++ b/docs/live-state-clock.html @@ -50,12 +50,13 @@

    1. The live state is a pure fold of the log

    current_heat
    -
    The heat currently on the timer: the heat whose latest HeatScheduled / - HeatStateChanged appears last in the log. A heat that reached the +
    The heat currently on the timer: the last event among + HeatStateChanged / CurrentHeatSelected wins (a freshly-scheduled + heat must not steal focus — the fill-no-steal rule), with the first + HeatScheduled as the fallback before anything has run. A heat that reached the terminal Final phase is still reported as current until a newer heat takes the timer — mirroring what an overlay shows between heats ("last heat, now scored"). - (Planned: a manual-selection override so the RD can pin the live view to a chosen heat - regardless of last-scheduled order.)
    + (Built: the RD's manual selection appends CurrentHeatSelected, which the fold honors — pinning the live view without racing it.)
    phase
    The current heat's folded HeatPhase — the linear status Scheduled → Staged → Armed → Running → Unofficial → Final. The engine's off-ramp diff --git a/docs/marshaling-plan.html b/docs/marshaling-plan.html index be9af71..b5b60ee 100644 --- a/docs/marshaling-plan.html +++ b/docs/marshaling-plan.html @@ -12,6 +12,19 @@

    GridFPV

    Marshaling Build Plan

    Internal design doc · the sliced implementation plan for marshaling

    + +
    +

    + SUPERSEDED — fully shipped (v0.4.0-alpha.1). Every slice this plan + sequences is built: signal capture + the RSSI graph, lap-level marshaling + the audit, + the provisional → official lifecycle with the protest gate, full adjudication, and — + beyond the plan — the D25 shared removal record, the D26 min-lap floor, and Tune + Detection (the re-detect this plan deferred). The as-built design lives in + Marshaling; this file is kept as the historical build + record only. The §2 refinements (no per-person provenance; protest window optional and + OFF by default; RD-only writes) all carried through to the shipped model. +

    +

    diff --git a/docs/marshaling.html b/docs/marshaling.html index 27c35a6..9d7011b 100644 --- a/docs/marshaling.html +++ b/docs/marshaling.html @@ -96,10 +96,14 @@

    2.2 FPVTrackside — the richest lap-level model

    exit — and the marshal can:

      -
    • Add / remove a detection;
    • -
    • Split one over-long lap into two (a missed mid-lap detection);
    • -
    • Edit a lap's time, with the neighbouring lap auto-adjusting - so total race time stays consistent;
    • +
    • Add / Remove a detection (removals land on the shared + removal record with a Restore — D25);
    • +
    • Split one over-long lap into two at its midpoint (a missed mid-lap + detection), tuning either half afterwards;
    • +
    • Save an edited lap time (the editor pre-fills the current time; the + neighbouring lap auto-adjusts because the correction re-times the shared crossing);
    • +
    • Throw out a real lap from scoring (distinct from Remove — the lap stays + on the clock);
    • DQ a pilot reversibly — disqualification is a toggle, not a destructive op.

    @@ -152,7 +156,7 @@

    3.1 Foundation — timer-agnostic lap-level marshaling

    FPVTrackside-grade lap editing, but for any timer. Laps are enter/exit detections; the marshal can add, remove, split, edit-time (with the neighbour auto-adjusting), and reversibly DQ, - with the deferred smart-minimum so borderline laps aren't invalidated prematurely. This is built + with the D26 min-lap floor shipping instead of the once-planned deferred smart-minimum: an under-floor crossing is auto-removed immediately, visibly, with a marshal Restore override. This is built on the logged correction primitives that already exist in Architecture §3 — DetectionVoided, LapInserted, LapAdjusted, PenaltyApplied — extended with @@ -187,12 +191,12 @@

    3.2 Evidence — signal-as-evidence where the timer provides a trace

    Decision: v1 is signal-AS-EVIDENCE, not a re-implemented detector. - We persist and display the trace as the ground truth to review against, but we do - not re-run detection in v1. The full RotorHazard-style "Recalculate" with - draggable thresholds (re-deriving laps from the stored signal) is explicitly deferred - — it is a clean later add-on on top of the stored trace, not a v1 requirement. Building evidence - first, re-detection later, keeps the differentiating governance layer from waiting on a signal - algorithm we don't yet need.

    + We persist and display the trace as the ground truth to review against — and the + full RotorHazard-style "Recalculate" shipped as Tune Detection: + draggable enter/exit levels over the captured trace, a live preview in the lap list, committed + as canonical void/insert rulings. It shares the D25 removal record, so a crossing the marshal + removed is never re-proposed. (The original v1 plan deferred this; it landed ahead of + schedule, Grid-side.)

    IRLRotorHazard heats carry an RSSI trace; the graph is the evidence @@ -295,9 +299,9 @@

    4. Architectural fit

    5. Deferred / future seams

      -
    • Full signal re-detect. The RotorHazard-style "Recalculate" with draggable - thresholds, re-deriving laps from the stored trace. The v1 evidence layer stores the trace precisely - so this is a clean later add-on.
    • +
    • Full signal re-detect — realized (Tune Detection, §3.2). The remaining + future half is calibration write-back to the timer (gridfpv_calibrate, + plugin S4).
    • Video / DVR evidence linkage. FPVTrackside grounds calls in video; we want the same. Designed as a timestamp-flag seam now — marshaling facts carry timestamps that a future DVR layer can pivot on — without building the capture pipeline yet.
    • diff --git a/docs/mock-race-day.md b/docs/mock-race-day.md index c444dc1..d141721 100644 --- a/docs/mock-race-day.md +++ b/docs/mock-race-day.md @@ -11,15 +11,15 @@ inject emulated passes. See [`rotorhazard-plugin.html`](rotorhazard-plugin.html) ## What's running -After the overnight deploy, three things are up: +When the harness is up (bring-up commands below), three things run: | Service | Where | What | |---------|-------|------| | **Director** | http://127.0.0.1:8080 | The GridFPV app (latest `devel` build), data in `~/gridfpv-data` | | **Race RotorHazard** | http://localhost:5055 | RH v4.4.0 with the `gridfpv` + `gridfpv_mock` plugins (container `gridfpv-race-rh`) | -| (untouched) | :5099 | The maintainer's `gridfpv-demo-rh` — left alone | +(Any other RH containers on the box — demo/deploy rigs — are unrelated; leave them alone.) -The active event's timer already points at `http://localhost:5055` and reads **Connected** +Point the active event's timer at `http://localhost:5055`; it should read **Connected** with a green **plugin ✓** chip. > If the Director or race RH aren't running, restart them — see "Restarting" at the bottom. diff --git a/docs/pipeline-mains.html b/docs/pipeline-mains.html index 947fb73..81e568d 100644 --- a/docs/pipeline-mains.html +++ b/docs/pipeline-mains.html @@ -36,7 +36,7 @@

      Phase 4 — Brackets / Mains

      This is a deep dive on one phase of the event pipeline. It describes intent and capability and surfaces the decisions and data each implies. It does not define the schema or - APIs; those belong to the Architecture doc (coming soon). + APIs; those belong to the Architecture doc.

    @@ -52,9 +52,9 @@

    Capabilities

    - As built (v0.4) — the Rounds form. A bracket round is created on the + Recorded design (pre-carve v0.4 form — NOT the current console) A bracket round is created on the Rounds+Heats stage-page: a Label, a Format (friendly name — - Single/Double Elimination, Multi-Main, Round Robin), + Single/Double Elimination, Multi-Main, Round Robin — these formats now live on tournaments-snapshot; the current Add-round picker offers only the three primitives), then dynamic per-format fields — a single-select class (a round targets one class), a win condition, a seeding rule (From roster or From ranking with a source round + top-N — the cut is now labeled @@ -74,7 +74,9 @@

    Capabilities

    qualifying ranking (top-N)
    as a set of rounds — then editable by the RD (the #84 Class→Rounds carry). Decided (not yet built): a bracket generates as one round per level (Quarters/Semis/Final), seeded round-to-round via - FromRanking — single-elim is a clean winners-chain, double-elim's cross-bracket + FromHeatWinners (the advancement seeding is built server-side, + form-preserved but not form-creatable; the builders are what's carved) — single-elim + is a clean winners-chain, double-elim's cross-bracket losers-of feed is the design problem (D13, #217). Heat-filling has two v1 modes: manual and format-generated (the generators); a “Generate heats” action fills the whole deterministic round in diff --git a/docs/pipeline-practice.html b/docs/pipeline-practice.html index 0f79cdd..a6c82ae 100644 --- a/docs/pipeline-practice.html +++ b/docs/pipeline-practice.html @@ -18,7 +18,7 @@

    Phase 2 — Practice

    Flying time before anything counts. An optional, event-wide warm-up where pilots shake out gear, get comfortable on their channel, and (on a real timer) let the system calibrate to them — without a single result feeding the competition. As built, it is a single - channel-seated Open Practice round. + channel-seated Practice round.

    @@ -26,7 +26,7 @@

    Phase 2 — Practice

    This is a deep dive on one phase of the event pipeline. It describes intent and capability and surfaces the decisions and data each implies. It does not define the schema or - APIs; those belong to the Architecture doc (coming soon). + APIs; those belong to the Architecture doc.

    @@ -44,7 +44,7 @@

    Role

    As built (v0.4) — Practice is the open_practice round. In the race-running redesign there is no separate “Practice phase” with its own modes. Practice is just a round on the event whose format is - open_practice (friendly name “Open Practice”), + open_practice (friendly name “Practice”), created from the same Rounds+Heats stage-page as every other round. Selecting that format swaps the round form's class/win-condition/seeding block for an active-channels picker and an optional time-limit (minutes) field — nothing else. @@ -59,7 +59,7 @@

    Capabilities

    Organize the field — one open heat over the active channels

    - Open Practice is channel-seated, not pilot-seated. The round form's + Practice is channel-seated, not pilot-seated. The round form's active-channels picker selects which of the primary timer's node seats are live; the engine then auto-creates a single open heat whose “lineup” is those channels (competitor refs are node-0, node-1, … — the seats, diff --git a/docs/pipeline-qualifying.html b/docs/pipeline-qualifying.html index 4729f98..64065e7 100644 --- a/docs/pipeline-qualifying.html +++ b/docs/pipeline-qualifying.html @@ -24,7 +24,7 @@

    Phase 3 — Qualifying

    This is a deep dive on one phase of the event pipeline. It describes intent and capability and surfaces the decisions and data each implies. It does not define the schema or - APIs; those belong to the Architecture doc (coming soon). + APIs; those belong to the Architecture doc.

    @@ -84,9 +84,7 @@

    Win condition = qualifying metric (when run live)

    • Win condition (the round form's Win condition dropdown) — for a - qualifying format the form shows only the qualifying-applicable conditions: - Best lap, Best N consecutive, or Timed — Most Laps - (First to N laps is hidden). The usual last-lap rule applies to timed + qualifying format the form shows only the qualifying-applicable conditions — as built, two entries: Timed — most laps and Best of N laps (N=1 serialises BestLap, N>1 BestConsecutive). The usual last-lap rule applies to timed windows: a lap whose start/finish crossing came before the limit completes and counts even if it ends after the buzzer.
    • Derived ranking metric — the engine @@ -113,8 +111,13 @@

      Organizing qualifying flights — multiple heats per pilot

      rounds param (labeled “Heats per pilot”, default 3) sets how many heats each pilot flies; the engine builds a deterministic, channel-balanced plan (one pilot per distinct - channel per heat, capped by the timer's node count) across all of them.
    • -
    • Chunking large fields (#331) — when the class field exceeds + channel per heat, capped by the timer's node count) across all of them. + 0 = open-ended: the generator produces one more rotation each time the RD + asks (fill-all is rejected) — “qualify until we run out of time.” Once the round + has raced, its scoring-defining config freezes (D24 — only + rounds stays editable), and like every round it carries the D26 + min-lap floor (auto-removes double-detection echoes, marshal-restorable).
    • +
    • Chunking large fields (#326) — when the class field exceeds heat_size, timed_qual chunks each pass into multiple heats of heat_size (heat ids tq-r{n}-h{m}), so every pilot still flies the configured number of heats.
    • diff --git a/docs/pipeline-season.html b/docs/pipeline-season.html index d2f8dcc..9a4c0d3 100644 --- a/docs/pipeline-season.html +++ b/docs/pipeline-season.html @@ -25,7 +25,7 @@

      Phase 5 — Championship / Season

      event pipeline, kept lighter than the others: it is optional and cross-event, and it leans on the optional Cloud. The single-event pipeline stands on its own without any of this. Warehouse and Cloud details live in the - Architecture doc (coming soon). + Architecture doc.

      diff --git a/docs/pipeline-setup.html b/docs/pipeline-setup.html index 967910e..0c8f550 100644 --- a/docs/pipeline-setup.html +++ b/docs/pipeline-setup.html @@ -26,7 +26,7 @@

      Phase 1 — Setup & Registration

      event pipeline. It describes intent and capability — the things we want to hit — and surfaces the decisions and data each capability implies. It does not define the schema or APIs; those belong to the - Architecture doc (coming soon). + Architecture doc.

      @@ -73,12 +73,10 @@

      Define the event

      (full CRUD); built-ins are read-only. An event simply selects which classes it runs. A class is a categorization; GridFPV does not enforce equipment/hardware specs — those rules live with the league, not the app. -
    • Track selection. One track per event (resolved): +
    • Track selection. One track per event (decided, not built — no track field exists in the event model yet): a physical track layout (IRL) or a Velocidrone spec track and its track id (sim). All classes in an event share that one track.
    • -
    • Format configuration. Which phases this event runs — practice on/off, - qualifying mode (run live / import / skip), and the mains - format. The pipeline is configurable per event, not fixed.
    • +
    • Format configuration. Superseded as designed: there is no event-level format-config — an event simply accumulates rounds (each with its own format/win condition/seeding) as the day is built. See Format Model.
    • Pilot directory (app registry). As built (#74): a pilot is defined once in the app-level directory — callsign plus name, phonetic, team, color, country (ISO code), vtx_types (a multi-select of @@ -88,7 +86,7 @@

      Define the event

    • Guided setup wizard (built). Make the common path effortless (a table stake against the incumbents) while leaving every choice editable afterward. As built the wizard is a thin guided first pass over the event workspace's stage-pages — - Classes & Roster → Timer & channels → First round → Review, + Timer & channels → Classes & Roster → First round → Review, embedding the same stage components — and is Next-only (Back / Skip / Next / Finish); every selection auto-saves, so there are no Save buttons. The Review step is a live readiness check (a class selected, a pilot placed, a timer chosen, a round @@ -180,7 +178,7 @@

      Entities & data implied

      Event
      -
      name, date, one track, format-config (which phases are enabled, qualifying mode, mains +
      name, date, one track, its rounds (no event-level format-config — superseded; each round carries its format/win condition/seeding format), the selected Classes, and per-event state persisted in the event's sidecar meta table (#115).
      Class (app registry → per-event selection)
      diff --git a/docs/protocol.html b/docs/protocol.html index 64ed67b..e556a2e 100644 --- a/docs/protocol.html +++ b/docs/protocol.html @@ -105,8 +105,10 @@

      1. What the protocol serves — projections, not the log

      race_ended_at, server-time authoritative — see Live State & the Race Clock) all ship. Per-gate splits remain a later refinement (the lap-count + last-lap pair is the live core). - The Tauri shell + 3-OS single-binary packaging are still not built (#57/#58) - — the RD console runs as a web app served by the Director, not yet a native window. + The Tauri shell + 3-OS packaging SHIPPED (#57/#58) with v0.4.0-alpha.1: + self-contained portables for Linux / Windows / macOS (console embedded, live RH adapter in), + built by the gated release-builds workflow. The web-served console remains the + hosted mode — same app either way.

      @@ -161,6 +163,9 @@

      Endpoint surface (as built)

      GET /healthliveness + GET /aboutproduct identity: name, version, contract version (the console build-stamp source) + GET /timethe server clock (µs) — every server-anchored UI clock offsets against this, never Date.now() + GET /plugin/gridfpv.zipthe GridFPV RotorHazard plugin bundle (embedded at compile time — always matches the Director) GET /events · POST /events · DELETE /events/{id}list (Practice first) / RD-gated create / RD-gated permanent delete (Practice can't be deleted) GET·PUT /active-eventthe Director's active event (open read; RD-gated write) — ActiveEvent / SetActiveEventRequest GET·POST /timers · PUT·DELETE /timers/{id}app-level timer registry: Mock + RotorHazard, carrying channel_capability / node_count / available_channels (§ below). Open read; RD-gated writes; Mock undeletable @@ -178,7 +183,8 @@

      Endpoint surface (as built)

      PUT /classes · PUT /classes/{class}/membershipper-event class selection and per-class roster membership (RD-gated) POST /rounds · PUT·DELETE /rounds/{round}per-event class-tagged rounds (RD-gated) — RoundDef / NewRoundReq / UpdateRoundReq GET /heatsthe round-tagged scheduled-heats list (HeatSummary[]) the Heats UI reads. Open read - GET /rounds/{round}/ranking · GET /classes/{class}/standingsa round's per-pilot ranking and a class's aggregated standings. Open reads + GET /rounds/{round}/ranking · GET /rounds/{round}/standings · GET /classes/{class}/standingsa round's per-pilot ranking / standings and a class's aggregated standings. Open reads + GET /auditthe event-wide audit trail (every ruling, event-scoped). Open read PUT /roster · POST·DELETE /roster/{pilot}per-event roster, wholesale or single-pilot (RD-gated) PUT /timers · PUT /primary-timerper-event timer selection and the primary timer (RD-gated) GET /snapshot/event/{event}event-scope live race-state snapshot @@ -386,8 +392,10 @@

      Control — loopback is trusted, remote is opt-in

      runs on the Director's own machine (the Tauri app, or the v0.4 web app served at localhost). A request whose source address is loopback is the operator at the keyboard by definition, so it gets full control with no login, no token, - no passphrase — the RD opens the app and is in. This is enforced by source-IP - middleware (peer.ip().is_loopback()), and it is safe because the OS drops + no passphrase — the RD opens the app and is in. This is the decided design — the source-IP + middleware (peer.ip().is_loopback()) is not built yet (#80); + today control is open unless an RD token (GRIDFPV_RD_TOKEN) is configured + (the dev/local-trust posture, §9.4). It will be safe because the OS drops spoofed-loopback "martian" packets at the kernel before they ever reach the server: a remote host cannot forge a loopback source address. The practical consequence is that there is no local auth UI — no login screen in front of the event picker, diff --git a/docs/race-engine.html b/docs/race-engine.html index 5de9cbc..cf4aa5f 100644 --- a/docs/race-engine.html +++ b/docs/race-engine.html @@ -90,7 +90,7 @@

      2. The heat loop

      Staged --> Scheduled : Abort Armed --> Scheduled : Abort / Restart Running --> Scheduled : Abort / Restart - Unofficial --> Scheduled : Restart + Unofficial --> Scheduled : Restart / Discard Final --> Scheduled : Discard @@ -118,14 +118,13 @@

      2. The heat loop

      Abort, Restart, Discard, Revert, and the two race-day overrides SkipCountdown (force Armed → Running now) and ForceEnd (force - Running → Unofficial now) for when the clock can't be trusted. Notably, + Running → Unofficial now) for when the clock can't be trusted. (Console reality: ForceEnd surfaces as the plain Stop button; SkipCountdown is wire-only — the console retired it, the countdown just runs.) Notably, both Abort and Restart now reset the heat all the way to Scheduled (the RD re-Stages it): Abort is legal from Staged/Armed/Running, Restart from Armed/Running/Unofficial. Revert re-opens a finalized result (Final → Unofficial); - Discard queues a finalized heat for a clean re-run - (Final → Scheduled). + Discard throws a heat out for a clean re-run — legal from Unofficial or Final (both land back at Scheduled).

      In the log these are canonical events: a heat is created by a HeatScheduled @@ -313,11 +312,11 @@

      5. Advancement & scheduling

      Multi-class scheduling. Several classes can be live at once, each with its own format, sharing scarce resources — the timer and, for IRL, its channels. - The engine interleaves heats round-robin across classes in a fixed - rotation (so no class starves; a format that emits several heats at once has them buffered - and drained one per turn rather than monopolising the serialized timer), and - allocates each IRL heat's video channels by deterministic first-fit in seed - order — the engine's half of the split (§7.3): it decides who flies on what channel, the + Heat production is RD-driven per round (fill-round / generate-next; a + cross-class round-robin rotation scheduler remains future work), and the engine + allocates each IRL heat's video channels by IMD-aware set + selection (the cleanest third-order-intermod set, #209) then deterministic + seed-order assignment — the engine's half of the split (§7.3): it decides who flies on what channel, the adapter applies the tuning. The channel set is defined by the selected timer, not a free-floating event pool — the standard FPV catalog plus the adapter's fixed-or-flexible capability (custom raw-MHz where allowed) — and the timer's node/slot count caps the @@ -387,7 +386,8 @@

      7. Open decisions

      Heat Lifecycle; rationale in Decisions.
    • Resolved — Start / holeshot & lap model. Start - type is configurable (gate-start vs staggered); the first crossing of the start/finish + procedure has one shipped mode (RandomizedDelay — gate-start-vs-staggered + configurability never landed; add a variant if needed); the first crossing of the start/finish gate after arming opens lap 1; splits derive per the gate model in Timer Adapters.
    • Resolved — Frequency / channel allocation ownership. diff --git a/docs/roadmap.html b/docs/roadmap.html index 022198b..abb7ac1 100644 --- a/docs/roadmap.html +++ b/docs/roadmap.html @@ -11,19 +11,19 @@

      GridFPV

      Roadmap

      -

      Internal design doc · the phased build plan, and how it lives in GitLab (free tier)

      +

      Internal design doc · the phased build plan, and how it lives on GitHub

      The sequence we build in, and how that sequence is tracked. Written last, on purpose — it orders the now-settled design into release-versioned phases, each a shippable step, - and maps cleanly onto the GitLab free-tier planning features so the plan - can become real milestones and issues without paid add-ons. + tracked as GitHub issues and pull requests on + github.com/GridFPV/gridfpv.

      - This doc owns the phase plan and the GitLab planning model. + This doc owns the phase plan and how it is tracked on GitHub. It sequences the work the other docs specify — it does not redefine their designs; each phase links to the doc that owns its detail. It is a living plan (authoritative but revisable): phases and scope will shift as we @@ -80,9 +80,9 @@

      2. The phases

       flowchart LR
      -  v01["v0.1<br/>Walking skeleton"] --> v02["v0.2<br/>First adapters"]
      -  v02 --> v03["v0.3<br/>Race engine"]
      -  v03 --> v04["v0.4<br/>Direct a single race (RD console)"]
      +  v01["v0.1 ✓<br/>Walking skeleton"] --> v02["v0.2 ✓<br/>First adapters"]
      +  v02 --> v03["v0.3 ✓<br/>Race engine"]
      +  v03 --> v04["v0.4 ✓ RELEASED<br/>Direct a single race (v0.4.0-alpha.1)"]
         v04 --> v05["v0.5<br/>Racer/spectator PWA"]
         v05 --> v06["v0.6<br/>Streaming / broadcast"]
         v06 --> v07["v0.7<br/>Cloud"]
      @@ -96,28 +96,34 @@ 

      2. The phases

      classDef live fill:#eaf7e1,stroke:#43b301,stroke-width:2px; class v04 live
      -

      v0.4 (highlighted) is the first build that can run a real event end to end — and, after a hands-on UI review, the first we build as vertical feature slices rather than horizontal layers (see §1).

      +

      v0.1–v0.4 are complete; v0.4 (highlighted) shipped as the + v0.4.0-alpha.1 release — the first build that runs a real event end to end, + and the first built as vertical feature slices rather than horizontal layers (see §1).

      - Status (2026-06): v0.1 (walking skeleton), - v0.2 (first adapters + replay harness), and v0.3 - (race engine) are complete and merged — the SQLite append-only log, - projection engine, Rust→TS generation, RotorHazard + Velocidrone adapters, - recorded-session replay golden tests, live + emulated-signal dockerized-RH tests, the - heat-loop state machine, scoring/marshaling, and the format generators are all in - the tree — since the primitives-first carve-out (2026-06-30, PR #327) the registry ships - three primitive generators (Practice / Time Trials / Head-to-Head); the - tournament structures are preserved on tournaments-snapshot, rebuilt post-v1. - As designed, v0.2 already ingests a live (dockerized) source — "shadow mode." + Status (2026-07-05): v0.4 is RELEASED as + v0.4.0-alpha.1 — + tagged on main, with self-contained Tauri desktop portables for + Linux / Windows / macOS, all carrying the live RotorHazard adapter. + v0.1 (walking skeleton), v0.2 (first adapters + + replay harness, live "shadow mode"), and v0.3 (race engine) merged + before it; since the primitives-first carve-out (2026-06-30, PR #327) the registry + ships three primitive generators (Practice / Time Trials / + Head-to-Head), with the tournament structures preserved on + tournaments-snapshot for the post-v1 rebuild.

      - The v0.4 foundation is also built: the embedded axum - protocol server (snapshot + WS stream, scoping, auth), the - Svelte console shell + generated types, and the - contract-suite / observability backbone. After a hands-on UI review, - the remaining v0.4 work — actually directing a race from the console — is being - rebuilt as vertical feature slices (see §1 and the v0.4 section below). + Between v0.3 and the alpha, v0.4 grew well past "direct a single race": the six-slice + marshaling build (signal-as-evidence RSSI traces, re-detection with + tune-commit, the shared removal record D25, native + min-lap D26, penalties/protests/audit, the Final + freeze), app-wide race audio (procedure tones + spoken lap callouts), + the pre-release audit (D19–D24), + and the alpha versioning scheme (0.x milestones with + -alpha.N/-beta.N pre-releases, one workspace version via cargo xtask version, + GET /about + the console build-stamp; CONTRACT_VERSION stays + the independent wire integer).

      @@ -142,19 +148,18 @@

      v0.3 — Race engine

      Goal: turn passes + a format into a run race. (Race Engine)

      • The logged heat-loop state machine; scoring/lap-derivation + win conditions; marshaling adjudications.
      • -
      • The unified format/generator interface + first formats (Time Trials, single-elim; ZippyQ built then shelved as the dynamic-generator honesty check — D10); advancement; multi-class + engine-side frequency allocation.
      • +
      • The unified format/generator interface + first formats (Time Trials; single-elim built here, later carved to tournaments-snapshot with the rest of the structures — PR #327; ZippyQ built then shelved as the dynamic-generator honesty check — D10); advancement; multi-class + engine-side frequency allocation.
      -

      Done when: a full event (qualifying → bracket → winner) runs from a recorded source with correct results and marshaling; generators are table-tested.

      +

      Done when (met): a full event ran from a recorded source with correct results and marshaling; generators table-tested. The shipping format set was later narrowed to the three primitives (see the status note above).

      -

      v0.4 — Direct a single race (RD console) — vertical slices

      -

      Goal: a person can run a real event end to end from the RD console. (Protocol, Clients)

      +

      v0.4 — Direct a single race (RD console) — RELEASED (v0.4.0-alpha.1)

      +

      Goal (met): a person can run a real event end to end from the RD console. (Protocol, Clients)

      The horizontal foundation — embedded axum protocol server (snapshot + WS stream, scoping, bearer + LAN join-token auth), the Svelte console shell + shared component library + generated types, and the contract-suite/observability backbone — - is built. The remaining work is the director workflow itself, delivered as - vertical slices (each backend → API → UI, working before the - next): + plus the director workflow, delivered as vertical slices (each + backend → API → UI). The slice record, with outcomes:

      @@ -186,13 +191,16 @@

      v0.4 — Direct a single race (RD console) — vertical slices

      (auto-add + auto-bind name matches).
    • Slice 2 — Rounds. Event-level, class-tagged, dynamically-added rounds (practice / quali as-you-go).
    • -
    • Slice 3 — Engine + heats. Wire the v0.3 heat-loop FSM, scoring, and the six - generators per-event; heat-filling = manual + format-generated.
    • +
    • Slice 3 — Engine + heats. ✅ Wire the v0.3 heat-loop FSM, scoring, and the + generators per-event (the three shipping primitives); heat-filling = manual + format-generated.
    • Slice 4 — Channels. Timer-defined channels (standard catalog + adapter fixed/flexible capability + custom raw-MHz), node-count cap, engine first-fit + manual assign, dynamic node tuning at race time (#117, folded into timers).
    • -
    • Slice 5 — Bracket carry. Advance-to-brackets generates the full bracket from - the top-N quali ranking, editable — reusing run_event's quali→bracket carry (#84).
    • +
    • Slice 5 — Bracket carry. ✂ CUT to the post-v1 tournament rebuild: bracket + builders went to tournaments-snapshot (PR #327). What shipped instead is + hand-chained FromRanking seeding (a quali → top-N final) plus engine-level + FromHeatWinners advancement; the generate-full-bracket helper (#30) and bracket + container/viz (#31) are the rebuild's first steps, on the D13 level-per-round model.
    • Slice 6 — Per-class standings. Results and standings derived per class.
    • Slice 7 — Setup wizard. A guided first-pass over the stage-pages, built last.
    • @@ -207,14 +215,30 @@

      v0.4 — Direct a single race (RD console) — vertical slices

      IMD-aware channel assignment — scoring a heat's channels by third-order intermod gap and auto-picking the cleanest set (#209), a race-quality refinement on top of the v0.4 channel model that cuts interference-related video dropouts; seasons (#129) stay local-first for now; - multi-cloud (#132) is later. Packaging follows alongside: a Tauri shell + - single-binary build for Windows/Linux/macOS (#57/#58). + multi-cloud (#132) is later. Packaging shipped with the alpha: the + Tauri desktop portables for Windows/Linux/macOS (#57/#58), self-contained + with the live RotorHazard adapter.

      -

      Done when: a complete event — registration → heats → live races → marshaling → results, across a multi-heat format — runs live from the RD console against real/dockerized RH, and the single binary launches on all three OSes.

      +

      Done when (met, 2026-07-05): a complete event — registration → heats → live + races → marshaling → results, across a multi-heat format — runs live from the RD console against + real/dockerized RH, and the single binary launches on all three OSes. Released as + v0.4.0-alpha.1.

      + +

      In flight — between the alpha and v0.5

      +
        +
      • Alpha hardening: real-hardware field testing of the portables against a + live RotorHazard rig (the first exercise of the staging-sync + stale-replay fixes on real RF).
      • +
      • Tournament rebuild (post-v1, from tournaments-snapshot on the + D13 level-per-round model): small fixes #32/#33 → the generate-full-bracket helper #30 → the + bracket container + tree viz #31; double-elim and multi-main first-class.
      • +
      • The required RotorHazard plugin (D16, design): + slice 0 (the RH 4.4.0 mock harness) is built; next the versioned handshake, dense live RSSI, + clean start/stop ownership, and re-detection over stored traces (#3 recalc).
      • +

      - v0.5 and beyond stay as the later milestones — they begin once a single race, and a full - multi-heat event, is genuinely directable from the console (v0.4 complete). + v0.5 and beyond stay as the later milestones — the single-race and multi-heat-event bars are + met; what follows builds outward from the shipped alpha.

      v0.5 — Racer/spectator PWA

      @@ -266,8 +290,10 @@

      RH integration — a required GridFPV RotorHazard plugin

      longer a post-1.0 firmware project — it is the integration path. "Required" is a guided one-step install (Grid detects a missing plugin and offers to install it), not a hard-fail, keeping the "try it fast" feel. The plugin becomes the - primary RH integration; the full plugin design doc + sliced build plan are - forthcoming (D16). + primary RH integration; the design doc lives at + RotorHazard Plugin (floor: RHAPI 1.3 / RH 4.3.0+), + slice 0 (the RH 4.4.0 mock harness) is built, and the released alpha integrates via the + live RH adapter while the plugin path lands (D16).

      3. Cross-cutting workstreams

      @@ -282,66 +308,39 @@

      3. Cross-cutting workstreams

      Every milestone is bookended by docs: read the owning docs before breaking the milestone into issues (so the build follows the design), and run a doc-reconciliation pass after, before merging — fixing drift, resolving now-validated open decisions, and recording new conventions. Doc updates - are part of the milestone's definition-of-done and ride along in the same milestone MR.
    + are part of the milestone's definition-of-done and ride along in the same PR.
    -

    4. The GitLab planning model (free tier)

    -

    - GitLab puts Epics, the Roadmap timeline view, and Iterations behind paid tiers, so the - plan is built from the free-tier primitives and emulates the rest. We use a - single project. -

    - - - - - - - - - - - - - -
    Plan conceptFree-tier mechanism
    Phase / releaseMilestone (release-versioned, e.g. "v0.2 — First adapters") with start/due dates. The milestone list + dates is our timeline.
    Epic / featureA tracking issue whose description holds a task checklist of child issues (- [ ] #123); GitLab shows % progress.
    Work itemAn issue, assigned to its milestone + labels, listed under a tracking issue.
    Type / areaLabels by convention: type/feature, type/chore, type/bug; area/adapters, area/engine, area/protocol, area/clients, area/cloud, area/streaming, area/integrations. (Scoped-label exclusivity is paid; these are plain prefixes.)
    Workflow / statusA single project issue board with columns Open → Doing → Review → Done (board lists by status label).
    Dependencies"Relates to" links (blocking/blocked-by links are paid); express ordering in the tracking issue.
    Timeline narrativeThis doc (and optionally a Wiki page) — the Roadmap timeline view is paid.
    -
    -

    - What we deliberately don't rely on: Epics, the Roadmap view, Iterations, - scoped-label enforcement, group-level multi-boards, and blocking links — all paid. The - tracking-issue + milestone + label-convention model covers the same ground on free tier, - and this doc remains the source of truth either way. -

    -
    - -

    5. From plan to GitLab

    -

    The agreed sequence once this roadmap is finalized:

    -
      -
    1. Finalize this roadmap (review & sign-off).
    2. -
    3. Merge planningdevel (which promotes to main on releases).
    4. -
    5. Add the GitLab MCP integration.
    6. -
    7. Instantiate the plan: create the label set and the project board; create the milestones (v0.1 … v0.8) in sequence; create one tracking issue per feature and seed its child issues.
    8. -
    +

    4. Planning on GitHub

    - Seeding depth: break v0.1 down fully into issues now, create the - tracking issues for all phases as placeholders, and detail later phases just-in-time — so the - board reflects what's actionable without pretending we've planned the far future to the task level. + The plan lives on GitHub (migrated from + GitLab 2026-06-20, issue numbers preserved): issues for the work, + labels for the axes, pull requests into devel + for slices (GitFlow: devel is the working trunk, main is releases; + both protected — PR + CI required), and GitHub Actions for CI + (cargo xtask ci + the frontend job) plus the manual/main-gated + release-builds workflow for the desktop portables. Releases are + devel → main PRs, tagged (v0.4.0-alpha.1 is the first), with the + portables attached to the GitHub Release.

    -

    6. Open decisions

    +

    5. Open decisions

    1. Resolved — License: AGPL-3.0 (+ optional commercial dual-license). FOSS with network-use copyleft — anyone offering it as a hosted service must release their modifications, which protects the hosted-SaaS model while staying true FOSS; as copyright holder we can offer a commercial license later (a contributor CLA/DCO keeps that open). Applied early (at/around v0.1) as the FOSS foundation.
    2. -
    3. Resolved — Branch flow. planning merges into - devel; devel promotes to main on releases (main stays - release-stable).
    4. +
    5. Resolved — Branch flow. GitFlow on GitHub: slices land as + PRs into devel (the working trunk); devel promotes to + main on releases (main stays release-stable, tagged). The GitLab-era + planning branch is history.
    6. Resolved — Milestone dates. Sequence-only for now — order the milestones without calendar commitments; attach target dates once a real cadence emerges.
    7. -
    8. Per-format milestone placement. Which bracket/qualifying formats - land in v0.3 vs later (each per-format spec, Race Engine §7).
    9. +
    10. Resolved — Per-format milestone placement. Settled by the + primitives-first carve (PR #327, D17): the three primitives shipped in the v0.4 alpha; the + tournament structures rebuild post-v1 from tournaments-snapshot on the D13 + level-per-round model.