---
calendar:
  '2026':
    b4bh-toronto: https://bikeforbrainhealth.ca/
    toronto: https://supertri.com/toronto-triathlon/
  '2027':
    hyrox-miami: https://hyrox.com/event/sweat-pals-hyrox-miami-beach-26-27/
    lanzarote: https://www.ironman.com/races/im-lanzarote/register
    muscat: https://www.ironman.com/races/im703-oman
    toronto: https://supertri.com/toronto-triathlon/
    toronto-marathon: https://www.torontowaterfrontmarathon.com/
    vancouver: https://t100triathlon.com/vancouver/participate/
date: '2026-05-29'
description: training data and tracking
dexa:
  - ag: 1.29
    arms:
      bmc: 1
      fat: 4.7
      lean: 17.2
    bmcLbs: 7.8
    bmd: 1.383
    bmdT: 1.8
    bodyFat: 27.4
    date: '2026-06-25'
    fatLbs: 54.2
    fatLossTo14PctLbs: 30.8
    ffmLbs: 143.5
    leanLbs: 135.7
    legs:
      bmc: 3
      fat: 17.7
      lean: 54.4
    rmr: 1695
    rsmi: 9.2
    targetBodyFat: 14
    targetFatLbs: 23.4
    targetTotalLbs: 166.9
    tissueFat: 28.5
    tissueLean: 68.7
    totalLbs: 197.6
    trunk:
      bmc: 2.3
      fat: 29.3
      lean: 55
    vatAreaIn2: 8.64
    vatLbs: 1.24
events:
  b4bh--tor--26:
    participated: true
  ironman-70.3-muscat--oman--27:
    T1: null
    T2: null
    bike: null
    distance: null
    overall: null
    run: null
    swim: null
  ironman-lanzarote--spain--27:
    T1: null
    T2: null
    bike: null
    distance: null
    overall: null
    run: null
    swim: null
  supertri--tor--26:
    T1: '00:08:42'
    T2: '00:03:05'
    bike: '01:11:11'
    distance: olympic
    overall: '02:57:19'
    run: '00:53:15'
    swim: '00:41:08'
id: triathlon
layout: triathlon
maintenance:
  OSPW:
    - - type: Ultegra R8100 Pulley Wheel
      - distance: 1335
      - range:
          - end: '2026-08-10'
            start: '2026-05-16'
      - reason: upgrades to CeramicSpeed OSPW
    - - type: CeramicSpeed OSPW RS 5 Spoke
      - distance: 799.02
      - range:
          - end: null
            start: '2026-08-10'
  bottom bracket:
    - - type: FSA, T47 BBright for 24mm spindle
      - distance: null
      - range:
          - end: null
            start: '2026-05-16'
      - reason: upgrades to CeramicSpeed
    - - type: CeramicSpeed T47 BBright for Shimano
      - distance: 380.62
      - range:
          - end: null
            start: '2026-08-22'
      - reason: null
  brake pads:
    - - type: Shimano Brake Pads
      - distance: 1731.28
      - range:
          - end: '2026-08-22'
            start: '2026-05-16'
      - reason: upgrades to BRAKCO Graphene
    - - type: BRAKCO Airpads Graphene Venting Disc Brake Pads
      - distance: 380.62
      - range:
          - end: null
            start: '2026-08-26'
      - reason: null
  chain:
    '1':
      chainring: ultegra
      distance: 621
      lubricant: Muc-Off Dry Lube
      ospw: ultegra
      since: '2026-05-16'
      waxed: false
    '2':
      chainring: ultegra
      distance: 419.42
      lubricant: Muc-Off Dry Lube
      ospw: ultegra
      since: '2026-07-16'
      waxed: false
    '3':
      chainring: ultegra
      distance: 418.39
      lubricant: UFO Wax Drip-On
      ospw: ceramicspeed
      since: '2026-08-10'
      waxed: true
    '4':
      chainring: carbonti
      distance: 380.62
      lubricant: UFO Wax Drip-On
      ospw: ceramicspeed
      since: '2026-08-26'
      waxed: true
  chainrings:
    - - type: Ultegra FC-R8100 52-36T
      - distance: 1731.28
      - range:
          - end: '2026-08-22'
            reason: upgrades to CarbonTi
            start: '2026-05-16'
    - - type: CarbonTi 54-40T
      - distance: 380.62
      - range:
          - end: null
            reason: null
            start: '2026-08-26'
  service:
    soloist:
      - date: '2026-08-21'
        distance: 1731.28
        place: Racer Sportif
    speedmax: null
  tires:
    front:
      tires:
        - - type: Vittoria Corsa N.EXT TLR G2.0 700x29c
          - distance: 751.81
          - range:
              - end: '2026-07-16'
                start: '2026-05-16'
          - reason: training to race tires
        - - type: Pirelli P Zero Race TLR RS 700x28c
          - distance: 619.84
          - range:
              - end: '2026-08-10'
                reason: punctures
                start: '2026-07-16'
              - end: '2026-09-11'
                reason: big big punctures GG
                start: '2026-09-07'
        - - type: Pirelli P Zero Race SL-R 700x28c
          - distance: 776.9
          - range:
              - end: null
                reason: null
                start: '2026-08-12'
        - - type: Pirelli P Zero Race TLR SL-R 700x28c
          - distance: null
          - range:
              - end: null
                reason: null
                start: '2026-09-11'
      tube:
        - - type: Pirelli P Zero TPU
          - distance: 619.84
          - range:
              - end: '2026-08-10'
                repaired: false
                start: '2026-07-16'
          - reason: punctures
        - - type: Pirelli P Zero TPU
          - distance: 776.9
          - range:
              - end: null
                reason: null
                start: '2026-08-12'
    rear:
      tires:
        - - type: Vittoria Corsa N.EXT TLR G2.0 700x29c
          - distance: 751.81
          - start: '2026-05-16'
          - end: '2026-07-16'
          - reason: training to race tires
        - - type: Pirelli P Zero Race SL-R 700x28c
          - distance: 1138.08
          - range:
              - end: '2026-08-10'
                reason: punctures and big ruptures, but fixed
                start: '2026-07-16'
              - end: '2026-09-04'
                reason: setup tubeless
                start: '2026-08-18'
        - - type: Pirelli P Zero Race SL-R 700x28c
          - distance: 258.65
          - range:
              - end: '2026-08-17'
                reason: big punctures cut
                start: '2026-08-12'
        - - type: Pirelli P Zero Race TLR RS 700x28c
          - distance: null
          - range:
              - end: null
                reason: null
                start: '2026-09-07'
      tube:
        - - type: Pirelli P Zero TPU
          - distance: 878.49
          - range:
              - end: '2026-08-17'
                reason: big punctures, but will be the backup now.
                repaired: true
                start: '2026-07-16'
        - - type: Pirelli P Zero TPU
          - distance: 521.66
          - range:
              - end: '2026-09-03'
                reason: slow punctures
                repaired: true
                start: '2026-08-17'
              - end: null
                start: '2026-09-04'
modified: 2026-09-30 16:36:46 GMT-04:00
race: Lanzarote
seealso:
  - '[[thoughts/pdfs/supertri.pdf|SuperTri fuel plan]]'
  - '[[thoughts/pdfs/703NYC.pdf|IRONMAN 70.3 NYC fuel plan]]'
strava: '2026-05-15'
tags:
  - evergreen
title: triathlon
triathlon: ironman
vo2max:
  - caloriesAtVt1: 850
    date: '2026-06-25'
    hrAtVo2max: 180
    hrMax: 182
    massKg: 88.9
    maxKmh: 15
    percentile: 70
    profile:
      cooldownStartSec: 735
      durationSec: 760
      samples:
        - - 0
          - 9.6
          - 73
          - 31.6
          - 16.9
          - 1.86
        - - 10
          - 9
          - 77
          - 34.9
          - 15.2
          - 2.37
        - - 20
          - 8.4
          - 79
          - 34.2
          - 14.9
          - 2.51
        - - 30
          - 7.9
          - 81
          - 33.7
          - 17.5
          - 2.21
        - - 40
          - 7.1
          - 87
          - 32.2
          - 16.7
          - 2.21
        - - 50
          - 7.6
          - 92
          - 31.7
          - 15.8
          - 2.2
        - - 60
          - 8
          - 99
          - 28.5
          - 14.2
          - 2.09
        - - 70
          - 8.3
          - 95
          - 20.8
          - 13.9
          - 1.59
        - - 80
          - 7
          - 94
          - 14.8
          - 18.9
          - 1.02
        - - 90
          - 9.4
          - 95
          - 19.3
          - 18.8
          - 1.34
        - - 100
          - 12.3
          - 95
          - 24.2
          - 19.6
          - 1.5
        - - 110
          - 15.4
          - 102
          - 31.6
          - 18.7
          - 1.89
        - - 120
          - 21.3
          - 112
          - 34.9
          - 19.1
          - 1.98
        - - 130
          - 17.6
          - 112
          - 30.9
          - 21.1
          - 1.53
        - - 140
          - 15.7
          - 110
          - 27.7
          - 20.4
          - 1.45
        - - 150
          - 13.4
          - 105
          - 26.4
          - 19.4
          - 1.49
        - - 160
          - 13.7
          - 106
          - 26.7
          - 22.6
          - 1.44
        - - 170
          - 15.6
          - 105
          - 30.4
          - 27.3
          - 1.37
        - - 180
          - 16.2
          - 109
          - 31.6
          - 31.9
          - 1.2
        - - 190
          - 17.4
          - 106
          - 32.9
          - 26.9
          - 1.42
        - - 200
          - 16
          - 101
          - 31.4
          - 24.4
          - 1.52
        - - 210
          - 14.5
          - 101
          - 31.4
          - 21.8
          - 1.52
        - - 220
          - 13.8
          - 103
          - 30.6
          - 22.2
          - 1.51
        - - 230
          - 14.3
          - 111
          - 33.1
          - 22.3
          - 1.67
        - - 240
          - 15.7
          - 121
          - 35.6
          - 21.2
          - 1.91
        - - 250
          - 16.7
          - 124
          - 37.8
          - 18.8
          - 2.18
        - - 260
          - 18.4
          - 127
          - 38.4
          - 18.3
          - 2.25
        - - 270
          - 20.3
          - 126
          - 40.8
          - 18.8
          - 2.33
        - - 280
          - 23
          - 127
          - 43.3
          - 18.6
          - 2.54
        - - 290
          - 25.8
          - 132
          - 46.8
          - 17
          - 2.83
        - - 300
          - 27.3
          - 139
          - 48.9
          - 17.4
          - 2.88
        - - 310
          - 27.4
          - 138
          - 49.7
          - 18.6
          - 2.78
        - - 320
          - 27.7
          - 133
          - 50.4
          - 21.2
          - 2.67
        - - 330
          - 26.5
          - 130
          - 51.2
          - 24.5
          - 2.33
        - - 340
          - 26.8
          - 135
          - 52.4
          - 26.3
          - 2.16
        - - 350
          - 28
          - 141
          - 54.9
          - 23.9
          - 2.38
        - - 360
          - 29
          - 145
          - 54.6
          - 22.8
          - 2.51
        - - 370
          - 29.8
          - 146
          - 54.6
          - 21.7
          - 2.65
        - - 380
          - 30.1
          - 145
          - 54.9
          - 21.4
          - 2.76
        - - 390
          - 30.3
          - 146
          - 56.7
          - 22.8
          - 2.58
        - - 400
          - 31.1
          - 145
          - 58.1
          - 21.6
          - 2.75
        - - 410
          - 31.9
          - 145
          - 59.3
          - 23
          - 2.59
        - - 420
          - 32
          - 148
          - 58.6
          - 22.1
          - 2.73
        - - 430
          - 36.2
          - 151
          - 66.5
          - 22.1
          - 3.02
        - - 440
          - 36.8
          - 154
          - 71.2
          - 23.5
          - 3.02
        - - 450
          - 36.2
          - 154
          - 72.2
          - 25.4
          - 2.9
        - - 460
          - 35
          - 152
          - 71.2
          - 29
          - 2.5
        - - 470
          - 36.9
          - 155
          - 74.7
          - 29.5
          - 2.58
        - - 480
          - 38.9
          - 158
          - 79.6
          - 27.5
          - 2.93
        - - 490
          - 38.4
          - 159
          - 81.1
          - 27.1
          - 3.01
        - - 500
          - 38.7
          - 161
          - 82.9
          - 27.3
          - 3.06
        - - 510
          - 40.5
          - 163
          - 86.1
          - 28
          - 3.11
        - - 520
          - 40.4
          - 164
          - 87.6
          - 31.3
          - 2.81
        - - 530
          - 39.9
          - 167
          - 85
          - 30.9
          - 2.76
        - - 540
          - 44.2
          - 168
          - 100.2
          - 34.2
          - 2.98
        - - 550
          - 43.1
          - 168
          - 110.3
          - 39.2
          - 2.82
        - - 560
          - 43.3
          - 169
          - 109.6
          - 39.3
          - 2.79
        - - 570
          - 43.1
          - 171
          - 106.8
          - 39.1
          - 2.72
        - - 580
          - 44.2
          - 173
          - 109.1
          - 39.4
          - 2.77
        - - 590
          - 43.7
          - 173
          - 107.6
          - 40.3
          - 2.68
        - - 600
          - 43.9
          - 176
          - 107.6
          - 41.7
          - 2.61
        - - 610
          - 46.1
          - 175
          - 111.6
          - 41
          - 2.74
        - - 620
          - 46.4
          - 176
          - 115.5
          - 41.4
          - 2.77
        - - 630
          - 47.5
          - 177
          - 115
          - 42.9
          - 2.67
        - - 640
          - 49.5
          - 177
          - 120.7
          - 42.8
          - 2.81
        - - 650
          - 46.5
          - 180
          - 116
          - 42.8
          - 2.7
        - - 660
          - 46.6
          - 179
          - 113.7
          - 43
          - 2.64
        - - 670
          - 47
          - 181
          - 111.5
          - 43.6
          - 2.57
        - - 680
          - 46
          - 181
          - 109.1
          - 42.6
          - 2.56
        - - 690
          - 47.2
          - 180
          - 108
          - 42.6
          - 2.54
        - - 700
          - 47.3
          - 181
          - 112
          - 45.8
          - 2.44
        - - 710
          - 48.5
          - 181
          - 121.4
          - 54.3
          - 2.27
        - - 720
          - 42.8
          - 180
          - 114.3
          - 45.5
          - 2.55
        - - 730
          - 42
          - 175
          - 112
          - 45.8
          - null
        - - 740
          - null
          - 166
          - null
          - null
          - null
        - - 750
          - null
          - 161
          - null
          - null
          - null
        - - 760
          - null
          - 158
          - null
          - null
          - null
      stats:
        hr:
          - 72
          - 182
          - 139
        rf:
          - 13
          - 56.4
          - 31.9
        tv:
          - 1
          - 3.2
          - 2.5
        ve:
          - 14.3
          - 123
          - 79.2
        vo2:
          - 6.7
          - 50.5
          - 35.9
      targetKmh:
        - - 0
          - 5
        - - 120
          - 7
        - - 240
          - 9
        - - 360
          - 11
        - - 480
          - 13
        - - 600
          - 15
        - - 710
          - 17
        - - 735
          - 5
      vo2maxSec: 670
      vt1Sec: 390
      warmupEndSec: 120
    value: 47.8
    ve: 117
    vt1Hr: 145
    vt1Kmh: 10.5
    vt2Hr: null
    vt2Kmh: null
    zonesHr:
      - 112
      - 121
      - 142
      - 171
    zonesKcal:
      - 480
      - 580
      - 820
      - 1170
    zonesKmh:
      - 7
      - 8
      - 10.5
      - 14
created: '2026-05-29'
published: '2026-05-29'
pageLayout: triathlon
slug: triathlon
permalink: https://aarnphm.xyz/triathlon.md
generator:
  quartz: v4.6.0
  hostedProvider: Cloudflare
  baseUrl: aarnphm.xyz
full: https://aarnphm.xyz/llms-full.txt
---
<!-- training plan start
meta: IRONMAN Lanzarote
date: 2026-09-01
distance: ironman
endDate: 2027-05-15
target: sub-10
author: James Cunnama
-->

things go well <span>&rarr;</span> let James know

if things don’t go well <span>&rarr;</span> also let James know

4-6 weeks: finding our feet, first few months really

note for self:

- be honest
- inconvience and consistent and also let James know
- fungible

### goals

> \[!note\] NOTE
>
> IRONMAN Lanzarote 2027
>
> - dry, close to the sea
> - lava

find something 70.3 March or latest April

Single sports events: Long Ride, 100 mi Sportif, Half-Marathon

> cautious and take it slow.

feedback, constant on TrainingPeak + WhatsApp

EOD: Send voice notes, 30s, (swim great, bike tough, but didn’t want to be there, did ok.)

### tech

_TrainingPeak + Turbo + GO MAX WIN, locked in time._

### training camps

> when, where?

After Oman, 2-3 weeks. Mid-End March 2027

Options:

- Lanzarote (Routes and ask James about this)
- KIS training camp, Utah

<!-- training plan end -->

<!-- training plan start
meta: equation references
date: 2026-06-07
-->

## equation reference

This is the current calculation sheet for the triathlon activity cards and analytics. It describes what the site computes, rather than every value a provider may send. Unless a formula says otherwise, distance is in metres, time is in seconds, speed is in metres per second, heart rate is in beats per minute, and grade is a decimal rather than a percentage.

### route, document, and interaction reference

The `/triathlon` route family is generated from the same enriched activity, health, analytics, maintenance, and training-plan snapshot. The browser pages expose these views:

| route                            | behaviour                                                                                                                                                         | data shown                                                                                                                                                                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/triathlon`                     | scrollable day timeline, day selection, activity expansion, and the shared gear, pace, calculator, analytics, map, and training controls                          | daily activity cards, recovery state, activity telemetry, weather, training state, gear, and maintenance                                                                                                                     |
| `/triathlon/tools`               | scrollable equipment and conversion reference                                                                                                                     | bike and training gear, component and service history, run/swim/bike pace and speed conversions                                                                                                                              |
| `/triathlon/calc`                | race, gear-ratio, and tire-pressure tabs; editable legs and finish target; shareable race inputs                                                                  | race-distance presets, current thresholds and calibration, projected paces, drivetrain ratios and losses, rider/bike mass, weather, and pressure inputs                                                                      |
| `/triathlon/analytics`           | searchable analytics catalog, activity lookup, two-activity comparison, chart scrubbing, and linked activity detail                                               | body composition, training load, recovery, sleep, VO2max, lactate threshold, power, abilities, distributions, cardiovascular trends, weekly load and effort, heat, readiness, pace trend, training actions, and FTP evidence |
| `/triathlon/maps`                | searchable route map with sport filters, heat/power/heart-rate/cadence/speed overlays, terrain, satellite style, route selection, and an activity-detail scroller | routed activity geometry, streams, summary metrics, weather, health, zones, and power references                                                                                                                             |
| `/triathlon/training`            | searchable plan selector, section tree, and scrollable plan document                                                                                              | parsed training-plan metadata and complete plan content                                                                                                                                                                      |
| `/triathlon/feed`                | newest-first activity search; free-text terms; `filter:<sport-or-date>`; `sort:distance`, `sort:cadence`, or `sort:pace`; expandable activity detail              | activity summaries plus lazily loaded telemetry and same-day recovery                                                                                                                                                        |
| `/triathlon/on`                  | newest-first year, month, and day tree with activity count, distance, duration, sport labels, and rest days                                                       | the complete generated date index                                                                                                                                                                                            |
| `/triathlon/on/<YYYY>`           | year-scoped version of the date tree and feed document                                                                                                            | days and activities whose local date starts with the selected year                                                                                                                                                           |
| `/triathlon/on/<YYYY>/<MM>`      | month-scoped version of the date tree and feed document                                                                                                           | days and activities whose local date starts with the selected month                                                                                                                                                          |
| `/triathlon/on/<YYYY>/<MM>/<DD>` | expanded canonical day card with anchors for individual activities                                                                                                | that local day’s activities, streams, recovery, health, weather, tracking overrides, and daily analytics; rest-only dates still receive a page                                                                               |

#### agent navigation

Agents should use `/triathlon.md` as the route-family index and select the narrowest document that answers the task. Request a canonical route with `Accept: text/markdown` or append `.md`, then follow the route-local data pointers.

- Use a fixed document for one domain, `/triathlon/feed.md` for the complete activity history,
- `/triathlon/on/<YYYY>/<MM>/<DD>.md` for a known local date, `/triathlon/data` for chronological cross-route analysis, and `/static/strava-detail.json` to locate the bounded shard containing complete activity payloads.
- Open the canonical HTML route when the task requires rendered-state or interaction evidence because the Markdown sibling describes the contract without reproducing hydrated browser state.
- <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>K</kbd> opens the triathlon command palette.
- <kbd>/</kbd> focuses or blurs the active search on analytics, maps, training, and feed. On a fixed subpage, <kbd>g</kbd> followed within one second by <kbd>g</kbd>, <kbd>c</kbd>, <kbd>a</kbd>, <kbd>m</kbd>, <kbd>t</kbd>/<kbd>r</kbd>, <kbd>f</kbd>, <kbd>o</kbd>, or <kbd>h</kbd> navigates to tools, calculator, analytics, maps, training, feed, the date index, or `/triathlon`, respectively.
- On `/triathlon`, <kbd>g</kbd> followed by <kbd>a</kbd>, <kbd>c</kbd>, <kbd>m</kbd>, <kbd>t</kbd>, <kbd>g</kbd>, <kbd>p</kbd>, or <kbd>s</kbd> opens analytics, calculator, maps, training, gear, pace, or the summary.
- <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>G</kbd> opens tools anywhere in the route family, while <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>\\</kbd> returns a fixed subpage to `/triathlon` and `/triathlon` to the site root.
- <kbd>j</kbd>/<kbd>k</kbd> moves through feed and date-tree rows, and the calculator uses <kbd>r</kbd>, <kbd>c</kbd>, and <kbd>t</kbd> for race, gear ratios, and tire pressure.
- `?tri-debug=performance` opens the performance panel on every fixed, archive, and day page; <kbd>Option</kbd>+<kbd>Shift</kbd>+<kbd>D</kbd> toggles it. The panel reports FPS, frame p95, over-budget and long-frame counts, blocked time, layout shift, cursor/timeline/popover samples, scroll rate, heap, DOM size, and worst script/event attribution.

Every document route has a Markdown sibling. `/triathlon.md` is this source note. The generated fixed, year/month archive, and day pages use the same pathname with `.md` appended. A `GET` or `HEAD` request from a recognized agent user agent, or one accepting `text/markdown` with positive quality, receives the Markdown sibling automatically. Ordinary browser requests receive HTML. Explicit `.md` requests stay explicit. Generated Markdown is route-specific:

- `tools.md` contains conversions, race distances, maintenance, and gear.
- `calc.md` contains presets, calibration, thresholds, race readiness, events, FTP evidence, and zones.
- `analytics.md` contains the complete analytics object and related data URLs.
- `maps.md` contains compact per-activity route summaries and points to the full activity payload.
- `training.md` contains complete plan prose and tables.
- `feed.md` contains the complete generated activity feed, summary, active-day rows, and weekly rows.
- `on.md`, year/month archive Markdown, and day Markdown are date-scoped. Day Markdown includes the complete matching activity-detail objects.

The shortcut host `t.aarnphm.xyz` issues permanent redirects into this route family. `/`, `/tools`, `/calc`, `/analytics`, `/maps`, `/training`, `/feed`, and `/on` map to their `/triathlon` equivalents. `/<YYYY>`, `/<YYYY>/<MM>`, and `/<YYYY>/<MM>/<DD>` map into `/triathlon/on`; `/activities/<Strava ID>` resolves the activity’s local date through the generated activity index and redirects to its day page.

### machine-readable data and model access

`/triathlon/data` is the stable machine-readable feed route. A non-agent browser request that accepts `text/html` receives an expandable HTML inspector. Agent requests and other accepted media types receive `application/x-ndjson`. The response permits cross-origin reads, varies on `Accept` and `User-Agent`, and uses a five-minute shared-cache lifetime with 59 seconds of stale revalidation. `/static/triathlon/data.jsonl` is the raw asset behind both representations.

The NDJSON stream is chronological within each record group and contains one JSON object per line:

- `meta` declares schema version, generation time, athlete and observation window, zones, thresholds, critical-power estimates, and record counts.
- `day` records sessions, distance, duration, load, CTL, ATL, TSB, sport-specific CTL, readiness, sleep, HRV, resting heart rate, calories, body composition, weather, and next-day recovery context.
- `activity` records swim, bike, and run identity, local date, distance, times, elevation, heart rate, power, cadence or strokes, calories, weather, training exclusion, grade-adjusted speed, intensity, load, peak power and speed, efficiency factor, and decoupling.
- `week` records volume, time, load, relative effort, target ranges, ramp, monotony, and strain.

The browser pages load these public data assets as needed:

| path                                       | contract                                                                                                                                                          |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/static/strava-detail.json`               | activity-detail manifest with monthly shard paths, health and daily analytics, zones, power curves, critical power, thresholds, and matched Garmin/Strava efforts |
| `/static/strava-detail/<YYYY-MM[-N]>.json` | exact activity summaries, streams, routes, laps, zones, weather, and provider-derived detail for one bounded shard                                                |
| `/static/analytics.json`                   | complete generated analytics object used by analytics, feed, maps, command search, and calculator projection                                                      |
| `/static/oura-detail.json`                 | date-keyed detailed Oura sleep and recovery records used by the sleep surface                                                                                     |
| `/static/training.json`                    | parsed training-plan documents                                                                                                                                    |
| `/static/strava-activity-index.json`       | Strava activity ID to local-date map used by `t.aarnphm.xyz/activities/<ID>`                                                                                      |
| `/static/triathlon/data.jsonl`             | raw `meta`, `day`, `activity`, and `week` NDJSON used by the feed documents and pace forecaster                                                                   |

