Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .gitlab-ci.yml

This file was deleted.

15 changes: 8 additions & 7 deletions docs/architecture.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ <h3>Why these</h3>
runner-up (excellent single-binary story, gentler curve) — Rust wins on hardware
integration and race-day robustness. <em>Electron</em> 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.
</p>
</div>

Expand Down Expand Up @@ -232,7 +232,7 @@ <h2>4. Conceptual data model</h2>
<dd>A field within an event (e.g. Mini, Micro, a sim class) with its own format and
bracket — phases run per class. Classes are <strong>selected</strong> 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 <code>ClassId</code> (planned: promoted into the events crate, so
a class is identified by a <code>ClassId</code> (promoted into the events crate, so
log entries can be tagged by class).</dd>
<dt>Track</dt>
<dd>The layout flown — a physical track for IRL, or a Velocidrone spec track
Expand All @@ -248,10 +248,10 @@ <h2>4. Conceptual data model</h2>
<dd>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.
<strong>Rounds are event-level and class-tagged</strong>, identified by a <code>RoundId</code>
(planned: promoted into the events crate alongside <code>ClassId</code>). The
(promoted into the events crate alongside <code>ClassId</code>). The
<code>HeatScheduled</code> log entry that creates a heat is <strong>tagged with its class, round,
and per-pilot channel/frequency assignments</strong> (planned, v0.4 Slice&nbsp;0 — the
log-tagging slice).</dd>
and per-pilot channel/frequency assignments</strong> (built — the log-tagging slice; passes are
additionally bridge-stamped with the running heat, D20).</dd>
<dt>Timing &amp; race events <em>(the log)</em></dt>
<dd>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.</dd>
Expand Down Expand Up @@ -413,8 +413,9 @@ <h3>Why the scoping lives in storage, not the fact model</h3>
<h2>5. Components</h2>
<dl class="entities">
<dt>Timer adapter layer</dt>
<dd>One interface in front of every timing source (Velocidrone first, then
RotorHazard, LapRF, manual). Adapters are pure translators: source-specific input in,
<dd>One interface in front of every timing source (RotorHazard — the primary, via the
required GridFPV RH plugin, <a href="rotorhazard-plugin.html">D16</a> — 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
<a href="timer-adapters.html">Timer Adapters</a>.</dd>

Expand Down
20 changes: 10 additions & 10 deletions docs/clients.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,16 @@ <h3>The RD console — control-oriented, dense, loopback-trusted</h3>
</div>
<div class="callout">
<p>
<strong>Planned — the event workspace is a sequence of editable stage-pages, not a
<strong>As built — the event workspace is a sequence of editable stage-pages, not a
&ldquo;Setup&rdquo; tab.</strong> Inside an event the workspace is a left-to-right sequence of
<em>stage-pages</em> you can revisit and edit at any time:
<strong>Classes&nbsp;· Roster&nbsp;· Rounds+Heats&nbsp;· Live&nbsp;control&nbsp;·
Marshaling&nbsp;· Results</strong> (Rounds and Heats are <em>combined</em> on one page). There
<strong>Timers&nbsp;· Classes&nbsp;&amp;&nbsp;Roster&nbsp;· Rounds&nbsp;&amp;&nbsp;Heats&nbsp;·
Race&nbsp;control&nbsp;· Marshaling&nbsp;· Results&nbsp;· Audit</strong>. There
is no separate &ldquo;Setup&rdquo; phase or tab — setting an event up is just visiting its
early stage-pages. A <strong>guided setup wizard</strong> is a thin first-pass <em>over</em>
those same pages for the common path, and is built <strong>last</strong> (v0.4 Slice&nbsp;7) so
the pages exist before the wizard that walks them. One event flies <strong>one track</strong>.
those same pages, auto-opened once when an event is created (the manual relaunch button was
retired in the v1 chrome trim). <strong>Race audio</strong> — procedure tones + spoken lap
callouts — is an app-shell controller that follows the live stream on every page. One event flies <strong>one track</strong>.
(The race-running redesign; <a href="pipeline-setup.html">Setup</a>,
<a href="pipeline-mains.html">Mains</a>, <em>Roadmap</em> v0.4.)
</p>
Expand Down Expand Up @@ -206,9 +207,9 @@ <h4>The native desktop app — the release form <em>(as built; PRs #195, #204)</
Day-to-day work is unchanged: <code>cargo build -p gridfpv-app --features live</code> serves
the API + RD console at <code>:8080</code>. The desktop crate is <strong>excluded</strong>
from the root Cargo workspace, so <code>cargo xtask ci</code> 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
<code>release-builds</code> workflow builds the portables for <strong>Linux, Windows, and
macOS</strong> (all three shipped with v0.4.0-alpha.1, live RH adapter included).
</p>
</div>

Expand Down Expand Up @@ -383,8 +384,7 @@ <h2>3. Shared component library &amp; generated types</h2>
every surface shows the same data the same way unless it deliberately chooses otherwise.</p>
</div>
<p>
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).
</p>

<h2>4. PWA mechanics</h2>
Expand Down
16 changes: 13 additions & 3 deletions docs/code-conventions.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,19 @@ <h2>2. Checks — one source of truth</h2>
<p><span class="name"><code>cargo xtask</code> defines every check.</span> Local and CI
run the identical suite: <code>cargo xtask ci</code> = <code>fmt --check</code> &amp;&amp;
<code>clippy --all-targets -D warnings</code> &amp;&amp; <code>test --all</code> &amp;&amp;
the Rust→TS bindings drift-check. The GitLab pipeline is thin — it only calls
<code>cargo xtask ci</code>. Never put check logic in <code>.gitlab-ci.yml</code>; add it
to <code>xtask</code> so local and CI can't drift.</p>
the Rust→TS bindings drift-check. CI is <strong>GitHub Actions</strong>
(<code>.github/workflows/ci.yml</code>): the Rust job calls <code>cargo xtask ci</code>
verbatim, alongside a <code>cargo audit</code> job and a frontend job
(build / type-check / lint / test / Director build / <strong>contract suite</strong>);
the gated <code>release-builds.yml</code> builds the Tauri portables. Never put check
logic in the workflow files; add it to <code>xtask</code> so local and CI can't drift.</p>
<p><span class="name">One product version, bumped by <code>cargo xtask version</code>.</span>
The version lives in <code>[workspace.package].version</code>
(<code>0.x</code> milestones, <code>-alpha.N</code>/<code>-beta.N</code> pre-releases) and is
kept in lock-step with <code>src-tauri</code>, <code>tauri.conf.json</code>, and the console
<code>package.json</code> by <code>cargo xtask version &lt;x.y.z[-pre]&gt;</code>. It is
surfaced by <code>GET /about</code> and the console build-stamp;
<code>CONTRACT_VERSION</code> is the separate wire-compat integer.</p>
</div>

<h2>3. Generated wire types</h2>
Expand Down
12 changes: 9 additions & 3 deletions docs/event-pipeline.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ <h1>Event Pipeline</h1>
This doc defines the pipeline at the level of <strong>intent and
capability</strong> — 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 <em>Architecture</em>, <em>Timer Adapters</em> —
coming soon). When this doc and the vision disagree, the
to their own docs (see <a href="architecture.html">Architecture</a> and
<a href="timer-adapters.html">Timer Adapters</a>). When this doc and the vision disagree, the
<a href="vision.html">Vision</a> wins.
</p>
</div>
Expand Down Expand Up @@ -136,6 +136,12 @@ <h2>Phase 3 — Qualifying</h2>
<li><strong>Skip it</strong> — seed from registration order or a manual seed, and go straight to the mains.</li>
</ul>
<h3>Formats (when run live)</h3>
<p class="phase-meta">
<em>Intent-level list, superseded by the shipped model:</em> the as-built format set is the
three primitives (Practice / Time Trials / Head-to-Head) — see
<a href="format-model.html">Format Model</a>; ZippyQ was built then shelved (D10) and the
structure formats live on <code>tournaments-snapshot</code> for the post-v1 rebuild.
</p>
<ul>
<li><strong>Timed</strong> — best single lap, or fastest N consecutive laps (commonly 2 or 3).</li>
<li><strong>Rounds-based</strong> — a fixed number of rounds, best result kept.</li>
Expand Down Expand Up @@ -193,7 +199,7 @@ <h2>Phase 5 — Championship / Season</h2>
<p>
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 (<a href="architecture.html">Architecture</a>). The single-event pipeline above stands on its own
without it.
</p>

Expand Down
9 changes: 4 additions & 5 deletions docs/format-model.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ <h1>Format Model</h1>
This is the <strong>target model</strong> (decision <a href="decisions.html#d17">D17</a>). 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 <em>RD-facing</em> vocabulary is the three
round types + scoring + structures. It rolls out in stages (see <a href="#rollout">Rollout</a>);
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.
</p>
<p>
<strong>Release state (2026-06-30, PR #327) — this release is primitives-first.</strong> The
Expand Down Expand Up @@ -73,13 +72,13 @@ <h2>1 · Three round types</h2>
<td><strong>Practice</strong></td>
<td>Fly, no competition. Pick channels; pilots hop on and coordinate.</td>
<td>none</td>
<td>channels; optional time limit; named <em>"Open Practice"</em> or <em>"‹Class› Practice"</em> (a label only — no mechanical difference)</td>
<td>channels; optional time limit; friendly name <em>"Practice"</em> (class-free — practice rounds attach no class)</td>
</tr>
<tr>
<td><strong>Time Trial</strong></td>
<td>Qualify against the clock on fixed channels; your best result ranks you.</td>
<td>best result ranks the field</td>
<td><strong>Best of N laps</strong> (N configurable; <em>N = 1 is "best lap"</em>) + a race time</td>
<td><strong>Best of N laps</strong> (N configurable; <em>N = 1 is "best lap"</em>) or <strong>Timed — most laps</strong>, + a race time; <em>"Heats per pilot"</em> (<code>rounds</code>, default 3; <strong>0 = open-ended</strong> — generate the next rotation on demand)</td>
</tr>
<tr>
<td><strong>Head-to-Head</strong></td>
Expand Down Expand Up @@ -164,7 +163,7 @@ <h2>5 · Engine refactor (structures compose the building block)</h2>

<h2 id="rollout">6 · Rollout</h2>
<ol>
<li><strong>Bracket win-condition picker</strong> — the "Advance to bracket" modal picks one win condition for all the bracket's heats (default First-to-N; option Most-laps). <em>Shipped.</em></li>
<li><strong>Bracket win-condition picker</strong> — the "Advance to bracket" modal picks one win condition for all the bracket's heats (default First-to-N; option Most-laps). <em>Shipped, then carved to <code>tournaments-snapshot</code> with the bracket machinery (PR #327) — not in the current console.</em></li>
<li><strong>Head-to-Head building-block generator</strong> — 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.</li>
<li><strong>Recompose Round Robin</strong> over Head-to-Head (all-play-all + Points), retiring the bespoke <code>round_robin</code> generator. <em>Deferred — carved to <code>tournaments-snapshot</code> (PR #327); rebuilt post-v1.</em></li>
<li><strong>Recompose the Bracket</strong> over Head-to-Head (Placement + seeding + the existing FromHeatWinners chain), folding away <code>single_elim</code>. <em>Deferred — carved to <code>tournaments-snapshot</code> (PR #327); rebuilt post-v1.</em></li>
Expand Down
16 changes: 9 additions & 7 deletions docs/heat-lifecycle.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ <h2>2. Commands &amp; the legality table</h2>
<tbody>
<tr><td><code>Stage</code></td><td>Scheduled</td><td><code>Staged</code></td><td>Staged</td></tr>
<tr><td><code>Start</code></td><td>Staged</td><td><code>Armed</code></td><td>Armed</td></tr>
<tr><td><code>SkipCountdown</code> <em>(override)</em></td><td>Armed</td><td><code>Running</code></td><td>Running</td></tr>
<tr><td><code>ForceEnd</code> <em>(override)</em></td><td>Running</td><td><code>Finished</code></td><td>Unofficial</td></tr>
<tr><td><code>SkipCountdown</code> <em>(wire-only — retired from the console)</em></td><td>Armed</td><td><code>Running</code></td><td>Running</td></tr>
<tr><td><code>ForceEnd</code> <em>(the console's <strong>Stop</strong> button)</em></td><td>Running</td><td><code>Finished</code></td><td>Unofficial</td></tr>
<tr><td><code>Finalize</code></td><td>Unofficial</td><td><code>Finalized</code></td><td>Final</td></tr>
<tr><td><code>Advance</code></td><td>Final</td><td><code>Advanced</code></td><td>Final <em>(terminal)</em></td></tr>
<tr><td><code>Revert</code></td><td>Final</td><td><code>Reverted</code></td><td>Unofficial</td></tr>
<tr><td><code>Abort</code></td><td>Staged / Armed / Running</td><td><code>Aborted</code></td><td>Scheduled</td></tr>
<tr><td><code>Restart</code></td><td>Armed / Running / Unofficial</td><td><code>Restarted</code></td><td>Scheduled</td></tr>
<tr><td><code>Discard</code></td><td>Final</td><td><code>Discarded</code></td><td>Scheduled</td></tr>
<tr><td><code>Discard</code></td><td>Unofficial, Final</td><td><code>Discarded</code></td><td>Scheduled</td></tr>
</tbody>
</table>
<p>
Expand All @@ -118,9 +118,11 @@ <h2>2. Commands &amp; the legality table</h2>
backend and UI (release hardening, #330) — so a result cannot be locked with an unresolved
protest. The two <strong>middle forward steps</strong> —
<code>Armed → Running</code> and <code>Running → Unofficial</code> — 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
<code>SkipCountdown</code> and <code>ForceEnd</code> 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: <code>ForceEnd</code>
surfaces as the plain <strong>Stop</strong> button; <code>SkipCountdown</code> is
wire-only — the console retired it, and the &ldquo;override&rdquo; styling concept with it.)
</p>

<h3>Abort and Restart both reset to Scheduled</h3>
Expand Down Expand Up @@ -194,7 +196,7 @@ <h3>4.1 Start procedure — <code>Armed → Running</code></h3>
</div>
<p>
If the clock can't be trusted — a stuck countdown, or a race that must go now — the RD's
<code>SkipCountdown</code> override forces <code>Armed → Running</code> immediately,
<code>SkipCountdown</code> command (wire-only) forces <code>Armed → Running</code> immediately,
recording the same <code>Running</code> transition the driver would.
</p>

Expand All @@ -221,7 +223,7 @@ <h3>4.2 Completion — <code>Running → Unofficial</code></h3>
<p><span class="name">Win condition, then the grace window, then auto-Unofficial.</span>
Once the race-end criterion is met the driver holds the heat in <code>Running</code> for
the round's <strong>grace window</strong> — so a trailing pilot's final lap still counts —
and only then appends <code>Running → Unofficial</code>. The <code>ForceEnd</code> override
and only then appends <code>Running → Unofficial</code>. The <code>ForceEnd</code> command (the console's Stop)
forces the same step now when the completion clock must be bypassed.</p>
</div>

Expand Down
Loading