docs pare interface documentation further

Core interfaces now retain only caller-visible invariants, ordering, and edge cases. Remove repeated Heavy Duty rationale and design narration; project steering and the local doctrine reference remain the source for broader context.

Commit
28218c7a132d3a7d9e98239261a0eefe0732951d
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/core/evidence.mli
index 6c6417c4..c6ce4735 100644..100644
@@ -1,34 +1,18 @@
1 Removed: (** The record: what was actually done, at every scale {!Prescription} plans
2 Removed: one.
1 Added: (** Recorded stimuli, workouts, and history. Recording never judges. *)
3 2
4 Removed: The only source of evidence in the system. It records; it does not interpret
5 Removed: — what the evidence means is {!Progression}'s business, and a stimulus that
6 Removed: should not have been performed is still recorded. *)
7 Removed:
8 Removed: (** One stimulus: a single drive to muscular failure, and the record of what it
9 Removed: took.
10 Removed:
11 Removed: The unit of work. HD1 prescribes one set per exercise, so there is no set
12 Removed: count. Reaching failure is not recorded because it is not optional — a
13 Removed: movement here always went to failure. *)
3 Added: (** One performed drive to failure. *)
14 4 module Stimulus : sig
15 Removed: (** A means of continuing past positive failure. HD1 treats these as
16 Removed: occasional: used on every exercise they lead straight to overtraining, and
17 Removed: forced reps and negatives both need a spotter. *)
18 5 type extension = Forced_reps | Negatives | Rest_pause | Static_hold
19 6
20 Removed: (** How the drive ended. *)
21 7 type outcome =
22 Removed: | Positive_failure (** Unable to complete another rep unaided. *)
8 Added: | Positive_failure
23 9 | Beyond_failure of extension * extension list
24 Removed: (** In the order applied — HD1 stacks negatives after forced reps. Split
25 Removed: so that "beyond failure by no means at all" cannot be written. *)
10 Added: (** Nonempty, in application order. *)
26 11
27 12 val extensions_of_outcome : outcome -> extension list
28 13 val pp_extension : Format.formatter -> extension -> unit
29 14
30 Removed: (** Preparation for a stimulus: enough to bring blood to the muscles and
31 Removed: joints, no more. Carries no outcome, so a warm-up cannot reach failure. *)
15 Added: (** Preparation; it has no failure outcome. *)
32 16 module Warm_up : sig
33 17 type t
34 18
@@ -41,7 +25,6 @@
41 25 val pp : Format.formatter -> t -> unit
42 26 end
43 27
44 Removed: (** One movement driven to failure within a stimulus. *)
45 28 module Movement : sig
46 29 type t
47 30
@@ -60,8 +43,6 @@
60 43 val pp : Format.formatter -> t -> unit
61 44 end
62 45
63 Removed: (** How the stimulus was delivered — the performed counterpart of
64 Removed: {!Prescription.Stimulus.delivery}. *)
65 46 type delivery =
66 47 | Single of Movement.t
67 48 | Pre_exhaust of { isolation : Movement.t; compound : Movement.t }
@@ -74,50 +55,32 @@
74 55 val pp_error : Format.formatter -> error -> unit
75 56
76 57 val make : ?warm_ups:Warm_up.t list -> delivery -> (t, error) result
77 Removed: (** A [Pre_exhaust] must genuinely pre-exhaust; two unrelated movements are
78 Removed: two stimuli, not one. This refuses a mislabelled pairing, not an honest
79 Removed: record. *)
58 Added: (** Rejects a mislabelled pre-exhaust pairing. *)
80 59
81 60 val delivery : t -> delivery
82 61
83 62 val movements : t -> Movement.t list
84 Removed: (** In performance order; isolation first for a pre-exhaust. *)
63 Added: (** Performance order. *)
85 64
86 65 val warm_ups : t -> Warm_up.t list
87 66 val exercises : t -> Exercise.t list
88 Removed:
89 67 val extensions : t -> extension list
90 Removed: (** Everything that carried this stimulus past failure, across its movements.
91 Removed: *)
92 Removed:
93 68 val is_extended : t -> bool
94 69 val pp : Format.formatter -> t -> unit
95 70 end
96 71
97 Removed: (** One workout being performed, or one already performed: the prescription it
98 Removed: answers to, when it ran, and the stimuli it delivered.
99 Removed:
100 Removed: Where plan meets record. It admits only stimuli its prescription calls for —
101 Removed: the delivery shape must agree, and each movement must be prescribed or an
102 Removed: allowed substitute — so a workout cannot drift into something else and still
103 Removed: claim to be the prescribed one. *)
72 Added: (** A performed or in-progress prescribed workout. *)
104 73 module Workout : sig
105 74 type t
106 Removed:
107 Removed: (** Whether a stimulus was delivered as one movement or as a pre-exhaust pair.
108 Removed: *)
109 75 type shape = As_single | As_pre_exhaust
110 76
111 77 type error =
112 78 | Not_prescribed of Exercise.id
113 Removed: (** No prescription in this workout covers the movement. *)
114 79 | Delivery_mismatch of {
115 80 exercise : Exercise.id;
116 81 prescribed : shape;
117 82 logged : shape;
118 83 }
119 Removed: (** The movement is prescribed, but not delivered the prescribed way — a
120 Removed: pre-exhaust pair is not interchangeable with a lone set. *)
121 84 | Already_finished
122 85
123 86 val pp_error : Format.formatter -> error -> unit
@@ -127,47 +90,30 @@
127 90 clearance:Recovery.clearance ->
128 91 started_at:Recovery.timestamp ->
129 92 t
130 Removed: (** Beginning a workout demands a {!Recovery.clearance}: under HD1 training
131 Removed: before recovery completes is the primary error, so it cannot happen by
132 Removed: accident. The clearance is kept, because how it was obtained is evidence.
133 Removed: *)
93 Added: (** Clearance records the basis for starting. *)
134 94
135 95 val add_stimulus : t -> Stimulus.t -> (t, error) result
136 Removed: val finish : t -> ended_at:Recovery.timestamp -> (t, error) result
96 Added: (** Only prescribed movements and delivery shapes are accepted. *)
137 97
138 Removed: (** {1 Reading a workout} *)
139 Removed:
98 Added: val finish : t -> ended_at:Recovery.timestamp -> (t, error) result
140 99 val prescription : t -> Prescription.Workout.t
141 Removed:
142 100 val clearance : t -> Recovery.clearance
143 Removed: (** On what basis this workout was begun. *)
144 Removed:
145 101 val started_at : t -> Recovery.timestamp
146 102 val ended_at : t -> Recovery.timestamp option
147 103 val is_finished : t -> bool
148 Removed:
149 104 val duration : t -> Recovery.duration option
150 Removed: (** [Some] once finished. HD1 reads a shortening duration on the same workout
151 Removed: as rising intensity, so it is a progress signal. *)
152 105
153 106 val stimuli : t -> Stimulus.t list
154 Removed: (** In the order performed. *)
107 Added: (** Performance order. *)
155 108
156 109 val outstanding : t -> (int * Prescription.Stimulus.t) list
157 Removed: (** Prescribed stimuli this workout has yet to answer, each with its position
158 Removed: — a caller that offers to log one has to be able to say which. *)
110 Added: (** Unanswered prescribed slots and their positions. *)
159 111
160 112 val unperformed : t -> Prescription.Stimulus.t list
161 Removed: (** {!outstanding} without the positions. *)
162 Removed:
163 113 val pp : Format.formatter -> t -> unit
164 114 end
165 115
166 Removed: (** The training diary: every workout performed, and the questions history can
167 Removed: answer.
168 Removed:
169 Removed: HD1 insists on keeping records — progress is knowable only from them, and
170 Removed: "even a one rep increase is significant". *)
116 Added: (** Workout history. *)
171 117 module Log : sig
172 118 type t
173 119
@@ -175,33 +121,25 @@
175 121 val add : t -> Workout.t -> t
176 122
177 123 val workouts : t -> Workout.t list
178 Removed: (** Most recent first, by when each workout began. *)
124 Added: (** Most recent first. *)
179 125
180 126 val last_prescription : t -> Prescription.Workout.t option
181 Removed: (** What was performed most recently, which is what
182 Removed: {!Prescription.Routine.workout_after} needs to know where the cycle
183 Removed: stands. *)
184 127
185 128 type observation = {
186 129 exercise : Exercise.t;
187 130 movement : Stimulus.Movement.t;
188 131 performed_at : Recovery.timestamp;
189 132 }
190 Removed: (** One movement as it was performed, on the day it was performed. Dated
191 Removed: because a stall is defined by progress ceasing *for two weeks*, so
192 Removed: evidence without time cannot answer the question. *)
193 133
194 134 val observations : t -> Exercise.t -> observation list
195 Removed: (** Every recorded performance of that movement, oldest first. *)
135 Added: (** Oldest first. *)
196 136
197 137 val readiness :
198 138 t ->
199 139 now:Recovery.timestamp ->
200 140 recommended:Recovery.duration ->
201 141 Recovery.readiness
202 Removed: (** Measured from the end of the last finished workout. [Ready] when nothing
203 Removed: has been logged yet, or when the last workout is still unfinished — there
204 Removed: is no completed effort to recover from. *)
142 Added: (** [Ready] for an empty log or no finished workout. *)
205 143
206 144 val pp : Format.formatter -> t -> unit
207 145 end
lib/core/exercise.mli
index 0dd59c26..f44d89df 100644..100644
@@ -1,10 +1,7 @@
1 Removed: (** Curated exercise catalog with author-specified substitution whitelists.
1 Added: (** Curated exercises, mechanics, and allowed substitutions. *)
2 2
3 Removed: Users do not log arbitrary movements: every exercise comes from {!catalog}.
4 Removed: Substitutions are limited to each exercise's author-specified whitelist. *)
5 Removed:
6 3 type t
7 Removed: (** Abstract catalog exercise; only obtainable via {!catalog} / {!find}. *)
4 Added: (** Only obtainable from {!catalog} or {!find}. *)
8 5
9 6 type id = private string
10 7
@@ -12,38 +9,25 @@
12 9 val name : t -> string
13 10 val equal : t -> t -> bool
14 11 val pp : Format.formatter -> t -> unit
15 Removed:
16 Removed: (** {1 Catalog} *)
17 Removed:
18 12 val catalog : t list
19 Removed: (** The complete curated catalog; the only source of {!t} values. *)
20 13
21 14 val find : string -> t option
22 15 (** By {!id}. *)
23 16
24 Removed: (** {1 Mechanics} *)
17 Added: type mechanic = Isolation | Compound
25 18
26 Removed: (** Whether a movement works one muscle or several. *)
27 Removed: type mechanic =
28 Removed: | Isolation
29 Removed: | Compound (** Involves assisting muscles, one of which is the weak link. *)
30 Removed:
31 19 val mechanic : t -> mechanic
32 20
33 21 val muscles : t -> Muscle.t list
34 Removed: (** Isolation: exactly one. Compound: two or more, strongest first. *)
22 Added: (** One for an isolation; at least two for a compound. *)
35 23
36 24 val may_pre_exhaust : isolation:t -> compound:t -> bool
37 Removed: (** Whether the pair forms a valid pre-exhaustion: [isolation] isolates a muscle
38 Removed: that [compound] also works, and [compound] involves at least one further
39 Removed: muscle able to serve it once the target is pre-exhausted. *)
25 Added: (** [isolation] targets a muscle that [compound] also works. *)
40 26
41 Removed: (** {1 Substitutions} *)
42 Removed:
43 27 type error = Not_permitted of { original : id; candidate : id }
44 28
45 29 val permitted_substitutes : t -> t list
46 30 val may_substitute : original:t -> candidate:t -> bool
47 31
48 32 val substitute : original:t -> candidate:t -> (t, error) result
49 Removed: (** [Ok candidate] iff [candidate] is on [original]'s whitelist. *)
33 Added: (** [Ok candidate] iff it is whitelisted for [original]. *)
lib/core/muscle.mli
index 0acc0076..a5da3c60 100644..100644
@@ -1,8 +1,4 @@
1 Removed: (** The muscles HD1 names as training targets.
2 Removed:
3 Removed: Deliberately minimal: muscles exist here only so a pre-exhaust pairing can
4 Removed: be validated — an isolation movement must feed a compound that works the
5 Removed: same muscle alongside others able to assist it. *)
1 Added: (** Training targets used to validate pre-exhaust pairings. *)
6 2
7 3 type t =
8 4 | Pecs
lib/core/prescription.mli
index 8a9eee67..3df159a6 100644..100644
@@ -1,73 +1,43 @@
1 Removed: (** The plan: what to do, at every scale HD1 prescribes one.
1 Added: (** Plans for stimuli, workouts, and routines. *)
2 2
3 Removed: A stimulus is one drive to failure, a workout is a sequence of them, and a
4 Removed: routine is the cycle of workouts. Nothing here observes anything — the
5 Removed: performed counterpart is {!Evidence}, and judging one against the other is
6 Removed: {!Progression}'s work. *)
7 Removed:
8 Removed: (** One prescribed stimulus: how a single drive to failure is to be delivered,
9 Removed: and the rep window that calibrates its load.
10 Removed:
11 Removed: Heavy Duty prescribes one working set, so that is implicit and not
12 Removed: configurable. *)
3 Added: (** One prescribed drive to failure. *)
13 4 module Stimulus : sig
14 5 type t
15 6
16 Removed: (** How the stimulus is delivered. HD1 knows only these two shapes: a single
17 Removed: movement, or an isolation carried straight into a compound. There is no
18 Removed: antagonist superset — "superset" in HD1 always means pre-exhaustion. *)
19 7 type delivery =
20 8 | Single of Exercise.t
21 9 | Pre_exhaust of { isolation : Exercise.t; compound : Exercise.t }
22 Removed: (** Performed with no pause: even a three-second delay lets the target
23 Removed: recover and restores the weak link. *)
10 Added: (** No pause between movements. *)
24 11
25 12 type error =
26 13 | Not_a_pre_exhaust of { isolation : Exercise.id; compound : Exercise.id }
27 Removed: (** The pair cannot pre-exhaust; see {!Exercise.may_pre_exhaust}. *)
28 Removed: | Reps_outside_limits (** The rep window escapes {!rep_limits}. *)
14 Added: | Reps_outside_limits
29 15 | Substitute_not_permitted of Exercise.id
30 Removed: (** A substitute is on no delivery movement's catalog whitelist. *)
31 16
32 17 val pp_error : Format.formatter -> error -> unit
33 18
34 19 val rep_limits : Units.Rep_range.t
35 Removed: (** HD1's stimulus window, 6-12: fewer than six does not tax the reserves
36 Removed: sufficiently, and beyond twelve the set ends in cardiorespiratory
37 Removed: insufficiency before the muscle reaches failure. A prescribed range must
38 Removed: lie within it. *)
20 Added: (** 6-12; every prescribed range must lie within it. *)
39 21
40 22 val make :
41 23 delivery:delivery ->
42 24 rep_range:Units.Rep_range.t ->
43 25 allowed_substitutes:Exercise.t list ->
44 26 (t, error) result
45 Removed: (** Each substitute must be permitted for one of the delivery's movements, so
46 Removed: a prescription may only narrow the curated set. *)
27 Added: (** Substitutions may only narrow catalog whitelists. *)
47 28
48 29 val delivery : t -> delivery
49 Removed:
50 30 val rep_range : t -> Units.Rep_range.t
51 Removed: (** Calibrates load selection only. Reps are an outcome: a set ends at
52 Removed: failure, never because a number was reached. *)
53 Removed:
54 31 val allowed_substitutes : t -> Exercise.t list
55 32
56 33 val exercises : t -> Exercise.t list
57 Removed: (** The delivery's movements, in performance order. *)
34 Added: (** Performance order. *)
58 35
59 36 val permits : t -> Exercise.t -> bool
60 Removed: (** Whether the movement may be logged against this prescription — one of its
61 Removed: own, or an allowed substitute. *)
62 Removed:
63 37 val pp : Format.formatter -> t -> unit
64 38 end
65 39
66 Removed: (** One prescribed workout, such as HD1's Day 1: an ordered sequence of
67 Removed: prescribed stimuli.
68 Removed:
69 Removed: Each stimulus is exactly one drive to failure, so the workout's volume is
70 Removed: its length — HD1's "least amount required" is structural here. *)
40 Added: (** Ordered prescribed stimuli. *)
71 41 module Workout : sig
72 42 type t
73 43 type id = private string
@@ -75,57 +45,44 @@
75 45
76 46 val make :
77 47 id:string -> name:string -> stimuli:Stimulus.t list -> (t, error) result
78 Removed: (** [Error Empty_workout] if [stimuli] is empty. *)
48 Added: (** Rejects an empty workout. *)
79 49
80 50 val id : t -> id
81 51 val name : t -> string
82 Removed:
83 52 val stimuli : t -> Stimulus.t list
84 Removed: (** In performance order. *)
85 53
86 54 val equal : t -> t -> bool
87 Removed: (** By {!id}, which is what lets a routine find its place in the cycle. *)
55 Added: (** By [id]. *)
88 56
89 57 val pp : Format.formatter -> t -> unit
90 58 end
91 59
92 Removed: (** The cycle of workouts, and how long to rest between them. *)
60 Added: (** A workout cycle and its recovery intervals. *)
93 61 module Routine : sig
94 62 type t
95 63 type error = Empty_routine
96 64
97 65 val make : name:string -> workouts:Workout.t list -> (t, error) result
98 Removed: (** [Error Empty_routine] if [workouts] is empty. *)
66 Added: (** Rejects an empty routine. *)
99 67
100 68 val name : t -> string
101 69
102 70 val workouts : t -> Workout.t list
103 Removed: (** In cycle order. *)
71 Added: (** Cycle order. *)
104 72
105 73 val workout_after : t -> Workout.t -> Workout.t
106 Removed: (** The next workout in the cycle, wrapping at the end. Falls back to the
107 Removed: first workout when [t] does not contain the one given. *)
74 Added: (** Wraps; unknown workouts fall back to the first. *)
108 75
109 76 val pp : Format.formatter -> t -> unit
110 77
111 Removed: (** {1 Rest between workouts}
112 Removed:
113 Removed: HD1 prescribes training every other day, then two full days off at the
114 Removed: conclusion of each cycle — so the recommended rest depends on where in the
115 Removed: cycle you are, and is not one flat interval. *)
116 Removed:
117 78 val training_interval : Recovery.duration
118 Removed: (** 48h: every other day, within a cycle. *)
79 Added: (** 48h. *)
119 80
120 81 val cycle_rest : Recovery.duration
121 Removed: (** 72h: the two days off once the cycle completes. *)
82 Added: (** 72h after the final workout. *)
122 83
123 84 val recovery_after : t -> Workout.t -> Recovery.duration
124 Removed: (** How long to rest having performed that workout. *)
125 85
126 Removed: (** {1 Presets} *)
127 Removed:
128 86 val ideal : t
129 Removed: (** HD1's Ideal Routine, three workouts: pecs/delts/triceps, then
130 Removed: lats/traps/erectors/biceps, then legs/abs. *)
87 Added: (** HD1's three-day routine. *)
131 88 end
lib/core/progression.mli
index 0b646117..8b1d846e 100644..100644
@@ -1,23 +1,10 @@
1 Removed: (** Assessment: what the record says, and what follows from it.
1 Added: (** Interprets recorded training against Heavy Duty rules. *)
2 2
3 Removed: The only module that judges. {!Prescription} states what to do, {!Evidence}
4 Removed: states what happened, and this decides what that means.
5 Removed:
6 Removed: Its central asymmetry is Mentzer's: when progress halts, the answer is never
7 Removed: more work. HD1 is explicit that the urge to raise volume and frequency on a
8 Removed: stall "must be kept in check and avoided at all costs", so no remedy here
9 Removed: can add either — there is one remedy, and it subtracts. *)
10 Removed:
11 Removed: (** {1 Progress} *)
12 Removed:
13 3 val beats :
14 4 previous:Evidence.Stimulus.Movement.t ->
15 5 current:Evidence.Stimulus.Movement.t ->
16 6 bool
17 Removed: (** Whether the later performance is an advance: more load, or the same load for
18 Removed: more reps. HD1 counts "an increase in reps, weight or both", and holds that
19 Removed: even a single extra rep is significant. Fewer reps at a heavier load still
20 Removed: counts — that is exactly what raising the load is meant to produce. *)
7 Added: (** More load, or equal load and more reps. *)
21 8
22 9 type assessment = Progressing | Stalled
23 10 type error = Insufficient_data
@@ -26,72 +13,44 @@
26 13 val pp_error : Format.formatter -> error -> unit
27 14
28 15 val stall_window : Recovery.duration
29 Removed: (** Two weeks. Progress must have ceased *entirely* for this long before the
30 Removed: routine is judged to be overtraining. *)
16 Added: (** 14 days. *)
31 17
32 18 val assess : Evidence.Log.observation list -> (assessment, error) result
33 Removed: (** Oldest first, and all of one exercise — {!Evidence.Log.observations} returns
34 Removed: exactly that. Mixing exercises compares unrelated movements and means
35 Removed: nothing. [Error Insufficient_data] while the record is too short or too
36 Removed: recent to distinguish a stall from an ordinary gap between advances. *)
19 Added: (** Oldest first, for one exercise only. *)
37 20
38 Removed: (** {1 The one sanctioned response to a stall} *)
39 Removed:
40 21 type remedy =
41 22 | Lay_off_then_reduce of {
42 Removed: lay_off : Recovery.duration; (** One week, to replenish reserves. *)
23 Added: lay_off : Recovery.duration;
43 24 drop_stimuli_per_workout : int;
44 Removed: (** Eliminate one set from each workout. *)
45 25 extra_rest : Recovery.duration;
46 Removed: (** An extra day off between workouts thereafter. *)
47 26 }
48 27
49 28 val remedy : assessment -> remedy option
50 Removed: (** [Some] only when stalled. There is no remedy that adds work. *)
29 Added: (** [Some] only for [Stalled]; it never adds work. *)
51 30
52 31 val pp_remedy : Format.formatter -> remedy -> unit
53 32
54 Removed: (** {1 Whether the load still calibrates the window} *)
55 Removed:
56 33 val load_increase_trigger : Units.Reps.t
57 Removed: (** Twelve reps, and absolute: HD1 raises the load on reaching twelve, whatever
58 Removed: window was prescribed, because beyond twelve the set ends in
59 Removed: cardiorespiratory failure before the muscle does. *)
34 Added: (** 12 reps, regardless of the prescribed range. *)
60 35
61 36 val load_increase : current:Units.Weight.t -> Units.Weight.t * Units.Weight.t
62 Removed: (** The 10-20% window to move into. A window, not a figure: HD1 says "or any
63 Removed: amount necessary" to be forced back into the rep range. *)
37 Added: (** 10-20% increase window. *)
64 38
65 39 type load_verdict =
66 Removed: | Hold (** The load still places failure inside the window. *)
40 Added: | Hold
67 41 | Increase of Units.Weight.t * Units.Weight.t
68 Removed: (** Twelve reps reached: move into this window. *)
69 Removed: | Too_heavy (** Failure arrived below the window's floor. *)
42 Added: | Too_heavy
70 43
71 44 val judge_load :
72 45 rep_range:Units.Rep_range.t -> Evidence.Stimulus.Movement.t -> load_verdict
73 Removed: (** Only [rep_range]'s floor gates the verdict. Its ceiling does not: exceeding
74 Removed: the prescribed window is the slack HD1 allows — a 6-10 band is not breached
75 Removed: at eleven — and the load rises at {!load_increase_trigger} regardless of
76 Removed: where the ceiling sits. *)
46 Added: (** Only the range floor matters: below it is [Too_heavy]; at twelve reps the
47 Added: verdict is [Increase]. *)
77 48
78 49 val pp_load_verdict : Format.formatter -> load_verdict -> unit
79 50
80 Removed: (** {1 Diagnostics}
81 Removed:
82 Removed: Habits that HD1 identifies as causes of overtraining. These are observations
83 Removed: about the record, never constraints on it — the log's job is to say what
84 Removed: happened, so these can only be read back out of it afterwards. *)
85 Removed:
86 51 type diagnostic =
87 52 | Extensions_on_every_stimulus of int
88 Removed: (** Workouts in which every stimulus went beyond failure. HD1 uses
89 Removed: extensions occasionally and "never with every exercise of a workout".
90 Removed: *)
91 53 | Trained_under_recovered of int
92 Removed: (** Workouts begun on an overridden clearance. *)
93 54
94 55 val diagnose : Evidence.Workout.t list -> diagnostic list
95 Removed: (** Only the habits actually present. *)
96 Removed:
97 56 val pp_diagnostic : Format.formatter -> diagnostic -> unit
lib/core/recovery.mli
index 51dc0f02..b0baaa7c 100644..100644
@@ -1,12 +1,5 @@
1 Removed: (** Recovery: the elapsed time between two workouts, judged against a
2 Removed: recommended window, and the permission to train that follows from it.
1 Added: (** Recovery timing and explicit permission to train. *)
3 2
4 Removed: HD1 treats recovery as a precondition of growth: training before the body
5 Removed: has replenished its reserves is the primary error, and its effects are
6 Removed: systemic, not local to the muscles worked. So starting a workout requires a
7 Removed: {!clearance}, either earned by resting or taken deliberately and on the
8 Removed: record. *)
9 Removed:
10 3 type timestamp = private int
11 4
12 5 val timestamp_of_unix_seconds : int -> timestamp
@@ -19,29 +12,21 @@
19 12 val duration_to_seconds : duration -> int
20 13
21 14 val elapsed : since:timestamp -> now:timestamp -> duration
22 Removed: (** Zero if [now] precedes [since]: rest cannot be negative. *)
15 Added: (** Zero when [now] precedes [since]. *)
23 16
24 17 val pp_duration : Format.formatter -> duration -> unit
25 18
26 Removed: (** {1 Readiness} *)
27 Removed:
28 19 type readiness =
29 20 | Ready
30 21 | Recovering of { rested : duration; recommended : duration }
31 Removed: (** [rested] of [recommended] has passed. *)
32 22
33 23 val evaluate_readiness : elapsed:duration -> recommended:duration -> readiness
34 24 val is_ready : readiness -> bool
35 25 val pp_readiness : Format.formatter -> readiness -> unit
36 26
37 Removed: (** {1 Clearance to train} *)
38 Removed:
39 27 type clearance
40 Removed: (** Permission to begin a workout. Unforgeable: obtainable only by having
41 Removed: recovered, or by {!override}. *)
28 Added: (** Unforgeable permission: earned by recovery or explicitly overridden. *)
42 29
43 Removed: (** Why training was permitted. Recorded, so that an unrecovered workout remains
44 Removed: visible to {!Progression} when diagnosing a stall. *)
45 30 type basis =
46 31 | Recovered
47 32 | Overridden of { rested : duration; recommended : duration; reason : string }
@@ -50,7 +35,6 @@
50 35 (** [Some] iff recovery is complete. *)
51 36
52 37 val override : readiness -> reason:string -> clearance
53 Removed: (** Train regardless, on the record. Under HD1 this is an error, not a shortcut
54 Removed: — the reason exists so the decision can be weighed later. *)
38 Added: (** Records training before recovery completes. *)
55 39
56 40 val basis : clearance -> basis
lib/core/units.mli
index 28e9a7a9..171ca6c0 100644..100644
@@ -1,16 +1,13 @@
1 Removed: (** Physical quantities recorded during training.
1 Added: (** Validated training quantities. *)
2 2
3 Removed: Every type here is abstract and constructible only through a smart
4 Removed: constructor, so an invalid measurement cannot exist. *)
5 Removed:
6 3 type error =
7 Removed: | Negative (** A quantity that must be >= 0 was negative, or not finite. *)
8 Removed: | Not_positive (** A quantity that must be > 0 was <= 0. *)
9 Removed: | Inverted_range (** A range's lower bound exceeded its upper bound. *)
4 Added: | Negative (** Negative or non-finite value. *)
5 Added: | Not_positive (** Non-positive value. *)
6 Added: | Inverted_range (** Lower bound exceeds upper bound. *)
10 7
11 8 val pp_error : Format.formatter -> error -> unit
12 9
13 Removed: (** Load in kilograms. Nonnegative: body-weight movements are [zero]. *)
10 Added: (** Nonnegative kilograms; [zero] is valid for body-weight movements. *)
14 11 module Weight : sig
15 12 type t
16 13
@@ -22,7 +19,7 @@
22 19 val pp : Format.formatter -> t -> unit
23 20 end
24 21
25 Removed: (** A completed repetition count. Strictly positive. *)
22 Added: (** Strictly positive completed repetitions. *)
26 23 module Reps : sig
27 24 type t
28 25
@@ -33,9 +30,7 @@
33 30 val pp : Format.formatter -> t -> unit
34 31 end
35 32
36 Removed: (** An inclusive rep band. Calibrates load selection: HD1's guideline is 6-10.
37 Removed: Reps are an outcome, never a target — a set ends at failure, not at a
38 Removed: number. *)
33 Added: (** Inclusive load-calibration range. *)
39 34 module Rep_range : sig
40 35 type t
41 36