The published model families are `pace` and `hr`. Their public objects are:

| path                                            | data                                                                                                                     |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `/models/<family>/latest.json`                  | mutable pointer containing the current version, manifest and weight keys, SHA-256, dataset hash, and validation metadata |
| `/models/<family>/v<version>/manifest.json`     | immutable typed feature, normalization, output, validation, and golden-parity contract                                   |
| `/models/<family>/v<version>/model.safetensors` | immutable model weights                                                                                                  |

The models endpoint accepts `GET` and `HEAD`; other methods return `405`. Production reads objects from R2, forwards object HTTP metadata and ETag, enables cross-origin access, caches `latest.json` for 60 seconds with mandatory revalidation, and caches versioned manifests and weights for one year as immutable objects. Missing keys return `404`. Local requests read the generated static model tree, enable cross-origin access, and use `no-store`; the development watcher atomically exposes the latest archive under `/models/<family>/local-v<version>/` and writes a local `latest.json` pointer.

The in-browser pace forecaster first reads `/static/triathlon/data.jsonl`, then gets `/models/pace/latest.json`, its manifest, and its weights. It verifies the weights against the advertised SHA-256, loads WebGPU when available with WASM as the runtime fallback, runs the manifest’s golden-parity check, and only then exposes predictions.

### source precedence

For a ride recorded on both Rouvy and the BOLT, attach the BOLT file with `wahoo: [[triathlon/wahoo/<file>.fit]]` in the ride’s tracking block. The Strava activity ID remains canonical. With `virtual: true`, Garmin supplies the virtual course, distance, and elevation when available; Strava supplies the activity timing. The linked FIT supplies recorded power, heart rate, cadence, device temperature, cycling dynamics, shifts, and native Wahoo summary metrics. Telemetry aligns by UTC time, with Strava or Garmin filling missing power, heart-rate, and cadence samples only when the FIT has no value within 2.5 seconds. Gear shifts and cycling dynamics use the virtual course’s distance axis. The source caches and FIT stay unchanged, and an existing Wahoo recording is counted once.

