chore: initial padel scaffold + design docs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1016
docs/superpowers/plans/2026-07-02-padel-scoring.md
Normal file
1016
docs/superpowers/plans/2026-07-02-padel-scoring.md
Normal file
File diff suppressed because it is too large
Load Diff
166
docs/superpowers/specs/2026-07-02-padel-scoring-design.md
Normal file
166
docs/superpowers/specs/2026-07-02-padel-scoring-design.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 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 1–5, 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 1–5 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.)
|
||||
Reference in New Issue
Block a user