Under the hood · 06

Warmups and fatigue

How Logex models tiredness and warmth: capacity lost per hard set, the exercise-order fatigue multiplier, dynamic warm-up trims and learning from your own logs. The multiplier scales expectations only. PRs never scale.

12 min read

  • fatigue
  • warmups
  • exercise order
  • multiplier
  • learning

The third chest exercise of a session cannot match the same lift done first. If an app ignores that, it grades your incline press against a fresh-muscle number, calls it a bad day, and sets the next target too high. Logex models two kinds of tiredness and one kind of warmth, and uses them to adjust expectations, never records.

In plain terms. Every hard set you do costs some strength, a little within the same exercise and more for related muscles done earlier in the workout. Logex turns that into an expected-performance multiplier m (for example 0.91 means “expect about 9% less”). Targets, quality scores and most badges are compared on a fair basis. PRs and stored maxes use the raw number, always: a record is still a record.

Three models, one idea

Model Question Where it lives
Set fatigue How much capacity do I lose from one hard set to the next in the same exercise? utils/setFatigue.js
Session fatigue How much do earlier exercises lower what this one can do? utils/sessionFatigue.js
Session warmth How warm are these muscles already, so how many warm-ups do I need? utils/warmupEngine.js

Session fatigue is switched on by the ENABLE_FATIGUE_ADJUSTMENT flag, which is true at release head.

Set fatigue: capacity lost per hard set

Within an exercise, each hard set removes a share of your e1RM capacity. The default depends on the effort of the set:

lossPerSet(RPE) = 0.04 + (RPE − 7) × 0.02        RPE clamped to 7 to 10

That is 4% at RPE 7, 6% at RPE 8, 8% at RPE 9 and 10% at RPE 10. The code calls this a product heuristic. If you have logged RPE on both sets of at least 4 consecutive pairs (where the second set is not heavier, so a ramp-up does not read as recovery), Logex learns your own loss as the median measured drop, clamped to 3% to 15%. The pair estimate uses RPE-aware Epley, weight × (1 + (reps + min(3, 10 − RPE)) ÷ 30).

The planner uses the loss to write realistic sequences. After a set of reps at a given RPE, capacity is (1 + (reps + RIR) ÷ 30) × (1 − loss):

  • Straight sets keep the load and lose reps, reps_next = floor(30 × (capacity − 1) − RIR). If that falls below a floor (the target reps minus 3, at least 1), the load comes down instead.
  • Back-offs and added sets keep the reps and lighten the load by the loss.

Worked example

Strength plan: target 5 reps at RPE 8, top set 200 lb, two warm-ups, three work sets (top set plus two back-offs). The generated rows:

Set Load Reps Share of top
Warm-up 110 8 55%
Warm-up 140 6 70%
Top set 200 5 100%
Back-off 175 7 88%
Back-off 165 7 83%

The first back-off is about 12% lighter, and its reps are what that load allows after one hard set, kept between the target and two more.

Session fatigue: the exercise-order multiplier

The ingredients

For a target exercise T, look at every exercise done before it in the workout.

Overlap. How much of T’s work did the earlier exercise P also load? Both exercises list canonical muscles with a role (see Weekly volume). For fatigue each muscle is weighted by its role: direct 1, indirect 0.5 and, unlike muscle volume, stabilizers 0.25, because heavy squats do tire the lower back an RDL needs next even though they do not grow it.

f[m]          = 1 direct · 0.5 indirect · 0.25 stabilizer
overlap(P, T) = Σ_m min(f_P[m], f_T[m]) ÷ Σ_m f_T[m]

If either exercise resolves to no muscles, the fallback is 0.8 when they share a primary muscle group, 0.3 when T’s primary group is among the groups P trains, otherwise 0. The same exercise logged twice overlaps fully (1).

Effort of each hard set: 1.0 at RPE 8 or higher, 0.8 at RPE 7 or no RPE, 0.5 at RPE 6 or lower. “Hard set” means a complete set that is not a warm-up, dropset or assisted set.

Recovery weights how much of P’s fatigue is still present when T starts:

recovery = 0.6 + 0.4 × e^(−minutes ÷ 12)      minutes from P's last hard set to T's first set (or now)
         = 0.9 ^ (exercises between P and T)  when either side has no timestamps

The 0.6 floor means hard work on the same muscle never fully clears within one session. At 0, 12, 30 and 60 minutes the weight is 1.00, 0.75, 0.63 and 0.60.

The formula

load = Σ_P Σ_hard sets  overlap × effort × recovery
S    = number of hard sets earlier in the session (any muscle)

m = (1 − 0.18 × scale × (1 − e^(−load ÷ 6))) × (1 − min(0.04, 0.004 × max(0, S − 10)))
scale = 0.85 for compound lifts, 1.1 for isolation

