Files
garmin-padel/docs/superpowers/specs/2026-07-02-padel-scoring-design.md

167 lines
9.1 KiB
Markdown
Raw Permalink Normal View History

# Padel Scoring App — Design Spec
**Date:** 2026-07-02
**Target:** Garmin Connect IQ watch-app (Monkey C), product `venu445mm` (Venu 4, 45mm, round AMOLED), minApiLevel 6.0.2.
**Status:** Approved design, pre-implementation.
## 1. Purpose
A padel score tracker for the watch. It shows the live score (sets / games / points) and a
top-down court diagram whose highlighted quadrant tells you who serves the next point. Points
are recorded by swiping. No GPS, no recorded Garmin activity, no match history in v1.
## 2. Screen layout
Round screen. A top band (heart rate) and a bottom band (time) frame a central row split into
three columns: **left** = sets + games, **center** = court, **right** = current-game points.
```
♥ 142 ▮▮▮▯▯ ← heart rate + zone (top band)
S G pts
2 4 +------+------+ 15 ← your team (upper number in each column)
1 3 | | | 40 ← opponents (lower number)
+------+------+ net
| | ● ← yellow ball = next server
+------+------+
14:32 ← current time (bottom band)
```
- **Left column — sets & games.** Two mini-columns: **S** (sets) and **G** (games). A small
label (`S`, `G`) sits above each pair of numbers; under it, two stacked numbers — **your team
on top**, opponents below. Sets in **bold accent color**, games in **accent color**.
- **Right column — points.** A small `pts` label (same small font as `S`/`G`) above two stacked
numbers for the current game — your team on top, opponents below — in **white**
(`0 / 15 / 30 / 40`, and `AD` if advantage is enabled).
- **Center — court.** Simplified top-down padel court, your team the **bottom** half and
opponents the **top** half. A small **yellow ball** is drawn in the quadrant of the **next
server** (see §4); it moves as serve rotates. Nothing else is highlighted.
- **Top band — heart rate.** Current heart rate (bpm) with a **zone indicator** (the current HR
zone 15, shown as a short colored bar / zone number). Requires a heart-rate sensor reading
(see Permissions below).
- **Bottom band — time.** Current time (`HH:MM`), refreshed by a 1-second timer.
The small `S` / `G` / `pts` labels share one small font and a muted (dim grey) color so the
numbers stay dominant.
**Accent color.** A single constant (proposed: a vivid padel green/teal) used for the sets and
games numbers, so it can be changed in one place. The serve ball is **yellow** (its own constant).
**Permissions.** Reading live heart rate requires the **Sensor** permission; deriving the zone
from the user's profile requires the **UserProfile** permission. Both are added via the
`Monkey C: Edit Permissions` palette command (which regenerates `manifest.xml`).
## 3. Interaction model
**During play (gestures):**
- **Swipe up** → point to **your team**.
- **Swipe down** → point to the **opponents**.
- **Swipe left** → **undo** the last recorded point (steps back through game/set boundaries too).
**Before the first point (tap):**
- A **tap** toggles the starting server between **bottom-right** (your team serves first) and
**upper-left** (opponents serve first) — the yellow ball jumps between those two quadrants.
These are the only two options because the first point
of any game is served from the deuce (right) court, and the opponents' right-hand court maps to
the screen's upper-left. Tapping is only accepted while the match is at its very start (no points
recorded yet).
**At match end:**
- The court shows a "won by *your team* / *opponents*" state and stops accepting points; **undo
still works**. A **tap** starts a fresh match, returning to server-positioning.
## 4. Serving / court geometry
Your team = bottom half, opponents = top half. The yellow ball is drawn in the quadrant
determined by (serving team) × (serve side):
| Serving team | Serve side | Quadrant |
|--------------|------------|--------------|
| Your team | deuce (right) | bottom-right |
| Your team | ad (left) | bottom-left |
| Opponents | deuce (their right) | upper-left |
| Opponents | ad (their left) | upper-right |
**Serve side by point parity.** Even number of points played so far in the current game →
deuce (right); odd → ad (left). Game start (0-0) is always deuce.
**Serving team.**
- Normal game: one team serves the entire game; the serving team alternates after each completed game.
- The first server of the match is chosen by tap (see §3).
**Tiebreak serving** (when tiebreak is enabled and reached, see §5):
- Serve side still follows point parity: even total tiebreak points → deuce, odd → ad.
- The first server serves 1 point, then service alternates every 2 points. (Server for the point
with `P` points already played = the team given by `floor((P+1)/2) mod 2` offset from the
team due to serve at 6-6.)
## 5. Scoring rules
Rules are held in a `RulesConfig` object that the engine reads as parameters, so the future
settings screen can drive them without an engine rewrite. **v1 defaults** (all overridable later):
- **Points in a game:** `0 / 15 / 30 / 40`.
- **40-40 resolution:** **golden point** (default). Next point at 40-40 wins the game. When
`RulesConfig` is set to advantage instead, 40-40 = deuce, then advantage (`AD`), then game;
must win by two points.
- **Set:** first to **6 games, win by 2**.
- **6-6:** **7-point tiebreak** (win by 2). When tiebreak is disabled in `RulesConfig`, the set
continues until a team leads by 2 games.
- **Match:** **best of 3 sets** (first to 2 sets).
## 6. Architecture (Monkey C)
Keeps the scaffold's file-per-role split; adds two logic-only files. The scoring engine imports no
`WatchUi`/`Graphics`, so it is unit-testable in isolation.
- **`source/PadelMatch.mc`** — the scoring engine. Holds points/games/sets for both teams, the
serving team, tiebreak state, and match-over/winner. Public surface:
- `pointTo(team)` — record a point, advancing game/set/match as needed.
- `undo()` — revert the last recorded point.
- `servingQuadrant()` — returns which of the four quadrants holds the serve ball.
- `setStartingServer(team)` / toggle — only valid at match start.
- accessors for the display: points (per team, formatted), games, sets, `isMatchOver()`, `winner()`.
- **Undo strategy:** push a full state snapshot onto a stack before each `pointTo`; `undo` pops
and restores. This makes swipe-left correct across point/game/set boundaries with no special-casing.
- **`source/RulesConfig.mc`** — the rules struct (golden-point flag, sets-to-win, games-per-set,
tiebreak flag/target) plus the v1 defaults.
- **`source/CourtRenderer.mc`** — draws the top-down court and the yellow serve ball in a given
quadrant, so the view stays focused on layout. Pure drawing against a `Dc`.
- **`source/HeartRateProvider.mc`** — wraps the heart-rate sensor and zone lookup. Enables
`Sensor.SENSOR_HEARTRATE`, exposes the latest bpm, and maps it to a zone 15 using
`UserProfile.getHeartRateZones`. Returns a "no reading" state when HR is unavailable
(simulator / no strap) so the view can render a placeholder.
- **`source/garmin-padelView.mc`** — `WatchUi.View`. `onUpdate` draws the top HR band, the left
sets/games column, the center court (via `CourtRenderer`), the right points column, and the
bottom time band. A 1-second `Timer` calls `requestUpdate` to keep the clock (and HR) live.
- **`source/garmin-padelDelegate.mc`** — `BehaviorDelegate`. Maps `onSwipe` (up/down/left) and
`onTap` (pre-match server toggle; new match after match end) to the engine, then `requestUpdate`.
- **`source/garmin-padelApp.mc`** — owns the `PadelMatch`, `RulesConfig`, and `HeartRateProvider`
instances; wires the view + delegate; enables sensors on start and disables them on stop.
Resources (`resources/`): the whole screen is drawn programmatically in `onUpdate` rather than via
a static layout, since positions depend on state. `strings.xml` holds team labels and the
match-end text; the template menu can be removed or repurposed later. Sensor + UserProfile
permissions are declared in `manifest.xml` via the Edit Permissions command.
## 7. Testing
- **Engine unit tests** (`monkeyc --unit-test` with a test runner) cover `PadelMatch`:
- point → game → set → match progression (golden point and advantage modes),
- tiebreak entry at 6-6, tiebreak win-by-2,
- serving team alternation between games and serve-side parity within a game,
- tiebreak serving rotation,
- `servingQuadrant()` mapping for all four cases,
- `undo()` across point/game/set boundaries and at match start (no-op).
- Rendering (`CourtRenderer`, view, HR band) is verified in the simulator (HR via the
simulator's data-simulation, since there is no real strap).
## 8. Deferred (not in v1)
- Settings screen (drives `RulesConfig`; makes the top/bottom info slots configurable).
- Match history / saved matches.
- Resume-after-close persistence (engine state is in memory only for v1).
- Recorded Garmin activity, GPS. (Heart rate **is** in v1 as a read-only display, but the match is
still not recorded as a Garmin activity.)