rides.md

← Profile

Fueling method

Current method · updated

Current calculation used by /profile/, /rides/, and /ride/ for carbohydrate demand and intake targets.

Inputs

  • CPET XML substrate data: VO2, RER, power, VT1, and VT2.
  • Completed ride power: actual watts from the FIT power trace.
  • Planned ride power: structured workout steps, or estimated average power from IF × FTP.
  • Gut cap: user-set gutCapGh, default 90 g/h. Editable on /profile/.
  • FTP: for planned rides, FTP comes from /profile/. It is needed for planned average-power estimates and workout steps expressed as %FTP. Completed ride estimated oxidation does not need FTP because the FIT file already contains power samples.

CPET data is parsed locally. The substrate curve stays in the browser unless the user copies markdown or requests ride feedback.

Substrate conversion

Each breath has VO2 (L/min) and RER. Frayn equations convert those to substrate oxidation:

CHO (g/min) = VO2 × (4.55 × RER − 3.21)
Fat (g/min) = 1.67 × VO2 × (1 − RER)

Values are smoothed with a 30-second trailing window, then binned by 10 W. The result is a substrate curve: CHO and fat grams per hour at each ramp power.

Ramp-test handling

A ramp test is not a steady-state oxidation test. rides.md uses the saved ramp substrate curve directly. No VT1-based low-intensity dampening is applied:

cho_gh = lookupCurve(substrateCurve, power_w).choGh

Demand is the raw ramp substrate curve at the matched power. Practical intake limits are handled later by gut cap and duration, not by hidden demand correction factors. The reason for this change is listed in Model history.

Oxidation is not intake

The CHO and fat numbers from the curve are estimated substrate oxidation from gas exchange (Frayn). They describe how carb-expensive a power is metabolically. They are not a fueling ledger and not an intake target.

Why the gap matters:

  • Muscle and liver glycogen carry a large share of the CHO cost on shorter rides; you don’t need to ingest gram-for-gram what you oxidize.
  • Exogenous carbohydrate absorption is bounded by gut transporters (SGLT1 / GLUT5), not by oxidation rate.
  • Fat oxidation is endogenous — you do not eat against the fat-g/h column.
  • Intensity, duration, and prior glycogen state shape the intake decision more than the instantaneous oxidation number.

The intake target below is a separate, duration-driven heuristic. It uses only duration and your gut cap — not oxidation.

Target calculation

The intake target is set by duration, clamped by your gut, then capped at your oxidation. Carbohydrate intake during exercise is limited by gut absorption (SGLT1 / GLUT5), not by how much carbohydrate the ride burns, so demand doesn't drive the number. But on an easy ride where you oxidize less than the ladder says — say under 60 g/h — the target drops to that. You're never told to eat more than you burn.

DurationTarget
< 30 minNo fuel needed
30 min – 1 h30 g/h
1 – 2 h60 g/h
> 2 – 3 h90 g/h
≥ 3 hgut cap (90 default, up to 120 trained)

This follows Jeukendrup's recommendations chart (mysportscience) for sessions where performance matters and you burn more than ~500 kcal/h — which covers most structured training on a power meter. Carb stays glucose-only up to 60 g/h; above that you add fructose (multiple transportable carbohydrates) to raise delivery. Every tier is clamped to your gut cap, so a lower cap pulls the ladder down. 120 g/h has become common practice but isn't strongly evidenced as necessary, so the default cap is 90 g/h — raise it only if you've trained your gut. When a lab curve is loaded the target is then capped at your modeled carbohydrate oxidation: on an easy ride you won't be told to eat more than you burn, so the number drops below the ladder (Jeukendrup adjusts down when intensity is low). The same cap runs on completed-ride coach feedback.

Two things deliberately left out, because the research says they don't change the number: body mass (peak exogenous carb oxidation is independent of body weight — the limit is gut transporters, not size, so the target is absolute g/h, never g/kg) and carb format (drinks, gels, chews, and bars deliver the same oxidation at a given g/h). What does matter is the individual gut, which is why the cap is yours to set.

