Skip to content

Author the thin indoor cells and audit the doses (#89) - #120

Merged
kilianmc merged 1 commit into
devfrom
feat/89-indoor-wall-breadth
Sep 4, 2026
Merged

kilianmc merged 1 commit into
devfrom
feat/89-indoor-wall-breadth

Conversation

@kilianmc

@kilianmc kilianmc commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Closes #89.

The pass condition

No single exercise may exceed ~10% of a plan's prescribed minutes in a plain bouldering gym (equipment_keys=("bouldering_wall",)).

profile worst single exercise BEFORE AFTER
sport beginner 6a → 6c limit_boulders 27.9% boulders_on_the_two_minute 8.5%
boulder beginner 6A → 7A limit_boulders 25.5% hip_positioning_drills 8.5%
sport intermediate 6c → 7b limit_boulders 25.9% deadpoint_drills 8.5%
boulder intermediate 7A → 7C limit_boulders 25.8% arc_traversing 8.1%

Distinct exercises drawn: 23→34, 22→34, 25→36, 23→36. Library 87→100 rows. Totals fell 10–13% on purpose — the added rows are cheaper per block than the ones they displace.

The cause was not "too few power exercises". limit_boulders was the only on-wall power row reachable in a plain gym that carried a Phase.STRENGTH prescription, and 18 of its 35 draws were in the strength phase. All six new power rows carry one; that is what broke the monoculture.

What is in it

  • 13 new exercises — six plain-gym power, two anaerobic_capacity (the aspect had no on-wall option at all), four general_strength, one endurance.
  • Seven rows re-dosed against Barrows' §7 quick reference, plus dumbbell_press_and_row reworded to resolve its overlap with the new heavy_single_arm_rows.
  • Three generator defects the content exposed: sessions under their type's window floor are topped up with climbing rather than accessories (floor misses 34→0); the wall-share band's top edge is read per session rather than per week, so more days never means less climbing (breaches 60→2, both remaining ones registered); and the base-ordering guard re-scoped to §3.2's actual claim.
  • A latent persistence bug: generated titles could exceed their String(80) column and raise StringDataRightTruncation. Now bounded by construction, with a guard pinning the constant to the column width.

Three things a reviewer should not "fix"

1. The bodyweight-multiple standards are absent on purpose. Climb Strong ("deadlift 1.5–2× bodyweight"), Steven Low ("~50% bodyweight pistol") and trainingforclimbing.com ("improve body composition") all state standards as multiples of bodyweight. Following the citations will turn up standards this copy does not carry. Restoring them would break CLAUDE.md's first domain rule — the app never recommends losing weight. Loading language is "add load" and "depth first, load second". The absence is the decision.

2. _DISTINCT_SHARE_FLOOR_PCT 68 → 63 is not a weakened guard. The 68% figure was measuring a defect: four long accessories were reachable only through sessions running under their type's floor, where _pick took the longest candidate that fit instead of its rotation. This PR closes that path, so 68% counted reachability that depended on a bug. 63 leaves exactly one exercise of slack at the lowest profile. The one apparent fix was measured and rejected — widening _length_pick's pool leaves boulder 6A at 67.8% and pushes advanced sport to 63.2% against the band's 62% ceiling.

3. arc_traversing took the continuous reading deliberately. Barrows gives "20–40 min continuous" or "10-on/10-off ×4". The interval reading is a 70-minute block and would have blown the pass condition by itself. The interval form is not represented anywhere in the library.

Guards shown to fail before being trusted

Nine sabotages across six guards, every one restored and verified byte-identical before the gate ran. The load-bearing one: reverting the _wall_pref fix produces 5 unregistered inversions (8 s to 628 s) and blows 2 accepted rows past their caps — which is what proves _ACCEPTED_INVERSIONS did not weaken the invariant it registers exceptions to.

Accepted monotonicity exceptions, now data rather than silence

_ACCEPTED_INVERSIONS carries seven rows with a per-row loss cap and a reason, so none can grow and a row that stops inverting fails. Two reasons: the solo-week collision at 1→2 only (a one-session week aims at 100% climbing while two sessions put the climber on their band — worst 1184 s, advanced boulder 7C), and the week-level _floor_allows residual at 3→4 (12 s; CLIMBING_FLOOR_PCT is documented as a percent of a week, so reading it per session would be stricter than the contract rather than a refactor of it).

⚠️ The 3→4 rows are broader than the ruling that approved the 1→2 case. Flagged for a decision rather than left silent.

Still open

  • Three dose corrections declined with reasons, awaiting a yes/no: lead_route_doubles (four laps contradicts the row's own name and key — a rename is a data migration), campus_ladders (two exercises sharing a name, not a bad dose), hangboard_repeaters (a run-to-failure protocol is a different row).
  • Coverage note: halving a single (phase, aspect) pool leaves the breadth floor green, because most rows appear in several phases. The floor bites on a rotation defect or a library-wide loss, not one cell. Nothing else covers that case.

npm run check green: web 55 files / 1282 tests; server 945 passed, 0 failed.

🤖 Generated with Claude Code

The plain-gym monoculture is fixed at its cause: `limit_boulders` was the
only on-wall `power` row a bouldering-gym user could reach that carried a
`STRENGTH` prescription, so the generator drew it 18 times running. Six new
plain-gym power rows now carry one, and no single exercise exceeds 8.5% of a
plan's prescribed minutes (was 25.5-27.9%).

13 exercises added, library 87 -> 100: six `power`, two `anaerobic_capacity`
(the aspect had no on-wall option at all), four `general_strength`, and one
`endurance` to break the `arc_traversing` monoculture the plan's own proposal
list did not cover. Seven existing rows re-dosed against Barrows' §7 table.

Three generator defects the content exposed, each fixed on its merits:
sessions under their type's floor are now topped up with climbing rather than
accessories; the wall-share band's top edge is read per session rather than
per week, so a day added is climbing added; and the base-ordering guard is
re-scoped to §3.2's actual claim (the two MAINTAINED qualities take <=15% of
base wall time) instead of a strict pairwise ranking no source states.

Also fixes a latent persistence bug: generated titles could exceed their
`String(80)` column and raise `StringDataRightTruncation` on save.

`GENERATOR_VERSION` 3.0.0 -> 4.0.0 for the behaviour changes; the content
alone would not have needed it, since `library_digest()` already makes a
content edit read as a different input.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
climb-trainer Ready Ready Preview Sep 4, 2026 5:08pm UTC

This branch was successfully deployed

1 active deployment
Preview — e6430e7a Deployed Sep 4, 2026 by vercel[bot]
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