The first factor is local fatigue, a saturating curve that levels off near 18% (times the scale). The second is whole-body fatigue: nothing for the first 10 hard sets, then 0.4% per extra set, capped at 4%. The priors are localMax 0.18, localK 6, systemic cap 0.04, 0.004 per set after 10.

Fatigue load Compound m Isolation m
1 0.977 0.970
2 0.957 0.944
4 0.926 0.904
6 0.903 0.875
8 0.887 0.854
12 0.868 0.829
20 0.852 0.809

The first exercise, or any with no overlap and 10 or fewer earlier hard sets, has m = 1.

Worked example

Session: barbell bench 4 sets (RPE 8), standing overhead press 3 sets (RPE 8), then dumbbell bench press. No timestamps.

  • Overlap bench to dumbbell bench is 1.00: in the catalogue both have the chest as the direct mover with the same synergists and stabilizers. Overhead press to dumbbell bench is 0.69 (front delts, all three triceps heads, and the upper chest, serratus and rotator cuff it stabilizes with).
  • Bench: 4 sets × effort 1 × overlap 1.00 × recovery 0.9 (one exercise in between) = 3.60.
  • Overhead press: 3 × 1 × 0.69 × 1.0 (adjacent) = 2.06.
  • load = 5.66. S = 7, so no whole-body term. Dumbbell bench is a compound, so scale = 0.85.
  • m = 1 − 0.18 × 0.85 × (1 − e^(−5.66 ÷ 6)) = 1 − 0.153 × 0.611 = 0.907.

You should expect about 9% less than fresh. Now add timestamps: six bench sets finishing 15 minutes before your first dumbbell set. The recovery weight is 0.6 + 0.4 × e^(−15 ÷ 12) = 0.715, load = 6 × 1.00 × 0.715 = 4.29, and m = 0.922. Without timestamps, an adjacent exercise counts in full (recovery 1.0), so the same six sets give load 6.0 and m = 0.903. Resting longer helps, as it should.

“Position” is not the same as load

The label “3rd chest movement” counts only same-movement lifts: those where the earlier lift covers at least 50% of the target’s direct muscles (primaryOverlap). Overhead press adds to the load above, but does not train the chest directly, so dumbbell bench is “2nd chest movement”, not 3rd. Because every exercise now names its muscles from the same canonical list, flat bench before incline press does count: the incline’s direct movers are both chest regions, and flat bench covers the lower one fully and the upper one at half, an overlap of 0.75. Compound or isolation comes from the catalogue’s own label first, then from the name.

Where m is used

m is saved per exercise (workout_exercises.fatigue_multiplier) along with the fatigue load and a warmth value. These are local-only derived columns, because everything they come from already syncs, and a backfill fills them for rows pulled from the server.

  • Targets: planner anchors use history made “fresh-equivalent” times today’s m. The Live Set Coach uses last × m_now ÷ m_last (see Live Set Coach).
  • Quality: the baseline is multiplied by m. Band baselines are built from fresh-equivalent session e1RMs (raw ÷ m).
  • Badges: Progress, Best in 6 Weeks, Within Reach, Grinder, Comeback and the near-miss gap compare raw ÷ m on both sides.
  • Plateaus: the Progress hub compares e1rmAdjusted = e1RM ÷ m, so moving a lift later in the session is not flagged as a stall.
  • Never adjusted: PR, Plateau Breaker, Rep PR, Ghost PR, Volume PR, stored maxes and the charts. Records stay raw.

Fresh-equivalent best. max(stored best, max over sessions of e1RM ÷ m). Only sessions with m < 1 can lift it. This stops double-counting: if your best press was always done second after bench, you should not be discounted twice when today is also second.

Your own response

The 0.18 prior comes from studies of exercise order, which find that later exercises lose roughly 5% to 20% depending on prior same-muscle volume. These are app heuristics, not values those studies validated. Logex learns your own localMax from exercises you have done both fresh and after related work:

observed    = e1RM ÷ median(fresh e1RMs of that lift within ±42 days)      fresh = load < 0.5
estimate_i  = (1 − observed) ÷ (scale × (1 − e^(−load ÷ 6)))               pre-fatigued = load ≥ 1
localMax    = clamp( w × median(estimates) + (1 − w) × 0.18 , 0.05, 0.30 ),  w = n ÷ (n + 8)

With 4 comparisons whose median estimate is 0.24, w = 4 ÷ 12, so your localMax becomes 0.333 × 0.24 + 0.667 × 0.18 = 0.20. With no data it returns the priors unchanged, and it takes about 8 comparisons to move halfway. The result is cached for 10 minutes.

Routine ordering

When you plan a routine, Logex treats every planned set as a hard RPE-8 set (3 sets if unspecified) and computes m for each line. A line gets an order warning if its multiplier is below 0.92 and it is a compound or priority lift. The suggested order minimises

cost = Σ priority × (1 − m)      priority = 1 compound · 0.35 isolation · +0.75 if a focus lift

by sorting on priority and then keeping any adjacent swap that lowers the cost (lines you pinned stay put). Example: curl, incline press, bench in that order becomes bench, curl, incline.

Session warmth: dynamic warm-ups

Fatigue lingers. Warmth does not: it fades in about 20 minutes and has no floor.

c       = overlap × min(1, effectiveSets ÷ 3) × e^(−minutes ÷ 20)        (overlap must be at least 0.2)
warmth  = 1 − Π(1 − c)      combined across earlier exercises
effectiveSets = hard sets + 0.5 × warm-up sets
without timestamps: recency = 0.85 per exercise in between

Example: you finish 4 hard bench sets, then do incline barbell press 5 minutes later. Overlap is 0.89, 4 sets saturate (4 ÷ 3 is capped at 1), recency is e^(−5 ÷ 20) = 0.78, so warmth = 0.69, enough to drop the lightest warm-up step. If you waited 25 minutes it would be 0.25.

Today’s trim

Warmth Change
0.75 or more Collapse to one feeder set: the heaviest step, 75% to 85% of the work weight, at most 3 reps
0.40 or more Drop the lightest step (two steps if the ramp has 4 or more)
Under 0.15, first lift of the day, and 21 or more days since this lift Add one lighter step, unless your learned response prefers fewer warm-ups

So a 3-step ramp with warmth 0.8 becomes 1 feeder set, a 4-step ramp at warmth 0.5 becomes 2 steps, and a cold lift after a 4-week break gets a 4th step. The trim applies once, never after you edit a warm-up or log a set, never after you tap “Use full warmup”, and is never saved as your next default.

Learning your warm-ups

  • Bodyweight and assisted lifts start with no warm-up rows, because there is no load ramp to climb. A saved default, a routine or your own edit can still add them.
  • Fixed loads vs ramp. Lifters often warm up with the same loads whatever the top set (95 × 10, 135 × 8). A load counts as fixed if it appears in at least 60% of recent sessions (and at least 2) and sits below 85% of the usual top set; loads within 2.5 lb are the same load. Fixed loads keep their absolute weight and median reps. Only steps above them stay relative to the top set.
  • Default ramp when nothing is learned: step i of n is 0.4 + 0.45 × (i + 1) ÷ (n + 1) of the work weight, with max(2, 8 − 2i) reps. For 3 warm-ups: 51% × 8, 63% × 6, 74% × 4.
  • Plate-friendly loads. Planned warm-ups snap to a load you have warmed up with before, then (barbell) to a clean plate load such as 95, 135, 185 or 225 lb (or 40, 60, 80, 100, 120 or 140 kg), within ±7.5%. Each is above the one before and below the first work set.
  • Learning the dose needs at least 4 comparable sessions in 180 days (sessions that started already warm, warmth 0.40 or more, are excluded). Each session’s warm-up dose is bucketed by count (0 to 1, 2, 3, 4+) and by how heavy the top warm-up was (under 70%, up to 85%, heavier). The outcome is the best RPE-aware, fresh-equivalent e1RM of the first two work sets relative to the median of the previous three sessions. A bucket scores its mean outcome times n ÷ (n + 3). A different dose than your habit is recommended only if it has n of at least 2 and beats the habit by at least 1% (about 2% raw at n = 3). It sits above history and below your saved defaults, routines and edits.

Limits and edge cases

  • Heuristics, honestly labelled. The 0.18 and 6-set saturation shape, the 20-minute warmth constant, the 0.4 and 0.75 thresholds and the bucket scoring are tuned conservatively and shrunk toward “do what you did” until your own logs say otherwise.
  • It needs sets to count. A prior exercise with no hard sets contributes nothing, and a missing muscle list falls back to coarse overlap.
  • Muscle roles matter for overlap and “same movement”. Custom exercises take part through the muscles you keep when you create them.
  • Planning ahead is an assumption. A routine has nothing lifted yet, so it assumes every planned set is a hard RPE-8 set with no timestamps.

Source

Written from these files in the Logex app (release head). Constants and formulas above are copied from them.

  • utils/sessionFatigue.js
  • utils/setFatigue.js
  • utils/warmupEngine.js
  • services/SessionFatigueService.js
  • utils/stimulusModel.js
  • constants/muscles.js
  • utils/exercisePlanning.js
  • constants/featureFlags.js
  • docs/WARMUP_AND_FATIGUE.md
  • docs/QUALITY_AND_BADGE_FORMULAS.md
  • docs/adaptive-exercise-planning.md