The card identifies Wahoo telemetry and retains separate Garmin and Strava normalized-power values and Garmin and Wahoo TSS. CORE temperature samples duplicated in the legacy muscle-oxygen fields are shown only as temperature, following [CORE’s Wahoo field mapping](https://help.corebodytemp.com/hc/en-us/articles/34420267066002-Wahoo).

The activity-card values use these ordered sources:

1. Intensity factor uses the linked Wahoo FIT’s native value when present, then Garmin’s native value.
2. Missing intensity factor is calculated from device power for a bike, pace for a run or swim, then heart rate as the remaining fallback.
3. Exercise load uses Garmin’s native value when it exists.
4. Missing exercise load is calculated from the linked Wahoo IF, then Garmin IF, otherwise from the locally calculated IF.
5. Garmin aerobic and anaerobic training effect scores, messages, and labels take priority when they exist. A sauna tracking block can pin the exact Garmin Connect source with `garmin: <activity ID>`.
6. When Garmin has no training effect details, an activity with observed load uses a local estimate. An Apple Watch sauna normally uses the relative effort from its canonical Strava recording. The aerobic score uses Strava relative effort when it exists, then exercise load. The anaerobic score uses the stronger result from upper heart rate zones and short high intensity intervals. The training effect heading exposes the selected inputs on hover and keyboard focus.

The analytics load that drives CTL, ATL, TSB, ACWR, and weekly load is separate from the exercise-load number shown on an activity card. Swim, bike, and run use grade-adjusted pace and duration. Strength and yoga use threshold-normalized heart-rate TRIMP when average exercise heart rate and resting, threshold, and maximum heart rate are available. A strength or yoga activity without valid heart-rate inputs contributes zero load. Pace and volume calibration remains scoped to swim, bike, and run.

Provider scores and local estimates are clamped for display:

$$
\mathrm{TE}_{\mathrm{display}}
=
\operatorname{clamp}
\left(
\mathrm{TE}_{\mathrm{source}},
0,
5
\right).
$$

When Garmin sends a score without a message, the local prose bins are

$$
\operatorname{label}(\mathrm{TE}) =
\begin{cases}
\text{no benefit}, & 0\le\mathrm{TE}<1,\\
\text{minor benefit}, & 1\le\mathrm{TE}<2,\\
\text{maintaining fitness}, & 2\le\mathrm{TE}<3,\\
\text{improving fitness}, & 3\le\mathrm{TE}<4,\\
\text{highly improving fitness}, & 4\le\mathrm{TE}<5,\\
\text{overreaching effect}, & \mathrm{TE}=5.
\end{cases}
$$

For a calculated ride, the site converts the complete power stream to one sample per second and smooths power over five seconds. It uses the calendar-year critical power estimate when available, then the six-week estimate. The model supplies critical power $CP$ and the finite work capacity above critical power $W'$. A continuous effort $i$ is eligible when the smoothed power stays above $CP$ for 10 to 120 seconds. Its share of $W'$ is

$$
q_i
=
\frac{1}{W'}
\sum_{t\in i}
\max\left(0,\bar P_t-CP\right).
$$

An effort starts contributing after it spends $10\%$ of $W'$. The session stimulus discounts accumulated road surges by moving duration $T$ in hours:

$$
S
=
\frac{
\displaystyle\sum_{i\in\mathcal I}
\max\left(0,\frac{q_i-0.1}{0.9}\right)
}{
\max(1,T)^{0.6}
},
\qquad
\mathrm{TE}_{\mathrm{power}}
=
5\left(1-e^{-0.24S}\right).
$$

The anaerobic estimate uses the larger score from the power stimulus and the total time in the upper two heart rate zones:

$$
\mathrm{TE}_{\mathrm{anaerobic}}
=
\max
\left(
\mathrm{TE}_{\mathrm{power}},
s_{\mathrm{HR}}(t_{Z4}+t_{Z5})
\right).
$$

The power branch stays unavailable when no fitted $CP$ and $W'$ profile exists. Run and swim estimates use the existing interval scale with pace efforts. These local scores remain estimates because the site does not reproduce Garmin’s EPOC model or training history.

### cycling stamina

Garmin activity details supply current stamina and potential stamina as native percentage streams. Garmin describes current stamina as the capacity left at the present effort. It can recover toward potential stamina after the effort falls. Potential stamina tracks slower fatigue and can start below 100 when recovery from earlier training is incomplete.\[^garmin-stamina\]

Wahoo FIT files do not contain either stamina stream. A Wahoo matched ride receives a Garden estimate when at least 80% of its aligned samples have valid time, power, and heart rate values, and when FTP and maximum heart rate are available. Garmin native stamina keeps first priority. The activity payload records `staminaTrace.source`, the method version, FTP, and maximum heart rate so the local estimate cannot be mistaken for a provider value.

Let $P_i$ be power, $H_i$ be heart rate, $P_{\mathrm{FTP}}$ be FTP, and $H_{\max}$ be maximum heart rate. The potential stamina loss rate in percentage points per hour is

$$
R_i
=
17.14
\left(
\frac{P_i}{P_{\mathrm{FTP}}}
\right)^{2.5}
+
98.85
\left(
\frac{H_i}{H_{\max}}
\right)^{10}.
$$

Potential stamina $U_i$ is

$$
U_i
=
\operatorname{clamp}
\left(
U_{i-1}-R_i\frac{\Delta t_i}{3600},
0,
100
\right).
$$

The current stamina deficit $D_i$ grows during work above FTP and recovers during easier work:

$$
D_i
=
\begin{cases}
\operatorname{clamp}
\left(
D_{i-1}
+
800
\left(
\frac{P_i}{P_{\mathrm{FTP}}}-1
\right)^{1.5}
\frac{\Delta t_i}{3600},
0,
U_i
\right),
& P_i>P_{\mathrm{FTP}},\\
D_{i-1}\exp\left(-\frac{\Delta t_i}{360}\right),
& P_i\le P_{\mathrm{FTP}}.
\end{cases}
$$

Current stamina is $S_i=U_i-D_i$. Gaps longer than five seconds do not spend potential stamina because the sensors do not establish the effort during the gap. The current deficit still recovers during the gap.

Between rides, potential stamina recovers with a six hour time constant:

$$
U_{\mathrm{start}}
=
100
-
\left(
100-U_{\mathrm{previous}}
\right)
\exp
\left(
-\frac{\Delta t}{21600}
\right).
$$

The `garden-stamina-v1` coefficients were fitted against 71 Garmin native cycling traces from June 3 to August 26, 2026. Every trace had power and heart rate. The rides ranged from 10 minutes to 7.1 hours. On those traces, the potential estimate had 3.3 percentage points of trace RMSE. Current stamina had 5.4 points of trace RMSE. These errors measure agreement with Garmin output for the same rider. The graph identifies every local result as an estimate on hover because this method does not reproduce Garmin’s private model and is not Wahoo data.

### tire pressure

The gear panel starts with the latest valid Garmin morning body-mass sample. The rider-weight input accepts $25$–$200\ \mathrm{kg}$ and can display or accept either kilograms or pounds while retaining kilograms as the calculation value. A manual override is stored against the current Garmin measurement date, so a newer morning measurement becomes the next default. The Cervélo defaults to $26.2\ \mathrm{lb}=11.884\ \mathrm{kg}$ and the Speedmax defaults to $26\ \mathrm{lb}=11.793\ \mathrm{kg}$, using $1\ \mathrm{lb}=0.45359237\ \mathrm{kg}$. Each bike row exposes an editable pound value, persisted locally for that bike. A custom-bike row defaults to $20\ \mathrm{lb}$ and accepts the same $10$–$80\ \mathrm{lb}$ range. The command-palette flow accepts the rider unit, rider mass, and bike mass before calculating system mass:

$$
m_{\mathrm{system}}
=
m_{\mathrm{rider}}+m_{\mathrm{bike}}.
$$

The pressure calculation reproduces the public field model used by the [SILCA Professional Tire Pressure Calculator](https://silca.cc/en-ca/pages/pro-tire-pressure-calculator?_eab=1).[^silca-pressure] Let measured tire width at axle $a$ be $w_a$ in millimetres, bead-seat diameter be $d$ in millimetres, average speed be $v$ in miles per hour, and $S$ be the surface coefficient:

| dry surface   |   $S$ |
| ------------- | ----: |
| new pavement  |   261 |
| worn pavement | 246.5 |
| poor pavement |   225 |
| cobblestone   |   199 |

The effective stiffness term and unloaded tire radius are

$$
k
=
0.5\left(m_{\mathrm{system}}-50\right)+S,
\qquad
r_{0,a}
=
w_a+\frac{d}{2}.
$$

The loaded radius, width polynomial, and centre pressure are

$$
r_{\mathrm{loaded},a}
=
r_{0,a}-
\frac{4.905}{k\left(20/w_a\right)},
$$

$$
N(w_a)
=
-226.44
\left(
-0.00006w_a^3
+0.0079w_a^2
-0.4102w_a
+12.725
\right),
$$

$$
p_{0,a}
=
\frac{N(w_a)}{r_{\mathrm{loaded},a}^2-r_{0,a}^2}.
$$

Speed contributes the linear coefficient

$$
c_v
=
0.97
+
(v-10)\frac{0.06}{23},
\qquad
10\le v\le33.
$$

Front/rear load balance is selected independently of the bike. SILCA maps each displayed split to a calibrated front and rear pressure coefficient:

| bike position          | displayed split | $c_f$ | $c_r$ |
| ---------------------- | --------------: | ----: | ----: |
| triathlon / TT / track |         $50/50$ | 1.000 | 1.000 |
| road                   |         $48/52$ | 0.985 | 1.010 |
| gravel                 |         $47/53$ | 0.975 | 1.020 |
| mountain               |     $46.5/53.5$ | 0.970 | 1.030 |

The road $48/52$ option remains the default. Tire identity and mounting setup are separate controls, both saved locally and independently editable from the command-palette result. The tire choices preserve the equipment-log names **P Zero Race SL-R** and **P Zero Race TLR SL-R**. The configured SL-R profile uses a **TPU inner tube**; the TLR profile offers either **TPU inner tube** or **Tubeless** with sealant. Choosing the SL-R profile after a tubeless setup returns the mounting choice to TPU. Existing saved TPU selections map to SL-R + TPU, while saved tubeless selections map to TLR + tubeless.

These are distinct equipment profiles for tracking the owned tires, not a claim that an omitted “TLR” proves a different manufacturer construction. Pirelli’s public SL-R page uses both the full TLR SL-R name and the shortened SL-R name.[^pirelli-sl-r] The calculator retains the two recorded identities without assigning an unverified rolling-resistance difference.

Both tire profiles use SILCA’s high-performance casing coefficient $c_t=1.00$. Applying that baseline with TPU remains a **Garden estimate**. SILCA’s current calculator has no TPU-specific option. Its explanation separates tire construction from casing efficiency, and its TPU product guidance describes rolling resistance approaching latex and tubeless, but neither publishes a TPU pressure factor.[^silca-tpu] A shared pressure coefficient does not establish equal rolling resistance: this calculator estimates starting pressure, not a tire-specific $C_{rr}$ or wattage loss. The axle recommendations are rounded to the nearest $0.5\ \mathrm{PSI}$:

$$
p_f
=
\operatorname{round}_{0.5}
\left(p_{0,f}c_vc_fc_t\right),
\qquad
p_r
=
\operatorname{round}_{0.5}
\left(p_{0,r}c_vc_rc_t\right).
$$

All wheelset choices use a $622\ \mathrm{mm}$ bead-seat diameter. The Cervélo Soloist defaults to Reserve 40|44 Road, with a $25.4\ \mathrm{mm}$ front and $25\ \mathrm{mm}$ rear internal width. The HUNT 54\_58 Aerodynamicist UD Carbon Spoke wheelset uses a $54.5\ \mathrm{mm}$ front depth, $58\ \mathrm{mm}$ rear depth, and hooked $22\ \mathrm{mm}$ internal width on both rims; Reserve 42|49 TA uses $25.4\ \mathrm{mm}$ front and $24.8\ \mathrm{mm}$ rear.[^pressure-wheels] The custom wheelset accepts independent $13$–$35\ \mathrm{mm}$ front and rear internal widths. Measured front and rear tire widths are independent $20$–$65\ \mathrm{mm}$ inputs, persisted locally, with $w_f=32\ \mathrm{mm}$ and $w_r=28\ \mathrm{mm}$ as the defaults. Advanced width mode exposes the two axle inputs, including setups such as $31/30\ \mathrm{mm}$. Turning advanced mode off uses the current front width for both tires; subsequent shared-width edits update both axles. The mode persists locally, and existing unequal saved widths open in advanced mode. Internal rim width is compatibility metadata rather than a second casing-width input, so choosing a custom internal width does not invent casing growth. Reserve publishes a $28\ \mathrm{mm}$ minimum for 40|44 Road and $29\ \mathrm{mm}$ for 42|49 TA, so the calculator warns only when a selected tire sits below its wheelset’s published range.

The [Pirelli tire-pressure tool](https://www.pirelli.com/tires/en-us/bike/pressure-tool) remains the manufacturer check for the selected Pirelli casing, tube or tubeless construction, and rim. Respect the lower of the tire and rim maximum pressures.[^pirelli-pressure] Pirelli distinguishes tubetype from tubeless and uses nominal tire width with internal rim width; it does not offer a TPU-specific selection. Its recommendations can therefore differ from this measured-width SILCA model.

Since both local casing coefficients are $1.00$, changing tire identity or switching the TLR profile between TPU and tubeless leaves PSI unchanged when measured width and every other input remain fixed. This equality is a local baseline assumption, not a claim that Pirelli recommends equal pressures. The calculator adds no universal tubeless reduction or TPU surcharge. The selected setup note explains the pressure basis; confirm the actual tire/rim combination in Pirelli’s tool and account for pinch-flat risk when using a tube.

The displayed front and rear values remain the dry SILCA baseline. Pirelli gives two road-condition offsets for fine tuning:

| riding condition | offset from dry pressure |
| ---------------- | -----------------------: |
| mixed            |       $-3\ \mathrm{PSI}$ |
| wet              |       $-8\ \mathrm{PSI}$ |

The daily gear panel also carries the nearest hourly [WeatherKit forecast](https://developer.apple.com/documentation/weatherkitrestapi/hourweatherconditions) at the centre of the most recently routed activity.[^weatherkit-pressure] It shows forecast temperature, condition, and precipitation chance beside these offsets. Forecast precipitation is context rather than a direct measurement of pavement wetness, so the panel does not silently subtract either offset from the dry result.

Pirelli states that pressure varies by approximately $3\ \mathrm{PSI}$ for each $10\ ^\circ\mathrm{C}$ difference between inflation and riding conditions, and approximately $1.5\ \mathrm{PSI}$ for each $1{,}000\ \mathrm{m}$ of altitude change. Written as magnitudes,

$$
\left|\Delta p_T\right|
\approx
3\ \mathrm{PSI}
\frac{\left|T_{\mathrm{ride}}-T_{\mathrm{inflation}}\right|}{10\ ^\circ\mathrm{C}},
$$

$$
\left|\Delta p_h\right|
\approx
1.5\ \mathrm{PSI}
\frac{\left|h_{\mathrm{ride}}-h_{\mathrm{inflation}}\right|}{1{,}000\ \mathrm{m}}.
$$

WeatherKit supplies the ride-temperature context. The current cache does not contain inflation temperature or altitude, so the panel presents these as explicit notes instead of manufacturing an adjusted number from missing inputs.

For example, the 2026-08-16 morning mass of $86.06\ \mathrm{kg}$, the $26.2\ \mathrm{lb}$ Cervélo, HUNT wheels, a $32\ \mathrm{mm}$ measured front tire, a $28\ \mathrm{mm}$ measured rear tire, worn pavement, TPU tubes, the road $48/52$ coefficients, and $19.5\ \mathrm{mph}$ produce $64\ \mathrm{PSI}$ front and $81\ \mathrm{PSI}$ rear.

[^silca-pressure]: [SILCA, “Professional Tire Pressure Calculator”](https://silca.cc/en-ca/pages/pro-tire-pressure-calculator?_eab=1). The local implementation copies the calculator’s public mass, surface, measured-width, wheel-diameter, speed, tire-casing, and load-distribution coefficients, then performs the same half-PSI rounding without making a runtime request. A controlled $100\ \mathrm{kg}$ system-weight check on 2026-08-27 reproduced SILCA’s $64/65.5\ \mathrm{PSI}$ result at $32\ \mathrm{mm}$ and $79/81\ \mathrm{PSI}$ result at $28\ \mathrm{mm}$ for worn pavement, a $622\ \mathrm{mm}$ wheel, high-performance casing, $19.5\ \mathrm{mph}$, and the road $48/52$ selection.

[^pressure-wheels]: [HUNT, “54\_58 Aerodynamicist UD Carbon Spoke Disc Wheelset”](https://us.huntbikewheels.com/products/hunt-54_58-aerodynamicist-ud-carbon-spoke-disc-wheelset), [Reserve, “40|44 Road”](https://eu.reservewheels.com/products/reserve-40-44-road-wheel), [Reserve, “42|49 TA”](https://reservewheels.com/products/reserve-42-49), and [Reserve, “Wheels Tech Info”](https://cdn.shopify.com/s/files/1/0769/6420/0723/files/Dealer_Tech_Doc_-_7-10-26.pdf?v=1783718075), official wheel dimensions and published tire-width compatibility.

[^pirelli-pressure]: [Pirelli, “Bike Tire Pressure Tool”](https://www.pirelli.com/tires/en-us/bike/pressure-tool), manufacturer guidance for matching tire construction, rim, system mass, use, and pressure limits.

[^silca-tpu]: [SILCA, “Tire Pressure Calculator Explained”](https://silca.gorgias.help/en-US/tire-pressure-calculator-explained-265377) distinguishes breakpoint pressure from construction-related limits and casing efficiency. [SILCA, “TPU Tubes”](https://silca.cc/products/tpu-tubes) discusses rolling-resistance efficiency, not a TPU-specific pressure coefficient. The calculator’s available casing categories were checked on 2026-09-07.

[^pirelli-sl-r]: [Pirelli, “P ZERO Race TLR SL-R”](https://www.pirelli.com/tyres/en-ww/bike/tyres/catalogue/p-zero-race-tlr-sl-r), the official TLR casing specification.

[^weatherkit-pressure]: [Apple, “HourWeatherConditions”](https://developer.apple.com/documentation/weatherkitrestapi/hourweatherconditions), hourly temperature, condition code, precipitation type, and precipitation probability supplied through the WeatherKit REST API.

### speed, pace, and grade adjustment

Raw speed and the human pace displays are

$$
v = \frac{d}{t},
\qquad
p_{100} = \frac{100t}{d},
\qquad
p_{\mathrm{km}} = \frac{1000t}{d},
\qquad
v_{\mathrm{km/h}} = 3.6v.
$$

For a segment with elevation change $\Delta h$ and horizontal distance $\Delta d$,

$$
g = \operatorname{clamp}\left(\frac{\Delta h}{\Delta d}, -0.30, 0.30\right).
$$

The run grade-cost factor is

$$
F_{\mathrm{run}}(g) =
\begin{cases}
1 + 8.85g + 44g^2, & g \ge 0,\\
\max\left(0.83,\ 1 + 8g + 44g^2\right), & g < 0.
\end{cases}
$$

With segment lengths $\ell_i$, run grade-adjusted speed is

$$
v_{\mathrm{GAP,run}}
=
v \times
\frac{\sum_i F_{\mathrm{run}}(g_i)\ell_i}
{\sum_i \ell_i}.
$$

When segment altitude and distance are unavailable, the same factor is applied to the activity’s overall grade. Cycling uses total climbing over distance,

$$
g_{\mathrm{overall}} = \frac{h_{\mathrm{gain}}}{d},
$$

and

$$
v_{\mathrm{GAP,bike}}
=
v \times
\min\left(
1 + 3.5\max(g_{\mathrm{overall}},0),
1.25
\right).
$$

Swimming receives no grade correction:

$$
v_{\mathrm{GAP,swim}} = v.
$$

### modeled threshold speed

Each activity receives the duration weight

$$
w_i = \left\lfloor \frac{t_i}{600} \right\rfloor + 1.
$$

The current threshold estimate for a sport is

$$
v_{\mathrm{thr}} =
\begin{cases}
Q_{0.90}\!\left(v_{\mathrm{GAP},i};w_i\right), & n \ge 4,\\
0.97\max_i v_{\mathrm{GAP},i}, & 2 \le n < 4,\\
v_{\mathrm{prior}}, & n < 2,
\end{cases}
$$

where $Q_{0.90}$ is the duration-weighted 90th percentile. The priors are

$$
v_{\mathrm{prior}} =
\begin{cases}
1.3, & \text{swim},\\
6.9, & \text{bike},\\
3.3, & \text{run}.
\end{cases}
$$

A run threshold becomes stale after 45 days without a run. The model applies the current threshold retroactively to the activities in the analytics window.

### intensity factor

The three local IF equations are

$$
\mathrm{IF}_{\mathrm{power}}
=
\frac{\mathrm{NP}}{\mathrm{FTP}},
$$

$$
\mathrm{IF}_{\mathrm{pace}}
=
\frac{v_{\mathrm{GAP}}}{v_{\mathrm{thr}}},
$$

and

$$
\mathrm{IF}_{\mathrm{HR}}
=
\frac{\mathrm{HR}_{\mathrm{avg}}}{\mathrm{LTHR}}.
$$

The activity-card fallback selects them in this order:

$$
\mathrm{IF}_{\mathrm{local}} =
\begin{cases}
\mathrm{NP}/\mathrm{FTP},
& \text{bike with device power, valid NP, and FTP},\\
v_{\mathrm{GAP}}/v_{\mathrm{thr}},
& \text{run or swim with a modeled pace IF},\\
\mathrm{HR}_{\mathrm{avg}}/\mathrm{LTHR},
& \text{valid HR and declared LTHR},\\
\varnothing,
& \text{otherwise}.
\end{cases}
$$

Treatment activities receive no calculated IF. Local IF is rounded to three decimals and remains unclamped, so a hard short activity can exceed $1$.

### exercise load and TSS-like load

Define the capped intensity used for load:

$$
I = \min(\mathrm{IF},1.15).
$$

The missing-card exercise-load fallback is

$$
L_{\mathrm{card}}
=
\operatorname{round}_{0.1}
\left(
100
\frac{t}{3600}
I^2
\right).
$$

The effective IF for this formula is

$$
\mathrm{IF}_{\mathrm{effective}}
=
\begin{cases}
\mathrm{IF}_{\mathrm{Garmin}}, & \text{when Garmin IF exists},\\
\mathrm{IF}_{\mathrm{local}}, & \text{otherwise}.
\end{cases}
$$

This has the same algebraic form as power TSS when $\mathrm{IF}=\mathrm{NP}/\mathrm{FTP}$:

$$
\mathrm{TSS}
=
\frac{t \times \mathrm{NP} \times \mathrm{IF}}
{\mathrm{FTP}\times3600}
\times 100
=
100\frac{t}{3600}\mathrm{IF}^2.
$$

The site generalizes the final form to pace-derived and heart-rate-derived IF, then caps IF at $1.15$ to stop short anomalous efforts from dominating load.

For swim, bike, and run, the longitudinal pace load is

$$
L_{\mathrm{pace},i}
=
\operatorname{round}_{0.1}
\left[
100
\frac{t_i}{3600}
\min\left(
\frac{v_{\mathrm{GAP},i}}{v_{\mathrm{thr},s(i)}},
1.15
\right)^2
\right].
$$

For strength and yoga, define average and lactate-threshold heart-rate reserve ratios

$$
r_i
=
\frac{\mathrm{HR}_{\mathrm{avg},i}-\mathrm{HR}_{\mathrm{rest},i}}
{\mathrm{HR}_{\max}-\mathrm{HR}_{\mathrm{rest},i}},
\qquad
r_{\mathrm{LT},i}
=
\frac{\mathrm{LTHR}-\mathrm{HR}_{\mathrm{rest},i}}
{\mathrm{HR}_{\max}-\mathrm{HR}_{\mathrm{rest},i}}.
$$

The normalized sex-specific Banister impulse function is

$$
B_s(r)=r e^{b_s r},
\qquad
b_s=
\begin{cases}
1.92, & s=\mathrm{M},\\
1.67, & s=\mathrm{F}.
\end{cases}
$$

The normalized heart-rate load is

$$
L_{\mathrm{HR},i}
=
\operatorname{round}_{0.1}
\left[
100
\frac{t_i}{3600}
\frac{B_s(r_i)}{B_s(r_{\mathrm{LT},i})}
\right].
$$

Normalization against the same athlete’s threshold reserve makes one hour at lactate-threshold heart rate equal to $100$ load. The calculation uses same-day Oura resting heart rate when it exists, then the median of valid resting-heart-rate observations from the preceding 28-day window. It rejects missing or physiologically inconsistent inputs.[^heart-rate-tss]

The load entering the performance management chart is

$$
L_{\mathrm{PMC},i}
=
\begin{cases}
L_{\mathrm{pace},i}, & s(i)\in\{\mathrm{swim},\mathrm{bike},\mathrm{run}\},\\
L_{\mathrm{HR},i}, & s(i)\in\{\mathrm{strength},\mathrm{yoga}\}\text{ with valid HR},\\
0, & \text{otherwise}.
\end{cases}
$$

Daily load is the sum of activity loads:

$$
L_d = \sum_{i\in d} L_{\mathrm{PMC},i}.
$$

Garmin exercise load is a provider fact. The local fallback is a TSS-like estimate and does not claim to reproduce Garmin’s EPOC model.

Absolute strength volume remains activity evidence and does not modify $L_{\mathrm{HR}}$. The existing records mix external weights, bodyweight movements, timed holds, plyometrics, bilateral and unilateral work, and exercises with different ranges of motion. A sum such as sets times reps times weight therefore lacks relative intensity, velocity, time under tension, rest, and proximity to failure. Current resistance-training research treats those omissions as material and supports session RPE as an additional internal-load measure.[^strength-tss] A future strength-load model needs a consistently recorded session RPE or reps-in-reserve field before the weight data can calibrate neuromuscular stress. Heart-rate load currently measures the systemic cardiovascular part of the session, so it can understate hard lifting performed at a modest average heart rate.

[^heart-rate-tss]: [TrainingPeaks, “Training Stress Scores Explained”](https://help.trainingpeaks.com/hc/en-us/articles/204071944-Training-Stress-Scores-TSS-Explained) defines $100$ TSS as one hour at threshold and limits heart-rate TSS accuracy when intensity fluctuates. The reserve-weighted exponential follows Banister TRIMP as restated by [Ulmer et al., “Test-retest reliability of TRIMP in collegiate ice hockey players”](https://pmc.ncbi.nlm.nih.gov/articles/PMC6561225/), then the site normalizes it against the athlete’s threshold reserve.

[^strength-tss]: [Imbach et al., “A Muscle Physiology-Based Framework for Quantifying Training Load in Resistance Exercises”](https://pmc.ncbi.nlm.nih.gov/articles/PMC11768794/) reviews the limits of sets, repetitions, volume load, time under tension, velocity, and effort-based indexes. [Day et al., “Monitoring exercise intensity during resistance training using the session RPE scale”](https://pubmed.ncbi.nlm.nih.gov/15142026/) found session RPE reliable across resistance intensities.

### performance management chart

For a time constant $\tau$ in days,

$$
K_{\tau} = 1 - e^{-1/\tau}.
$$

The site uses

$$
K_{42} = 1-e^{-1/42},
\qquad
K_7 = 1-e^{-1/7}.
$$

Chronic and acute load evolve as

$$
\mathrm{CTL}_{d+1}
=
\mathrm{CTL}_d
+
\left(L_d-\mathrm{CTL}_d\right)K_{42},
$$

$$
\mathrm{ATL}_{d+1}
=
\mathrm{ATL}_d
+
\left(L_d-\mathrm{ATL}_d\right)K_7,
$$

with form

$$
\mathrm{TSB}_d = \mathrm{CTL}_d-\mathrm{ATL}_d.
$$

The displayed value for day $d$ is the pre-update state, so load on day $d$ first appears in the state for day $d+1$. CTL and ATL start from the mean daily load over the first 14 active days:

$$
\mathrm{seed}
=
\frac{1}{n}
\sum_{d\in\text{first 14 active days}}L_d.
$$

Sport-specific CTL uses the same 42-day recurrence with sport load. Its initial seeds are

$$
\mathrm{CTL}_{0,\mathrm{swim}}=0.20\,\mathrm{seed},
\qquad
\mathrm{CTL}_{0,\mathrm{bike}}=0.50\,\mathrm{seed},
\qquad
\mathrm{CTL}_{0,\mathrm{run}}=0.30\,\mathrm{seed}.
$$

TSB zones are

$$
\operatorname{zone}(\mathrm{TSB}) =
\begin{cases}
\text{fresh}, & \mathrm{TSB}>5,\\
\text{neutral}, & -10<\mathrm{TSB}\le5,\\
\text{fatigued}, & -30<\mathrm{TSB}\le-10,\\
\text{deep}, & \mathrm{TSB}\le-30.
\end{cases}
$$

### weekly load and risk

Week-over-week ramp is

$$
r_w
=
\frac{L_w-L_{w-1}}{L_{w-1}}.
$$

For the loads of individual sessions in a week,

$$
\bar L_{\mathrm{session}}
=
\frac{1}{n}\sum_{i=1}^{n}L_i,
$$

$$
s_L
=
\sqrt{
\frac{\sum_{i=1}^{n}(L_i-\bar L_{\mathrm{session}})^2}
{n-1}
},
$$

$$
\mathrm{monotony}
=
\frac{\bar L_{\mathrm{session}}}{s_L},
\qquad
\mathrm{strain}
=
L_w\times\mathrm{monotony}.
$$

This implementation uses session loads for monotony, rather than seven daily totals.

Acute:chronic workload ratio is

$$
\mathrm{ACWR}
=
\frac{
\sum_{d=-6}^{0}L_d
}{
\frac{1}{4}\sum_{d=-27}^{0}L_d
}.
$$

It is withheld until there are at least 14 earlier active days and the 28-day load is at least $5$. The display states are

$$
\operatorname{state}(\mathrm{ACWR}) =
\begin{cases}
\text{low}, & \mathrm{ACWR}<0.8,\\
\text{ok}, & 0.8\le\mathrm{ACWR}\le1.3,\\
\text{caution}, & 1.3<\mathrm{ACWR}\le1.5,\\
\text{high}, & \mathrm{ACWR}>1.5.
\end{cases}
$$

Once three completed observed weeks exist, the weekly target baseline is

$$
B_w = \frac{L_{w-1}+L_{w-2}+L_{w-3}}{3},
$$

and the target range is

$$
L_w^{\mathrm{target}}\in[0.60B_w,\ 1.20B_w].
$$

During the first three weeks, the warmup baseline updates as

$$
B_{j+1}=B_j+0.5(L_j-B_j).
$$

### swimming

Active swim pace is

$$
p_{100}
=
100\frac{t_{\mathrm{active}}}{d}.
$$

The site accepts activity pace only in the range

$$
45 \le p_{100}\le360
\quad\text{seconds per 100 m}.
$$

Stroke rate is

$$
\mathrm{SR}
=
60\frac{N_{\mathrm{strokes}}}{t_{\mathrm{stroke}}},
$$

with a valid range of $5$ to $100$ strokes per minute. Distance per stroke is

$$
d_{\mathrm{stroke}}
=
\frac{d}{N_{\mathrm{strokes}}}.
$$

For a valid measured pool length $\ell$,

$$
\mathrm{strokes/length}_{\ell}
=
N_{\mathrm{strokes},\ell},
$$

$$
\mathrm{SWOLF}_{\ell}
=
\operatorname{round}
\left(
t_{\ell}+N_{\mathrm{strokes},\ell}
\right).
$$

Activity strokes per length and SWOLF are arithmetic means across valid measured lengths:

$$
\overline{\mathrm{strokes/length}}
=
\frac{1}{n}\sum_{\ell=1}^{n}
N_{\mathrm{strokes},\ell},
$$

$$
\overline{\mathrm{SWOLF}}
=
\frac{1}{n}\sum_{\ell=1}^{n}
\mathrm{SWOLF}_{\ell}.
$$

Kickboard lengths and lengths missing valid stroke data are excluded.

Critical swim speed from 400 m and 200 m tests is

$$
v_{\mathrm{CSS}}
=
\frac{400-200}{t_{400}-t_{200}}
=
\frac{200}{t_{400}-t_{200}},
$$

which is equivalent to the displayed pace per 100 m:

$$
p_{\mathrm{CSS},100}
=
\frac{t_{400}-t_{200}}{2}.
$$

The simple activity-card projections are

$$
t_{1.9\mathrm{k}}=19p_{100},
\qquad
t_{3.8\mathrm{k}}=38p_{100}.
$$

### cycling power and mechanics

For $M$ one-hertz power samples, the local 30-second normalized-power calculation is

$$
\bar P_{30,k}
=
\frac{1}{30}
\sum_{j=k-29}^{k}P_j,
\qquad
30\le k\le M,
$$

$$
\mathrm{NP}
=
\left(
\frac{1}{M-29}
\sum_{k=30}^{M}
\bar P_{30,k}^{\,4}
\right)^{1/4}.
$$

The local NP calculation is used inside bike decoupling. The activity card ordinarily receives weighted or normalized power from the activity providers.

When FTP is derived from a best 20-minute device-power effort,

$$
\mathrm{FTP}=0.95P_{20}.
$$

The corresponding maximal aerobic power estimate is

$$
\mathrm{MAP}=\frac{\mathrm{FTP}}{0.75},
$$

and power to weight is

$$
\mathrm{W/kg}=\frac{P}{m}.
$$

The bike VO2max estimate uses the ACSM power equation

$$
B(F,m)
=
10.8\frac{F/0.75}{m}+7,
$$

where $F$ is FTP and $m$ is body mass in kilograms. The current point uses the declared FTP. An older bike point uses $F=0.95P_{20}$.

The lab result sets the absolute baseline. Let $V_L$ be the measured lab VO2max, $m_L$ be the mass recorded by the lab, and $F_L$ be the FTP derived from that result. The calibrated bike estimate is

$$
\widehat V_{\mathrm{bike}}
=
V_L+B(F,m)-B(F_L,m_L).
$$

This makes the estimate equal the measured lab VO2max at the lab baseline. Later changes come from FTP per kilogram.

Vertical ascent rate is

$$
\mathrm{VAM}
=
3600\frac{h_{\mathrm{gain}}}{t}.
$$

The two-parameter critical-power model is

$$
P(t)=\mathrm{CP}+\frac{W'}{t}.
$$

The fit uses the best exact $180$, $420$, and $720$ second device-power windows. With

$$
x_i=\frac{1}{t_i},
\qquad
y_i=P_i,
$$

ordinary least squares gives

$$
W'
=
\frac{
\sum_i(x_i-\bar x)(y_i-\bar y)
}{
\sum_i(x_i-\bar x)^2
},
$$

$$
\mathrm{CP}
=
\bar y-W'\bar x.
$$

Fit error is

$$
\mathrm{RMSE}
=
\sqrt{
\frac{1}{n}
\sum_i
\left(
P_i-\mathrm{CP}-\frac{W'}{t_i}
\right)^2
},
$$

$$
\mathrm{nRMSE}
=
\frac{\mathrm{RMSE}}{\bar P}.
$$

The fit is rejected when $\mathrm{nRMSE}>0.05$, $\mathrm{CP}\le0$, $W'\le0$, or $\mathrm{CP}$ is not below every anchor power. Confidence is medium only when all three anchors are independent non-overlapping efforts; otherwise it is provisional.

Steady-state wheel power is modeled as

$$
P_{\mathrm{wheel}}
=
\frac{1}{2}\rho C_dA v^3
+
C_{rr}mgv.
$$

Solving for drag area with drivetrain efficiency $\eta$ gives

$$
C_dA
=
\frac{
2\left(P_{\mathrm{crank}}\eta-C_{rr}mgv\right)
}{
\rho v^3
}.
$$

Gear ratio is

$$
R=\frac{N_{\mathrm{chainring}}}{N_{\mathrm{cog}}}.
$$

The drivetrain visual models

$$
L_{\mathrm{total}}
=
L_{\mathrm{aligned}}(N_{\mathrm{chainring}},R)
+
L_{\mathrm{cross-chain}},
$$

$$
\eta_{\mathrm{drivetrain}}
=
100\times
\frac{250-L_{\mathrm{total}}}{250}.
$$

Aligned loss is linear in ratio, with its slope and intercept linearly interpolated between the CeramicSpeed 39-tooth and 53-tooth fits. Cross-chain loss is piecewise-linear in cog offset from the aligned cog.

### efficiency, decoupling, cadence, and stride

Bike efficiency factor is

$$
\mathrm{EF}_{\mathrm{bike}}
=
\frac{\mathrm{NP}}{\mathrm{HR}_{\mathrm{avg}}}
\quad
\left[
\frac{\mathrm{W}}{\mathrm{bpm}}
\right].
$$

Run and swim efficiency factor are

$$
\mathrm{EF}_{\mathrm{run/swim}}
=
\frac{60v_{\mathrm{GAP}}}{\mathrm{HR}_{\mathrm{avg}}}
\quad
\left[
\frac{\mathrm{m/min}}{\mathrm{bpm}}
\right].
$$

For activities of at least 20 minutes with approximately one-hertz streams, aerobic decoupling is

$$
\mathrm{decoupling}
=
100
\frac{\mathrm{EF}_{1}-\mathrm{EF}_{2}}
{\mathrm{EF}_{1}}.
$$

The halves split the sample stream at its midpoint. Bike half-efficiency uses locally calculated NP divided by mean HR. Run half-efficiency uses distance-derived speed divided by mean HR. Swimming has no decoupling value.

When native running stride is absent, estimated stride length is

$$
\ell_{\mathrm{stride}}
=
\frac{60v}{c},
$$

where $c$ is total steps per minute. Strava’s run cadence is doubled before use because its activity field reports cycles per minute.

### trend and calibration

The 28-day calibration average first computes duration-weighted grade-adjusted speed:

$$
\bar v_{\mathrm{GAP}}
=
\frac{
\sum_i v_{\mathrm{GAP},i}t_i
}{
\sum_i t_i
}.
$$

It then converts that speed to seconds per 100 m for swimming, seconds per kilometre for running, or kilometres per hour for cycling.

The signed faster percentage is

$$
\Delta_{\mathrm{bike}}
=
100\frac{x_{\mathrm{current}}-x_{\mathrm{previous}}}
{x_{\mathrm{previous}}},
$$

$$
\Delta_{\mathrm{run/swim}}
=
100\frac{x_{\mathrm{previous}}-x_{\mathrm{current}}}
{x_{\mathrm{previous}}}.
$$

For a dense sport history, the pace trend uses exponentially weighted OLS. With activity age $a_i$ in days,

$$
w_i=2^{-a_i/28},
$$

$$
\bar x_w
=
\frac{\sum_iw_ix_i}{\sum_iw_i},
\qquad
\bar y_w
=
\frac{\sum_iw_iy_i}{\sum_iw_i},
$$

$$
b
=
\frac{
\sum_iw_i(x_i-\bar x_w)(y_i-\bar y_w)
}{
\sum_iw_i(x_i-\bar x_w)^2
},
\qquad
a=\bar y_w-b\bar x_w.
$$

The 14-day forecast damps the daily slope by $\phi=0.94$:

$$
\hat y_d
=
\eta_{\mathrm{now}}
+
b\sum_{j=1}^{d}\phi^j,
\qquad
1\le d\le14.
$$

The weighted effective sample size and residual scale are

$$
n_{\mathrm{eff}}
=
\frac{\left(\sum_iw_i\right)^2}
{\sum_iw_i^2},
$$

$$
s_e
=
\sqrt{
\frac{\sum_iw_i(y_i-a-bx_i)^2}
{\sum_iw_i}
}
\sqrt{
\frac{n_{\mathrm{eff}}}
{\max(1,n_{\mathrm{eff}}-2)}
}.
$$

The forecast half-width is

$$
h_d
=
1.28s_e
\sqrt{
\frac{1}{n_{\mathrm{eff}}}
+
\frac{(x_{\mathrm{now}}+d-\bar x_w)^2}
{\sum_iw_i(x_i-\bar x_w)^2}
}.
$$

This OLS path requires at least six activities, a span of at least 21 days, and no gap over 14 days. Sparser non-stale histories use an exponentially weighted level:

$$
\ell_i
=
\ell_{i-1}
+
0.3(y_i-\ell_{i-1}).
$$

### activity and race projections

The activity-card running projection uses the Riegel equation

$$
T_2
=
T_1
\left(
\frac{D_2}{D_1}
\right)^{1.06}
$$

for the next standard distance above the completed activity.

The race model defines a one-hour threshold reference distance

$$
D_{\mathrm{ref}}
=
\frac{3600v_{\mathrm{thr}}}{1000}
\quad\text{kilometres}.
$$

For race-leg distance $D_s$,

$$
T_s
=
3600
\left(
\frac{D_s}{D_{\mathrm{ref},s}}
\right)^{k_s},
$$

with

$$
k_{\mathrm{swim}}=1.03,
\qquad
k_{\mathrm{bike}}=1.05,
\qquad
k_{\mathrm{run}}=1.06.
$$

The run split receives the brick multiplier

$$
f_{\mathrm{brick}}
=
\begin{cases}
1.02, & \text{sprint},\\
1.04, & \text{olympic},\\
1.07, & \text{70.3},\\
1.12, & \text{ironman}.
\end{cases}
$$

Total predicted time is

$$
T_{\mathrm{race}}
=
T_{\mathrm{swim}}
+
T_{\mathrm{bike}}
+
f_{\mathrm{brick}}T_{\mathrm{run}}
+
300
+
300.
$$

Trend-derived velocity ratios are clamped to $\pm8\%$ before they modify threshold speed. The uncertainty half-fraction is clamped to $12\%$.

For each sport,

$$
\mathrm{coverage}_s
=
\operatorname{clamp}
\left(
\frac{D_{\mathrm{longest},s}}{D_{\mathrm{race},s}},
0,
1
\right).
$$

The threshold-recency gate is

$$
g_s =
\begin{cases}
1.0, & \text{stale days}\le21,\\
0.6, & 21<\text{stale days}\le42,\\
0.3, & \text{stale days}>42.
\end{cases}
$$

Fitness readiness is

$$
F
=
\operatorname{clamp}
\left(
\frac{\mathrm{CTL}}{\mathrm{CTL}_{\mathrm{race}}},
0,
1
\right),
$$

with race references

$$
\mathrm{CTL}_{\mathrm{race}}
=
\begin{cases}
35, & \text{sprint},\\
50, & \text{olympic},\\
70, & \text{70.3},\\
90, & \text{ironman}.
\end{cases}
$$

The readiness score is

$$
\mathrm{readiness}
=
\operatorname{round}
\left[
100
\left(
0.45F
+
0.55
\frac{1}{3}
\sum_{s\in\{\mathrm{swim,bike,run}\}}
\mathrm{coverage}_s g_s
\right)
\right].
$$

The binding leg is the sport with the smallest $\mathrm{coverage}_s g_s$.

### heart rate and aerobic capacity

When declared or observed maximum heart rate is unavailable, Tanaka’s estimate is

$$
\mathrm{HR}_{\max}
=
208-0.7\,\mathrm{age}.
$$

Daniels’ oxygen-cost equation uses speed $v_{\min}$ in metres per minute:

$$
\mathrm{VO}_{2,\mathrm{Daniels}}
=
-4.6
+
0.182258v_{\min}
+
0.000104v_{\min}^2.
$$

The heart-rate-ratio estimate is

$$
\mathrm{VO}_{2\max}
=
15.3
\frac{\mathrm{HR}_{\max}}
{\mathrm{HR}_{\mathrm{rest}}}.
$$

The cycling estimate from MAP is

$$
\mathrm{VO}_{2\max}
=
10.8
\frac{\mathrm{MAP}}{m}
+
7.
$$

For the run-stream regression, the model fits

$$
\mathrm{HR}=a+bv,
$$

then estimates

$$
v_{\mathrm{VO2}}
=
\operatorname{clamp}
\left(
\frac{\mathrm{HR}_{\max}-a}{b},
v_{\max,\mathrm{observed}},
1.4v_{\max,\mathrm{observed}}
\right)
$$

before applying Daniels’ equation. When that regression is unavailable, each qualifying run uses heart-rate reserve fraction

$$
f_{\mathrm{HRR}}
=
\frac{
\mathrm{HR}_{\mathrm{avg}}-\mathrm{HR}_{\mathrm{rest}}
}{
\mathrm{HR}_{\max}-\mathrm{HR}_{\mathrm{rest}}
},
$$

and

$$
\mathrm{VO}_{2\max,i}
=
\frac{
\mathrm{VO}_{2,\mathrm{Daniels}}(v_{\mathrm{GAP},i})
}{
f_{\mathrm{HRR},i}
}.
$$

Only runs with $f_{\mathrm{HRR}}>0.4$ enter that fallback mean.

### FTP hypothesis from VO2max

Let source VO2max be $V$ in $\mathrm{mL\,kg^{-1}\,min^{-1}}$, body mass be $m$, cross-modal discount be $d$, threshold fraction be $q=0.85$, and gross metabolic efficiency be $\eta=0.21$. The efficiency term is a literature prior. Pedal smoothness and torque effectiveness describe force application, so they remain evidence beside the hypothesis rather than inputs to $\eta$.

An explicitly cycling-specific Garmin VO2max uses $d=0$. A treadmill value uses $d=0.08$. A Garmin generic value also retains $d=0.08$ because the cached field does not establish its source sport.

Absolute oxygen use is

$$
\dot V_{O_2}
=
\frac{Vm}{1000}
\quad
\left[
\mathrm{L/min}
\right].
$$

The cycling and threshold estimates are

$$
\dot V_{O_2,c}
=
\dot V_{O_2}(1-d),
$$

$$
\dot V_{O_2,\mathrm{thr}}
=
q\dot V_{O_2,c}.
$$

Using $20.9\ \mathrm{kJ}$ per litre of oxygen,

$$
P_{\mathrm{met}}
=
\dot V_{O_2,\mathrm{thr}}
\frac{20.9\times1000}{60},
$$

$$
\mathrm{FTP}_{\mathrm{eff}}
=
\eta P_{\mathrm{met}}.
$$

The independent ACSM path is

$$
\mathrm{MAP}_{\mathrm{ACSM}}
=
\frac{
\max\left(0,V(1-d)-7\right)m
}{
1.8\times6.12
},
$$

$$
\mathrm{FTP}_{\mathrm{ACSM}}
=
0.75\,\mathrm{MAP}_{\mathrm{ACSM}}.
$$

The hypothesis is the nearest 10 W to the mean of both paths:

$$
\mathrm{FTP}_{\mathrm{hyp}}
=
\operatorname{round}_{10}
\left(
\frac{
\mathrm{FTP}_{\mathrm{eff}}
+
\mathrm{FTP}_{\mathrm{ACSM}}
}{2}
\right).
$$

Its displayed low and high bounds are the unrounded mean minus and plus $25$ W, each rounded to the nearest $5$ W. The panel also reports six-week estimated critical power, modeled 60-minute power, declared FTP, independent-effort count, and activity-level median pedal dynamics. A ride enters the pedal summary only when each left and right pedal-smoothness and torque-effectiveness series has at least $80\%$ valid samples.

### body composition and energy

The standard BMI definition is

$$
\mathrm{BMI}
=
\frac{m}{h^2}.
$$

The displayed BMI comes from the Garmin scale rather than a local recomputation. Fat-free mass and FFMI are locally derived when scale weight and body-fat percentage are present:

$$
\mathrm{FFM}
=
m
\left(
1-\frac{\mathrm{body\ fat\ \%}}{100}
\right),
$$

$$
\mathrm{FFMI}
=
\frac{\mathrm{FFM}}{h^2}.
$$

Katch-McArdle BMR is

$$
\mathrm{BMR}_{\mathrm{KM}}
=
370+21.6\,\mathrm{FFM}_{\mathrm{kg}}.
$$

The male Mifflin-St Jeor goal-weight estimate is

$$
\mathrm{BMR}_{\mathrm{MSJ}}
=
10m_{\mathrm{kg}}
+
6.25h_{\mathrm{cm}}
-
5\,\mathrm{age}
+
5.
$$

Weight trend is the OLS slope of daily weight against day, multiplied by seven:

$$
\Delta m_{\mathrm{week}}
=
7
\frac{
\sum_i(t_i-\bar t)(m_i-\bar m)
}{
\sum_i(t_i-\bar t)^2
}.
$$

When trend is moving toward the target, goal ETA is

$$
\mathrm{weeks}_{\mathrm{goal}}
=
\min
\left(
104,
\left\lceil
\left|
\frac{m_{\mathrm{current}}-m_{\mathrm{goal}}}
{\Delta m_{\mathrm{week}}}
\right|
\right\rceil
\right).
$$

### recovery

Oura supplies sleep duration, stages, scores, HRV, resting heart rate, and the first choice for average respiration. Garmin adds measurements for the same wake-up date through `sleepMetrics`: its respiration average and range, average and minimum Pulse Ox, Body Battery at bedtime and waking, native overnight Body Battery change, average sleep stress, and restless moments. If Oura has no respiration measurement, the displayed average uses Garmin and records `respirationSource: garmin`. Garmin fields retain their own source and do not change Oura’s stages or the sleep-debt calculation.

These summaries appear in daily analytics, the `day` feed records, and both server-rendered and interactive sleep views. A Garmin-only night can show its measurements while sleep duration and stages remain unavailable. Missing measurements stay null; zero stress or Body Battery change remains a measured zero. Body Battery endpoints require a sample within five minutes of the corresponding sleep boundary. The Garmin sync refreshes recent nights, and `quartz/scripts/sync-garmin.ts --sleep-only` refreshes only nightly measurements using the existing date-range environment variables.

For samples $x_1,\ldots,x_n$,

$$
\bar x=\frac{1}{n}\sum_{i=1}^{n}x_i,
\qquad
s_x
=
\sqrt{
\frac{
\sum_{i=1}^{n}(x_i-\bar x)^2
}{n-1}
}.
$$

HRV uses $y_i=\ln(\mathrm{HRV}_i)$. Its baseline is the latest 28-day window with at least 14 samples, and its acute window is seven days with at least three samples:

$$
z_{\mathrm{HRV}}
=
\frac{
\bar y_{7}-\bar y_{28}
}{
s_{y,28}
}.
$$

The displayed HRV baseline converts the log mean back:

$$
\mathrm{HRV}_{\mathrm{baseline}}
=
e^{\bar y_{28}}.
$$

RHR uses the same windows without the log transform:

$$
z_{\mathrm{RHR}}
=
\frac{
\overline{\mathrm{RHR}}_{7}
-
\overline{\mathrm{RHR}}_{28}
}{
s_{\mathrm{RHR},28}
}.
$$

Fourteen-night sleep debt uses a seven-hour target:

$$
\mathrm{debt}_{14}
=
\sum_{i=1}^{14}
\operatorname{clamp}
\left(
7\ \mathrm{h}-\mathrm{sleep}_i,
0,
7\ \mathrm{h}
\right).
$$

Readiness baseline is a 28-day arithmetic mean with at least seven samples. Sleep baseline is a 28-day median with at least seven samples.

### heat acclimatisation

For duration-weighted observations,

$$
\bar x
=
\frac{
\sum_i x_i\Delta t_i
}{
\sum_i\Delta t_i
}.
$$

CORE heat is counted when heat strain index is at least $3$. When CORE data is unavailable, ambient heat is counted when temperature exceeds $22^\circ\mathrm{C}$. Runs prefer CORE FIT, then CORE app; rides prefer CORE app, then CORE FIT. WeatherKit and Strava device temperature supply the ambient fallback.

Daily heat dose is

$$
D_d
=
\operatorname{clamp}
\left(
\frac{\mathrm{hot\ workout\ minutes}_d}{60}
+
\frac{\min(8,\mathrm{recorded\ sauna\ HTL}_d)}{10},
0,
1
\right).
$$

Recorded sauna HTL contributes to this Garden proxy on the [CORE 0–10 scale](https://help.corebodytemp.com/hc/en-us/articles/34476316782866-Heat-Training-Load), with a [passive contribution capped at 8 HTL per day](https://help.corebodytemp.com/hc/en-us/articles/34606310515090-CORE-s-Passive-Heat-Training-Load-Calculator). The linear HTL conversion is a Garden heuristic. The resulting percentage is separate from CORE’s Heat Adaptation Score. Sessions without HTL remain visible with no inferred dose; an explicit HTL of zero remains zero. Sauna duration, room temperature, humidity, and cooldown are retained as manual context. They do not supply measured HSI or enter workout-temperature averages.

Heat credits evolve as

$$
C_{d+1}
=
\begin{cases}
\min(14,C_d+D_d), & D_d>0,\\
0.975C_d, & D_d=0\ \text{and more than 3 days since heat},\\
C_d, & \text{otherwise}.
\end{cases}
$$

Acclimatisation is

$$
\mathrm{heat\ acclimatisation\ \%}
=
100\frac{C_d}{14}.
$$

Forty-two-day observation coverage is

$$
\mathrm{coverage\ \%}
=
100
\operatorname{clamp}
\left(
\frac{
\mathrm{observed\ eligible\ seconds}
}{
\mathrm{all\ eligible\ seconds}
},
0,
1
\right).
$$

Routed bike and run activities and recorded sauna sessions are eligible for this heat model. Sauna minutes with recorded HTL count toward observation coverage; missing HTL reduces coverage. Linked sauna recordings contribute once and retain zero sport-training load.

<!-- training plan end -->

<!-- training plan start
meta: supertri toronto 2026
distance: olympic
date: 2026-07-26
target: sub-3
author: gpt-5.5-pro-xhigh,claude-fable-5[1m]
-->

The plan now uses five measured constraints:

- The VO2 test on 2026-06-25 put VO2max at 47.8 ml/kg/min at 88.9 kg, vVO2max at 15.0 km/h, HR at VO2max at 180 bpm, and peak observed HR at 182 bpm. VT1 was detected at 145 bpm and 10.5 km/h. VT2 was not detected, so do not invent precise threshold work above VT1.
- The useful fat-burning lane is boring: HR 121-142 bpm, roughly 8-10.5 km/h on the treadmill test. Most aerobic run minutes live there. Easy bike days use the same HR ceiling and power stays subordinate to HR drift.
- FTP is now a 230 W hypothesis with a 210-260 W realistic band, derived from running VO2max. The math crosses treadmill to bike and invents the missing VT2, so it sets power vocabulary, not training law.
- DEXA on 2026-06-25 measured 197.6 lb total mass, 54.2 lb fat, 135.7 lb lean, 7.8 lb bone mineral content, 27.4% total-mass fat, 28.5% tissue fat, 1.24 lb VAT, and 1695 kcal/day RMR.
- Holding fat-free mass at 143.5 lb, 14% body fat implies about 166.9 lb total mass and 23.4 lb fat. The chart weight target is 160.0 lb, which implies about 16.5 lb fat and 10.3% total-mass fat if fat-free mass stays flat. Race day is 31 days away, so the race block targets 4-6 lb loss, not the full cut.
- Current analytics have Olympic prediction at about 3:02 before transitions, binding leg run, CTL 69, ATL 93, and TSB -24 on 2026-06-25. The test day counts as hard. The next 48 hours buy freshness before adding more intensity.

TriDot has some pretty good drill for [swimming](https://support.tridot.com/hc/en-us/sections/201539593-Swim) here, and some form with drill which is recommended to follow to improve your stroke and general techniques.

For eating, make sure to eat:

- Fatty Fish (salmon)
- Dark leafy greens
- Fermented foods

## intensity anchors

Lab HR zones:

| lane     | HR          | speed        | use                                      |
| -------- | ----------- | ------------ | ---------------------------------------- |
| warm     | <121 bpm    | <8 km/h      | warmup, cooldown, recovery               |
| fat burn | 121-142 bpm | 8-10.5 km/h  | most easy run volume and aerobic doubles |
| VT1 cap  | 143-145 bpm | 10.5 km/h    | upper cap for ordinary endurance         |
| hard     | 146-170 bpm | 10.5-14 km/h | tempo, race-specific work                |
| VO2      | 171+ bpm    | 14-15 km/h   | short intervals only                     |

Swim:

- Easy: 2:30-2:45/100 m, nasal-calm between repeats, no thrashing.
- CSS work: 2:18-2:24/100 m. Current modeled swim threshold is about 2:21/100 m, so the job is durability near threshold, not fantasy-pacing 1.5 km at sprint effort.[^css]
- Fast: 1:55-2:08/100 m on 25-50 m repeats only.
- Race: 34:30-35:30 for 1.5 km. Faster is loot, but sub-3 mostly comes from bike-run execution now.

Bike, using FTP 230 W as a VO2-derived hypothesis until a bike test proves otherwise:

- Easy: 125-160 W, HR under 142 if the day is for fat loss, under 145 if the day is endurance.
- Tempo: 170-195 W, smooth cadence, no spikes.
- Race power: 185-205 W, with 80-90 rpm and no heroic surges over 270 W.
- VO2: 245-275 W for 3-5 minute repeats. One bike VO2 session per week is enough while Runna has hard running.
- Race: 40 km in 82-85 minutes, then run. A faster bike that detonates the first 3 km of the run is fake profit.

Run:

- Easy and fat-burning: HR 121-142 bpm, often 5:45-7:30/km. Stay there even when the watch begs for content.
- Aerobic ceiling: 145 bpm. If a normal run crosses this without intent, slow down.
- Runna intervals and tempo: the one hard run stimulus for the week. Bike that day is easy or absent.
- Long run: keep it aerobic. Fuel any run over 75 minutes with 30-60 g carbs/h.
- Race: open the 10 km off the bike at 5:05-5:10/km for 2 km, then settle toward 4:50-5:00/km if HR and legs agree. The old 45-minute run target is an A+ day, not the pacing plan.

Recovery gates:

- If readiness is under 70 for two straight days, HRV z is below -1, RHR is 5 bpm over baseline, or TSB is below -25, replace the next hard session with easy technique or 45-60 minutes Z2.
- The VO2 test is a hard session. On 06-25 and 06-26, stack recovery and easy aerobic work before chasing another interval badge.
- Fuel every ride over 75 minutes. Use 30-60 g carbs/h for 75-150 minutes and practice 60-90 g/h for race rehearsals.[^carb]

## weekly build

| week    |          dates |        load ceiling | Runna   | swim       | bike      | key proof                                          |
| ------- | -------------: | ------------------: | ------- | ---------- | --------- | -------------------------------------------------- |
| reset   | 06-25 to 06-28 |             420-500 | 15 mi   | 3.5-4.5 km | 2.5-3.5 h | absorb VO2 test, keep 400s controlled, long run ok |
| build 2 | 06-29 to 07-05 |             650-720 | 20.0 mi | 6.0-7.0 km | 6.0-7.5 h | tempo 2 miles, 7.5 mi long run, 40 km rehearsal    |
| peak    | 07-06 to 07-12 |             700-760 | 21.8 mi | 7.0-8.0 km | 7.0-8.0 h | 1 km repeats, 9 mi progressive long run, brick run |
| taper 1 | 07-13 to 07-19 |             480-540 | 17.6 mi | 5.0-5.5 km | 4.0-5.0 h | drop set, 6 mi long run, race-kit swim plus bike   |
| race    | 07-20 to 07-26 | 260-340 before race | 13.0 mi | 2.5-3.5 km | 2.0-3.0 h | race-pace half miles, 10 km race, arrive sharp     |

The load ceiling is total weekly load. The Runna mileage is the run lane, so extra jogs do not exist. If the app moves a run, keep the weekly mileage inside the same band and keep at least 24 hours between hard run work and bike race-power work. Most minutes stay easy, then taper volume drops while short intensity stays alive.[^seiler][^taper]

## calendar

| date  | Runna                                                  | swim                                                             | bike                                                        |
| ----- | ------------------------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------- |
| 06-25 | VO2 and DEXA day; Loading Up only if already scheduled | off                                                              | no bike quality; 20-30 min spin only if legs need movement  |
| 06-26 | 4.5 mi Easy Run, HR 121-142                            | S3, 1600-1800 m, no strain                                       | off                                                         |
| 06-27 | 400 m Repeats, 4 mi; Stretch & Stability 1, 25 min     | off                                                              | 45-60 min Z2 only if reps stay controlled                   |
| 06-28 | 6.5 mi Long Run, HR mostly under 145                   | 1500-1800 m continuous or open water                             | 30-45 min recovery spin                                     |
| 06-29 | 4 mi Easy Run                                          | S1, 2000 m                                                       | off                                                         |
| 06-30 | Strength Supersets, 55-65 min                          | off                                                              | B1, 70-75 min with 5x3 min at 245-265 W                     |
| 07-01 | Tempo 2 Miles, 4.5 mi                                  | S2, 2200-2400 m with 4-5x400 m                                   | 30-45 min easy                                              |
| 07-02 | Lower Body Strength Session, 55-65 min                 | off                                                              | B2, 80-90 min with 2x20 min at 180-195 W                    |
| 07-03 | 4 mi Easy Run, HR 121-142                              | S3, 1800-2000 m                                                  | off                                                         |
| 07-04 | Stretch & Stability 2, 25 min                          | off                                                              | B4, 40 km rehearsal at 185-200 W, full fueling              |
| 07-05 | 7.5 mi Long Run                                        | 1600-1800 m easy continuous                                      | 45 min recovery spin if HRV is normal                       |
| 07-06 | 3.25 mi Easy Run                                       | S1, 2200 m                                                       | off                                                         |
| 07-07 | Full Body Endurance Session, 55-65 min                 | off                                                              | B1, 75-80 min with 5x4 min at 245-265 W                     |
| 07-08 | 1 km Repeats, 4.5 mi                                   | S2, 2400-2600 m with 3x600 m                                     | 30-45 min easy                                              |
| 07-09 | Legs & Core Endurance, 55-65 min                       | off                                                              | B2, 90-100 min with 3x15 min at 185-200 W                   |
| 07-10 | 5 mi Brick Run                                         | S3, 1800-2000 m                                                  | 45-60 min race-power primer before brick if legs are normal |
| 07-11 | Stretch & Stability 3, 25 min                          | off or 1000 m easy                                               | B5, 75-90 min at race-power blocks, full fueling            |
| 07-12 | 9 mi Progressive Long Run                              | 1800-2000 m continuous, sighting every 6-8 strokes if open water | 45-60 min easy                                              |
| 07-13 | 3.25 mi Easy Run                                       | S1, 1800 m                                                       | off                                                         |
| 07-14 | Ticking Over, 55-65 min                                | off                                                              | B1, 55-60 min with 4x3 min at 245-260 W                     |
| 07-15 | Drop Set, 3.4 mi                                       | S2, 2000-2200 m with 3x400 m                                     | 30-40 min easy                                              |
| 07-16 | Light(er) Work, 55-65 min                              | off                                                              | B2, 65-75 min with 2x12 min at 185-200 W                    |
| 07-17 | 5 mi Easy Run                                          | S3, 1500-1600 m, stop while stroke still looks good              | off                                                         |
| 07-18 | Stretch & Stability 4, 25 min                          | 1200-1500 m race-kit swim, controlled                            | B4, 25-35 km at 185-200 W, full fueling                     |
| 07-19 | 6 mi Long Run                                          | off or 1000 m easy                                               | 30-45 min recovery spin                                     |
| 07-20 | off                                                    | S1, 1400-1600 m                                                  | off                                                         |
| 07-21 | Hard Work Is Done, 55-65 min                           | off                                                              | B1, 45-50 min with 3x3 min at 240-255 W                     |
| 07-22 | Race Pace Practice Half Miles, 3.5 mi                  | S3, 1200-1500 m with 8x50 m fast                                 | 30-40 min easy                                              |
| 07-23 | off                                                    | off                                                              | B2, 40-45 min with 3x5 min at 185-200 W                     |
| 07-24 | 3.25 mi Easy Run                                       | 1000-1200 m easy, 4x50 m fast                                    | off                                                         |
| 07-25 | Stretch & Stability 5, 25 min                          | 10-15 min water feel if available                                | 20 min spin, 3x30 s openers                                 |
| 07-26 | race: 10 km, first 2 km at 5:05-5:10/km                | race: 1.5 km, settle in the first 300 m                          | race: 40 km, cap first 10 min at 180 W, then 185-205 W      |

## session library

S1, technique plus CSS:

- 300 m easy.
- 8x50 m drill/swim by 25 m, 15-20 s rest.
- Main set starts at 6x100 m at 2:20-2:24/100 m and progresses to 10x100 m at 2:18-2:22/100 m.
- 4x50 m fast at 1:55-2:08/100m, full control.
- 200 m easy.

S2, endurance:

- 400 m easy.
- 3-5x400 m at 2:20-2:28/100 m, 45-60 s rest.
- Progress one rep each build week before adding speed.
- 200 m easy.

S3, speed and stroke:

- 300 m easy.
- 12-20x50 m alternating fast/easy. Fast is 1:55-2:08/100m; easy is 2:30/100m or slower.
- 4x100 m pull or strong-form swim.
- 200 m easy.

B1, VO2:

- 15 min warmup.
- 5-6x3-5 min at 245-275 W, equal easy recovery. HR can touch 171+ late, but the first reps should feel controlled.
- 10-15 min cooldown.
- End the session if power falls below target for two consecutive reps.

B2, tempo and race durability:

- 15 min warmup.
- Progress from 3x10 min at 170-185 W to 3x15 min at 185-200 W and then 3x12 min at 195-210 W.
- Keep cadence 80-90 rpm.
- Cool down until HR is boring again.

B3, long aerobic:

- 2:00-3:00 at 125-160 W.
- Add 20-30 min at 170-185 W only when readiness is normal and HR stays under 145.
- Eat 60-90 g carbs/h and drink to thirst plus sodium in heat.

B4, race rehearsal:

- 10 min easy.
- 40 km at 185-200 W, target 29-30 km/h, no power spikes over 270 W unless traffic forces it.
- Hold aero position whenever safe.
- Practice race bottle, carbs, and the first 10 minutes of restraint.
- If this takes more than 86 minutes in normal wind, the sub-3 budget moves to transitions plus run discipline.

B5, race-power durability:

- 15 min warmup.
- 3x15 min at 185-205 W, 5 min easy between.
- Finish with 10 min easy. If legs are sore at minute 45, choose 185 W before 215 W.

Runna R1, easy run:

- Hold conversation pace and keep cadence light.
- If HR drifts above easy, slow down before adding distance.

Runna R2, intervals or tempo:

- Warm up until stride mechanics feel normal.
- Hit the prescribed Runna work and end the session while form still exists.
- Bike quality moves at least 24 hours away unless the bike is explicitly easy.

Runna R3, long run:

- Start slower than ego wants.
- Fuel 30-60 g carbs/h once the run passes 75 minutes.
- Last 10 minutes can progress only if the next day is not a bike-quality day.

## body composition and food

The DEXA math:

- Current: 197.6 lb total, 54.2 lb fat, 135.7 lb lean, 7.8 lb bone, 143.5 lb fat-free mass.
- Current total-mass body fat: 27.4%. Reported tissue-fat view: 28.5%.
- Chart weight target: 160.0 lb.
- 14% body-fat checkpoint with fat-free mass preserved: about 166.9 lb total mass and 23.4 lb fat.
- 160 lb target math with fat-free mass preserved: about 16.5 lb fat, about 10.3% total-mass fat, and about 37.6 lb fat loss from DEXA.
- 160 lb goal-weight BMR: about 1780 kcal/day by Mifflin-St Jeor at 188 cm and age 25; lean-mass-preserved Katch-McArdle gives about 1775 kcal/day because fat-free mass stays 143.5 lb. Treat the DEXA RMR of 1695 kcal/day as the measured anchor until the next lab check.[^bmr]
- Race-block checkpoint: 191-193 lb by 07-26 if readiness, sleep, and workout quality stay green. Do not chase a lower number during taper.

The cut rate:

- Use 0.5-0.7% body mass per week, about 1.0-1.4 lb/week right now. That puts the 14% checkpoint about 22-31 weeks away and the 160 lb chart target about 27-38 weeks away.[^weightloss]
- Keep the deficit mostly on rest/easy days. Hard sessions, long runs, race rehearsals, and race week are maintenance or near-maintenance.
- If sleep score drops under 75 for two nights, RHR stays 5 bpm over baseline, or intervals flatten, remove the deficit before removing training quality.

Protein:

- Daily floor: 160 g.
- Normal target: 180 g.
- High-deficit or strength-heavy days: 190-200 g.
- Split into 4 feedings of 35-50 g. Put one feeding within 2 hours after hard sessions. Whey is allowed because logistics are real.[^protein]

Carbs:

- Easy/rest day: 180-250 g, mostly around training and dinner.
- Normal training day: 250-350 g.
- Hard, brick, long, or rehearsal day: 350-500 g. Fuel the work, then let the deficit come from the rest of the day.
- During training: 30-60 g/h for 75-150 minutes, 60-90 g/h for race rehearsals and race-day bike. Do not use chronic low-carb during this block; evidence for performance gain from aggressive CHO restriction is weak and it can compromise interval quality.[^cho-period]

Fat and calories:

- Keep fat around 60-80 g/day. Do not drop below 50 g/day repeatedly.
- Use measured total expenditure when available: intake equals total calories minus 300-500 kcal on easy/moderate days, and about total calories on hard/long days.
- Practical floor while training: about 2300 kcal/day. Lower than that is how the plan gets a stupid little stress fracture side quest.
- RMR is 1695 kcal/day, so the useful lever is weekly average intake, not starving a single day.

Race fueling:

- Breakfast 3-4 hours before: 1-2 g/kg carbs, low fiber, familiar sodium.
- Bike: 60-80 g carbs/h, 500-800 mg sodium/h if hot, fluid to thirst.
- Run: 30-45 g carbs/h if tolerated. The run is short enough that stomach calm beats maximal gel cosplay.

## race track

![[https://strava-embeds.com/route/3459615466775941042?style=standard&amp;fullWidth=true&amp;clubId=1459904&amp;fromEmbed=true#ns=94460b8c-1ff3-4475-b2f1-a5beeb3d47e5&amp;hostOrigin=https%3A%2F%2Fsupertri.com&amp;hostPath=%2Ftoronto-triathlon%2Folympic%2F&amp;hostTitle=Supertri+Toronto+Olympic+Triathlon]]

![[https://strava-embeds.com/route/3459617551271887794?style=standard&terrain=2d&fromEmbed=false&token=OwRoXABZDeHZP91TK9B4XxoSSByBkgNjmdYpHtlWpJ8#ns=13297610-0d59-4ddc-9ef8-f5ed70feaf4b&hostOrigin=https%3A%2F%2Fwww.strava.com&hostPath=%2Froutes%2F3459617551271887794&hostTitle=Supertri+Toronto+2026+-+Bike+Olympic+Distance+%7C+25.1+mi+Cycling+Route+on+Strava&mapHash=10.36/43.6761/-79.3919]]

![[https://strava-embeds.com/route/3459617422094043424?style=standard&fromEmbed=false&token=7oowr8Y5XzoFgrL8zbP1hHuoGOThXsJi576pA4e5-E4#ns=e51ce2df-4f5a-4afc-9fcc-f53f52c01bce&hostOrigin=https%3A%2F%2Fwww.strava.com&hostPath=%2Froutes%2F3459617422094043424&hostTitle=Supertri+Toronto+2026+-+Run+Olympic+Distance+%7C+6.3+mi+Running+Route+on+Strava]]

[^seiler]: Seiler, “What is best practice for training intensity and duration distribution in endurance athletes?”, 2010. <https://pubmed.ncbi.nlm.nih.gov/20861519/>

[^css]: Dekerle et al., “Validity and reliability of critical speed, critical stroke rate and anaerobic swimming capacity in relation to front crawl swimming performances”, 2002. <https://pubmed.ncbi.nlm.nih.gov/11842355/>

[^taper]: Bosquet et al., “Effects of tapering on performance: a meta-analysis”, 2007. <https://pubmed.ncbi.nlm.nih.gov/17762369/> See also Mujika and Padilla, “Scientific bases for precompetition tapering strategies”, 2003. <https://pubmed.ncbi.nlm.nih.gov/12840640/>

[^carb]: Burke et al., “Carbohydrates for training and competition”, 2011. <https://pubmed.ncbi.nlm.nih.gov/21660838/>

[^weightloss]: Manore, “Weight Management for Athletes and Active Individuals”, 2015. <https://pmc.ncbi.nlm.nih.gov/articles/PMC4672016/> See also Mountjoy et al., “2023 International Olympic Committee’s consensus statement on Relative Energy Deficiency in Sport”, 2023. <https://bjsm.bmj.com/content/57/17/1073>

[^protein]: Jager et al., “International Society of Sports Nutrition Position Stand: protein and exercise”, 2017. <https://pubmed.ncbi.nlm.nih.gov/28642676/> See also Helms et al., “A systematic review of dietary protein during caloric restriction in resistance trained lean athletes”, 2014. <https://pubmed.ncbi.nlm.nih.gov/24092765/>

[^cho-period]: Gejl and Nybo, “Performance effects of periodized carbohydrate restriction in endurance trained athletes: a systematic review and meta-analysis”, 2021. <https://link.springer.com/article/10.1186/s12970-021-00435-3>

[^bmr]: Mifflin et al., “A new predictive equation for resting energy expenditure in healthy individuals”, 1990. <https://pubmed.ncbi.nlm.nih.gov/2305711/> The Katch-McArdle style lean-mass estimate uses $BMR = 370 + 21.6 \times FFM_{kg}$.

<!-- training plan end -->