One exception to "duration sets the number": easy / Z2 sessions. The card flags a ride as fat-focus when it spends almost no time above your aerobic threshold — under ~5 minutes above VT1 across the whole session. That's measured from the workout's structured steps as absolute minutes over VT1, not the average, so a polarized session like 30/30 intervals plus Fatmax blocks is correctly left alone even though its average intensity looks easy. When there are no steps to read, it falls back to average IF against your VT1 (or ~0.75 with no lab test). If the goal of that ride is fat adaptation, you want the opposite: fuel lighter than the target so the session pushes fat metabolism. Train low means less carbohydrate during the ride, not fasted. Fasted training carries its own risks, especially around energy availability and hormonal health for women, so rides.md doesn't suggest it. It also doesn't lower the number for you, because it can't tell a deliberate train-low session from easy volume you need to recover from. So it flags the ride with a short "Z2 / fat-focus" note and leaves the call to you, with the fat-oxidation figure as the relevant physiology.

Example

Duration8 h
Gut cap60 g/h
Ladder tier≥3 h → 60 g/h (gut cap)
Modeled oxidation46 g/h
Target46 g/h · 368 g total (capped to oxidation)

The ≥3 h tier would give the gut cap (60 g/h), but the curve shows you only oxidize 46 g/h on this easy ride — no point eating more than you burn — so the target drops to 46 g/h. On a harder ride where oxidation runs above the tier, the gut-cap number stands and oxidation is just context.

Algorithm

Retrospective /ride/ estimated oxidation

Requires: substrate curve + FIT power trace
FTP: not required for estimated oxidation

for each powered sample:
  met = lookupCurve(substrateCurve, power_w)
  cho_g += met.choGh × capped_dt_h

demand = cho_g / active_power_hours


Planned /rides/ intake target

if duration_h < 0.5 → 0                 # <30 min: no fuel needed
if duration_h < 1   → min(30, gut_cap)  # 30 min–1h
if duration_h <= 2  → min(60, gut_cap)  # 1–2h
if duration_h < 3   → min(90, gut_cap)  # >2–3h
else                 → gut_cap           # ≥3h (default 90, trained up to 120)

then, if a curve is loaded:
target = min(target, modeled_cho_oxidation_gh)   # never eat more than you burn

total_carbs = target × duration_h
needs only: planned duration + gut cap


Planned /rides/ oxidation context (optional, needs substrate curve)

1. Structured workout with absolute-watt steps
   Requires: substrate curve + step watts
   FTP: not required
   ox = duration-weighted CHO from substrate curve

2. Structured workout with %FTP steps
   Requires: substrate curve + profile FTP
   step_watts = step_percent × FTP
   ox = duration-weighted CHO from substrate curve

3. Planned ride without usable steps
   Requires: substrate curve + profile FTP
   IF = sqrt(planned_load / (100 × duration_h))
   if planned_load is missing, IF defaults to 0.70
   avg_power = IF × FTP
   ox = lookupCurve(substrateCurve, avg_power).choGh

4. No substrate curve, or no FTP where FTP is required
   ox = not shown (target still shows)

The intake target needs only planned duration and your gut cap — it shows for every planned ride. Oxidation context is layered on when a substrate curve is saved: absolute-watt steps, %FTP steps, or average power from IF × FTP. Profile FTP is required for the last two. Completed ride estimated oxidation uses FIT watts directly and is not estimated without a substrate curve.

Exclusions

  • Sodium, caffeine, protein. Carbs only.
  • Glucose vs glucose+fructose blends. Gut cap is one number.
  • Heat, altitude, fatigue state, sleep. Rider as a black box.
  • Unstructured climb-specific pacing. Per-step integration works for structured intervals, not unknown route events.
  • Actual food intake. rides.md estimates cost and target, not compliance.

Model history

  • — replaced the demand-driven target (min(demand, gut cap) plus a 70%-gut-cap floor) with the duration ladder, gut-capped, then capped at oxidation. The number is set by how long you ride and what your gut absorbs; oxidation only pulls it down on easy rides, never up. Body mass and carb format don't change it.
    • Sources: Jeukendrup recommendations chart (mysportscience); Jeukendrup, Curr Opin Clin Nutr Metab Care 13(4):452–7, 2010 (intake independent of body mass); Pfeiffer 2010 & Hearris 2022 (drink/gel/chew/bar oxidize the same).
  • — removed the old VT1-based ×0.85 / ×0.90 / ×0.95 low-intensity dampening; rides.md uses the raw ramp substrate curve. The rule assumed ramp tests overstate low-intensity carb, but ramp and step oxidation track closely (Teso et al.) and any correction is individual and protocol-dependent (Iannetta et al.).

Import CPET XML on /profile/ to enable substrate-curve fueling estimates.