Add Rippr source snapshot and full-history bundle

Two copies for two jobs. rippr-src/ is a browsable git archive export of
the tracked tree at 2f76983 - no build outputs, no local.properties, no
nested .git - which is convenient to read in gitea but carries no history
and will drift.

rippr-full-history.bundle is the real backup: all 18 commits, verified as
"records a complete history" and test-cloned before committing. This
matters because ~/dojo/rippr has no git remote and otherwise exists only
on one machine.

rippr-src/SNAPSHOT.md explains the difference and how to restore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 08:30:25 -05:00
parent 4debdfa518
commit 280fd7f988
106 changed files with 10377 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# T14 — Path rendering
**Phase** 5 · **Depends on** T04, T13 · **Status** Done
## Goal
Draw the ride on the map: one polyline per segment, coloured by speed, decimated for
performance, auto-fitted to the route. Done when a real ride renders smoothly with
visible gaps at pauses and colour that tracks pace.
## Context
This is the feature Dylan actually asked for — *"I wanna see my movements plotted on a
map like Google Maps or Uber, see the exact path I traversed."*
The data has been there since v1. Every point carries `latitude`, `longitude`, and
`speedKmh` at 1–2 Hz. Nothing new is captured; this renders what already exists.
Scale is the constraint: a three-hour ride at 2 Hz is ~21,600 points. Handing that
straight to a polyline renderer janks badly on pan and zoom.
## Design
### Decimation — render only
`Geo.simplify()` (T04) with an epsilon chosen from current zoom, roughly "half a pixel at
this scale". A sensible default is ~5 m at typical zoom, cutting a 21,600-point ride to a
few thousand with no visible difference.
> **Raw points are never decimated in storage or export.** Decimation exists purely in
> the render path. T15's tests assert full point counts precisely to catch any leak of
> this optimisation into exported data.
### One polyline per segment
Segments must not be joined. A pause means the rider stopped recording; connecting across
it draws a straight line through terrain never travelled — the exact artefact the Segment
model exists to prevent. Each segment gets its own `Polyline`, so gaps appear naturally.
### Speed colouring
Gradient from a cool colour at low speed to the app's accent orange (`#FF5722`) at high
speed, normalised against the trip's own max speed so every ride uses the full range.
osmdroid 6.1 offers `PolyChromaticPaintList` for per-vertex colouring. It is fiddly. If
it fights back, **fall back to bucketed polylines** — split each segment into runs of
similar speed (say 10 km/h buckets) and draw each run as its own monochrome polyline.
Visually near-identical, considerably more predictable. Do not burn hours on the
chromatic API.
Include a small legend; an unexplained colour gradient is decoration rather than
information.
### Auto-fit and markers
`Geo.bounds()` (T04) → `mapView.zoomToBoundingBox(bounds, false, padding)`. Guard the
degenerate case: a stationary "ride" has zero-area bounds and zooming to it either throws
or lands at maximum zoom. Fall back to centring at a fixed zoom.
Start and end markers, styled minimally.
## Implementation
1. Load points grouped by segment (already ordered `segmentId, id` from T02).
2. Decimate each segment independently — never across a boundary.
3. Build one `Polyline` per segment with speed-derived colour.
4. Add start/end markers.
5. Auto-fit with padding; handle degenerate bounds.
6. Recompute decimation on significant zoom change; debounce it.
7. Legend.
## Acceptance criteria
- [ ] Real ride renders with no straight-line artefact across pauses
- [ ] Colour visibly tracks speed
- [ ] A 20,000-point ride pans and zooms smoothly
- [x] Auto-fit frames the whole route with padding
- [x] A stationary ride does not crash the fit
- [x] Exported GPX still contains every raw point (verified: 24 points recorded, 24 exported)
- [x] Single-point and empty trips render without crashing
> **Not verified.** Unchecked items above were not tested. Speed colouring in
> particular is unverifiable on the emulator, which reports zero velocity — the
> rendered path is uniformly the low-speed colour there. Tracked in T18.
## Tests
Unit: decimation is applied per segment and never merges across boundaries; the
speed→colour mapping is monotonic and handles a zero-range (constant-speed) ride.
Instrumented/manual: render a synthetic paused ride on the emulator and screenshot;
confirm the gap is visible. Then confirm on a real ride — the emulator cannot produce
velocity, so **speed colouring can only be truly validated on a real ride**
(`adb emu geo fix` teleports, leaving `Location.speed` at 0 and every point the same
colour).
## Risks / gotchas
- **Speed colouring is unverifiable on the emulator.** Expect a uniform-colour path
there; that is the harness, not a bug. Validate on a real ride.
- **Zero-range normalisation** — a constant-speed ride divides by zero. Guard it.
- **Decimation leaking into export** is the correctness risk in this task. Keep the
simplified list strictly local to rendering.
- **Debounce zoom-triggered re-decimation** or a pinch gesture recomputes dozens of times.
## Out of scope
Live path during recording (v3+); route matching; heatmaps.