Skip to content

fix: realistic Cameron, altitude, splits and heat models - #94

Closed
0jonjo wants to merge 4 commits into
mainfrom
fix/physiology-f1a
Closed

0jonjo wants to merge 4 commits into
mainfrom
fix/physiology-f1a

Conversation

@0jonjo

@0jonjo 0jonjo commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Phase F1a of the physiology review. Four models returned unrealistic numbers; this PR fixes them. Method names, signatures and return shapes don't change, and neither does the structure of environmental_factors.yml. Only the returned values move, except that Cameron now rejects distances above 100 km. No version bump: all phases ship together in one later release, so the CHANGELOG entry sits under [Unreleased].

Changes

  1. Cameron (cameron_predictor.rb): the old a + b·e^(−d/c) constants were not Cameron's formula. They gave 10K 42:00 → marathon 2:57:34, which is more optimistic than Riegel (3:13:12). This PR replaces them with Dave Cameron's velocity-ratio model, f(d) = 13.49681 − 0.000030363·d + 835.7114 / d^0.7905, T2 = T1·(D2/D1)·f(D1)/f(D2), with d in metres. Sources: Cameron's own metric version posted to the t-and-f list (2001-06-20, https://www.mail-archive.com/t-and-f@lists.uoregon.edu/msg11312.html) and the had2know.org Cameron calculator, which states metres and gives a worked example (3.5 mi in 51:30 → 5 mi in ~75:08). That example is pinned in a test. CAMERON_A/B/C are replaced by named constants for the new formula. Domain guard: f(d) crosses zero near 445 km, so a new constant CAMERON_MAX_DISTANCE_KM = 100.0 makes every Cameron method raise ArgumentError when either distance is above 100 km. The model is fitted up to the marathon, and 100 km keeps '100k' usable.
  2. Altitude (YAML): threshold_meters 914.4 → 300, plus a new 300: 0.0 point, so the penalty ramps up instead of jumping at 915 m. The NCAA points are kept exactly. New extrapolated points 3000: 7.92, 3500: 9.97, 4000: 12.2 come from the quadratic fit p = 0.3647·x² + 1.9482·x, x = km − 0.3, and the YAML documents them as extrapolation. The 0: 0.0 point is removed.
  3. Splits: negative/positive pacing changes from ±4% to ±1% per half. A 3:00:00 marathon now splits 1:30:54 + 1:29:06.
  4. Heat: new points 35: 8.7 and 40: 10.9 continue the 25→30 °C slope of 0.44 points/°C and are marked as extrapolation. duration_factor and the ideal range are untouched.

Compatibility checks

  • No public method removed or renamed, and no required parameter added. duration_factor is unchanged, and so are the race_splits signature and strategy names.
  • YAML keys are unchanged (altitude.threshold_meters, altitude.data_points, heat.ideal_range_celsius, heat.data_points). Points were only added, apart from the redundant 0: 0.0. A test pins this structure.
  • Breaking (CHANGELOG › Breaking): the public constants CameronPredictor::CAMERON_A/B/C are removed, and Cameron raises above 100 km.

Baseline diff (gem_realismo/baseline.rb, main → this branch)

Only the intended sections changed. Age grade, VO2max/zones, Riegel, VDOT, even splits and heat ≤ 30 °C are identical.

Predictions, Cameron column only (Riegel | Cameron | VDOT):
5k 20:00 -> 10k:       42:26   -> 41:39
5k 20:00 -> half:      1:29:42 -> 1:31:51
5k 20:00 -> marathon:  2:59:25 -> 3:15:11   (Riegel 3:11:49)
5k 30:00 -> marathon:  4:29:08 -> 4:52:47   (Riegel 4:47:43)
10k 42:00 -> half:     1:28:47 -> 1:32:36
10k 42:00 -> marathon: 2:57:34 -> 3:16:46   (Riegel 3:13:12)
10k 60:00 -> marathon: 4:13:40 -> 4:41:06   (Riegel 4:36:00)
half 1:45 -> marathon: 3:30:00 -> 3:43:07   (Riegel 3:38:55)
(other Cameron rows move by under 1 min, e.g. 10k 42:00 -> 5k 19:47 -> 20:09)

Altitude: 500 m 0.0 -> 0.46 | 914 m 0.0 -> 1.41 | 2800 m 5.9 -> 7.2 | 3600 m 5.9 -> 10.42
          (0, 915, 1200, 1500, 2000, 2438 m unchanged)

Heat (20/45/60/120/180/240/300 min):
35 C: 3.25/4.88/6.5/13.0/19.5/29.25/29.25 -> 4.35/6.53/8.7/17.4/26.1/39.15/39.15
40 C: 3.25/4.88/6.5/13.0/19.5/29.25/29.25 -> 5.45/8.18/10.9/21.8/32.7/49.05/49.05

Splits:
negative: 1:33:36 | 26:00  -> 1:30:54 | 25:15
positive: 1:26:24 | 24:00  -> 1:29:06 | 24:45

Verification

  • bundle exec rake test: 534 runs, 0 failures.
  • bundle exec rubocop: 44 files, no offenses.
  • The README and docstring examples are recomputed. test_documented_examples checks the single-line ones.

Draft: do not merge before review. Merging to main publishes to RubyGems.

0jonjo added 4 commits October 2, 2026 06:20
- Cameron: replace the made-up exponential constants with Dave Cameron's
  velocity-ratio model, f(d) = 13.49681 - 0.000030363*d + 835.7114/d^0.7905
  (d in metres). The old constants were far more optimistic than Riegel for
  the marathon; the real model is more conservative.
- Altitude: threshold 914.4 m -> 300 m with a linear ramp to the first NCAA
  point (no more 0% -> 1.41% jump at 915 m) and quadratic-fit extrapolation
  to 4000 m instead of a 5.90% cap from 2438 m.
- Splits: negative/positive strategies use +-1% pace per half instead of +-4%.
- Heat: extrapolate 35 C (8.7%) and 40 C (10.9%) from the 25->30 C slope.
All review phases will ship together in a single later release.
Cameron's f(d) crosses zero near 445 km, so long distances produced negative
or absurd times. Add CAMERON_MAX_DISTANCE_KM (100 km, keeps '100k' usable)
and raise ArgumentError when either distance exceeds it.

Also strengthen the altitude continuity and YAML structure tests, and move
the changelog entry under Unreleased, marking the removed CAMERON_A/B/C
constants and the new range limit as breaking.
@0jonjo 0jonjo changed the title fix: realistic Cameron, altitude, splits and heat (v1.19.0) fix: realistic Cameron, altitude, splits and heat models Oct 2, 2026
@0jonjo

0jonjo commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #97 (released as 2.0.0).

@0jonjo 0jonjo closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant