RIDE FILE · THU SEP 3 2026
Lakes loop
18.4 MI · 1:08 · MINNEAPOLIS
Body × Bike
What the bike did on top, what the body paid underneath. Where the two lines pull apart is the story of the ride. Coral is the body paying more than the bike asked. Ice is the body paying less.
Cardio engine
Everything above answers what this ride cost. This answers a slower question: is the engine getting bigger. VO₂ max is the one number three vendors estimate and none of them measure. Proto holds the measurement and grades the estimates against it.
LAB · GAS EXCHANGE
scheduled · the well, lake elmoMeasured. Metabolic cart, ramp to exhaustion, with VT1 and VT2 heart rates. Becomes the anchor every estimate below is graded against.
— mL/kg/minAPPLE WATCH · CARDIO FITNESS
estimate · aug 29 2026 · walk, run, hike onlyRated “above average” for 8 weeks. Started near 38.5 in late June, peaked near 43 in mid July, settled at 42. Every sample came from a walk or run, none from a bike ride.
42 mL/kg/minWHOOP · AEROBIC FITNESS
estimate · sep 4 2026 · monthlyMonthly readings of 37, 38, 39, 40 since June, the first of them a manual entry. Six-month average 38. WHOOP asks for a 15-minute GPS run to sharpen its estimate. Like Apple, it does not learn from a bike.
40 mL/kg/minPROTO · CYCLING ESTIMATE
model · sep 3 ride · stand-in road, fitted hr, no power meterPower estimated from speed, grade, and headwind, fitted against heart rate and extrapolated to 186 bpm: 41. Heart-rate-reserve scaling says 38. Rest-to-max ratio alone says 59. The spread is the honest reading.
≈41 mL/kg/min · 38 to 59READ Apple says 42, WHOOP says 40, the Proto model says 41. Three estimates within two points of each other, and all three are extrapolations from heart rate, none of them a measurement. Both wearables want a run before they will trust their own number. If the lab came back in that range for a 41-year-old man, it sits near the 65th percentile in the FRIEND registry, three points under the 75th-percentile line Proto calls optimal. Until the cart says otherwise, the anchor stays blank.
RULE The anchor stays blank until a measured value lands. Each estimate is shown next to it with its error, never in place of it. A trajectory over months needs two anchors, or one anchor and an estimator that has earned it.
What changed today
Not a description of the charts. A read of the ride, with every sentence tagged by how it was reached: observed by a sensor, calculated from the samples, or inferred from context.
The rest of the bill
Six more reads that a ride computer does not itemize. Each one names its source and its confidence.
Same road, different day
No leaderboard against strangers. The comparison is you, on this road, on other days. Proto recognizes recurring sections by GPS clustering, so the climb and the greenway become their own personal segments without a Strava segment ever existing.
Environment-adjusted
A slower ride can be a stronger ride. Wind at each GPS fix is decomposed against the rider's heading, temperature and humidity are aligned to the timestamp, and speed is solved back to a still-air, flat-road equivalent before it touches a baseline.
Works from the wrist up
No thousand-dollar head unit required. The file gets richer as sensors appear, and a casual rider is never shown a locked screen.
Apple Watch
GPS · heart rate · speed · distance · elevation · motion · one-minute recovery
- Body Cost
- Efficiency (still-air speed per beat)
- Cardiac Drift (speed-based)
- Snapback
- Climb Cost
- Stop Tax
- Surface Load (experimental)
Apple Watch + WHOOP
adds recovery · HRV · sleep · strain · skin temperature · respiratory rate
- Readiness × Performance
- Cost read against last night
- Two-wrist HR concordance
- Strain reconciled to the ride
+ power and cadence
adds watts · cadence · FTP · power zones · normalized power
- Efficiency Factor in W per beat
- Power-based drift (Pw:Hr)
- Torque-aware Climb Cost
- Stop Tax in kilojoules, not estimates
Where every number came from
Provenance is part of the page, not a settings screen. Tap a card above and it names its sources. This is the full ledger for this ride.
Start Ride, same wallet
/course reads a round from the wrist. /hoops reads a run. /cycle reads a ride. The gate is the same, the vault is the same, and what sits underneath is the same: the blood and sleep wallet. A ride lands next to your labs and your nights, and the verdict reads all three. Strava never has the nights. WHOOP never has the road. The bike computer never has either.
The Watch is the recorder
Outdoor Cycle on the wrist. GPS, heart rate, elevation, speed, and paired Bluetooth power or cadence land in one HealthKit workout with a route. Raw motion stays in the vault.
WHOOP is the night before
Recovery, HRV, sleep, and strain arrive by API. They are context for the ride, not a second ride. One workout is recorded, never two.
The weather is aligned to the fix
Wind, temperature, humidity, and air quality are pulled for the ride's hour and joined to every GPS point by heading. The headwind gets its own line on the bill.
The road remembers you
Repeated sections cluster on their own. The next time you ride the bluff, the page compares you to you, not to the fastest stranger who ever passed through.
Ride contract
The page renders whatever the Worker serves at /api/cycle/ride/latest. Bounded summaries plus a decimated route, same egress rule as /swing, /course, and /hoops. Raw high-rate motion never leaves the device pair.
{
"ride": "thursday-ride", "city": "Minneapolis",
"session_stamp": "2026-09-03T06:52", // local, coarse
"dt_s": 2, // decimated from 1 Hz on the phone
"series": { "t": [...], "lat": [...], "lon": [...], "d": [...], "v": [...], "ele": [...],
"g": [...], "hr": [...], "p": [...], "cad": [...],
"hw": [...], // headwind component, m/s, + into the face
"rough": [...], // wrist IMU roughness proxy, experimental
"seg": [...] }, // clustered section key, personal
"recovery_hr": [[s, bpm], ...], // 120 s after the finish, from the Watch
"env": { "wind_from_deg", "wind_mps_10m", "temp_c", "humidity", "aqi", "source" },
"rider": { "hr_rest", "hr_max", "mass_kg", "ftp_w", "zones_bpm": [z1..z5] },
"readiness": { "whoop_recovery", "whoop_hrv_ms", "sleep_h", "apple_hrv_sdnn_ms" },
"engine": { "vo2max_lab_ml_kg_min", "lab_date", "lab_operator", "vt1_bpm", "vt2_bpm",
"vo2max_apple", "vo2max_whoop", "vo2max_cycle_est" }, // anchor first, estimates graded against it
"baseline": { "days": 30, "rides": 11, "ef_w_per_bpm", "ef_gas_mps_per_bpm",
"body_cost_route_median", "snapback_60s_median", "drift_median_pct", "climb_cost_median" },
"provenance": { "apple_watch": [...], "whoop": [...], "power_meter": [...], "weather": [...], "proto": [...] }
}