Skip to content

decision.altitude_floor: measure what the name says, and drop a prior the archive does not support - #520

Merged
pokle merged 2 commits into
masterfrom
claude/altitude-floor-metric-explanation-2ffc88
Jul 31, 2026
Merged

decision.altitude_floor: measure what the name says, and drop a prior the archive does not support#520
pokle merged 2 commits into
masterfrom
claude/altitude-floor-metric-explanation-2ffc88

Conversation

@pokle

@pokle pokle commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Branch preview: https://claude-altitude-floor-metric.glidecomp.pages.dev

The metric was labelled "How high the pilot commits to the next climb" (Commit%) and explained as the height still in hand "at the moment they commit to a climb" — but the computation never looked at a climb. It swept the 30 s-smoothed altitude trace for local minima with 100 m of prominence either side.

It is now "How low the pilot goes before the next climb" (Floor%), computed by walking each consecutive PAIR of post-start climbs and taking the lowest fix between them, keeping the gaps that descended at least 100 m. Same question, structural answer: "between climbs" is the loop bound rather than something the prominence sweep happened to produce, so the run out to the first climb, the glide to goal and a sled run are excluded because nothing was climbed after them. No smoothing, no prominence, and the reported low is a real fix altitude rather than a 30 s mean that flattens a deep save upward.

Audited over the whole archive

184 tasks, 25 comps, 4,762 pilot-tasks from pokle/glidecomp-archive, scoring each task and running the real metric.

  • The two definitions agree and separate equally. Spearman 0.87 on pilot ordering; median 40.6% of band against 38.2%; median |ρ| against GAP rank 0.43 against 0.40, stronger on 51% of tasks — a coin flip. This change is made for explainability, not for signal.
  • Coverage rises to 85.0% of started pilots from 83.7%, but shifts which pilots qualify: three climbs are now needed for two gaps, where two dips used to do.
  • The direction: 'higher' prior does not hold, so it drops to neutral (the v9 treatment). Only 69% of 147 task correlations take the assumed sign, 16 are wrong-signed at |ρ| ≥ 0.3, and two large fields are emphatically opposite — Bright 2023 open T3 (85 pilots, ρ +0.73, top ten at 33% of band against the bottom ten at 78%) and Bright 2024 open T2 (95 pilots, ρ +0.52). Whether height in reserve pays is a property of the day, so the sign is the finding.

Two things the audit cleared rather than changed, both recorded in comments so the next reader doesn't re-ask:

  • The window runs to landing, not ESS. Only 8% of ESS finishers have any post-ESS dip, and capping at ESS moved the median by more than 5 points for 2% of them. A gap needs a climb on both sides, so the glide to goal is excluded by construction.
  • The old definition was not measuring wobbles inside climbs. Only 0.1% of 39,272 minima sat more than 60 s into a thermal; the rest were at a climb's boundary either side.

The explanation also now states the band's real endpoints — p10 of the field's thermal entries, p90 of its exits — so a negative reading reads as expected rather than alarming. Over the archive 2.3% of scored pilots sit below 0%.

Notes for review

  • FIELD_ANALYSIS_VERSION 18 → 19 rolls stored task_field_analysis reports onto the new label, direction and values.
  • Explanation text is written in the Simplified Technical English house style this file adopted in Simplified Technical English across the field analysis, and metric labels that name the quantity #519, which this branch merges.
  • decision.altitude_floor moves to the 55% coverage tier in the kosci integration gate. That is a fixture property, not a scope one: the synthetic tracks are climb-poor (15 of 41 started pilots take fewer than three climbs, against an archive median near nineteen). Real-archive coverage went up, not down.
  • New test pins the distinguishing behaviour: a pilot who climbs three times and then glides to the deck reads at their between-climb lows, not at the altitude they landed from.
  • bun run test:all green (engine 1229, frontend 518, auth-api 92, competition-api 477) and typecheck:all clean.

🤖 Generated with Claude Code

pokle and others added 2 commits July 31, 2026 19:31
The metric was labelled "How high the pilot commits to the next climb"
(Commit%) and explained as the height still in hand "at the moment they
commit to a climb" — but the computation never looked at a climb. It
swept the 30 s-smoothed altitude trace for local minima with 100 m of
prominence either side, and reported the median as a band percentage.

It is now "How low the pilot gets between climbs" (Floor%), computed by
walking each consecutive PAIR of post-start climbs and taking the lowest
fix between them, keeping the gaps that descended at least 100 m. Same
question, structural answer: "between climbs" is the loop bound rather
than something the prominence sweep happened to produce, so the run out
to the first climb, the glide to goal and a sled run are excluded
because nothing was climbed after them. No smoothing, no prominence, and
the reported low is a real fix altitude rather than a 30 s mean that
flattens a deep save upward.

Audited over the whole glidecomp-archive (184 tasks, 25 comps, 4,762
pilot-tasks):

  - The two definitions agree closely — Spearman 0.87 on pilot ordering,
    median 40.6% of band against 38.2% — and separate the leaderboard
    equally: median |rho| 0.43 against 0.40, stronger on 51% of tasks.
    This is a change made for explainability, not for signal.
  - Coverage rises to 85.0% of started pilots from 83.7%, but shifts
    which pilots qualify: three climbs are needed for two gaps where two
    dips used to do.
  - The old prior direction 'higher' does not hold. Only 69% of 147
    task correlations take the assumed sign, and two large fields are
    emphatically opposite — Bright 2023 open T3 (85 pilots, rho +0.73,
    top ten at 33% of band against the bottom ten at 78%) and Bright
    2024 open T2 (95 pilots, rho +0.52). Whether height in hand pays is
    a property of the day, so direction drops to 'neutral' (the v9
    treatment) and the sign becomes the finding.

The explanation also states the band's real endpoints — p10 of the
field's thermal entries, p90 of its exits — so a negative reading reads
as expected rather than alarming. Over the archive 2.3% of scored pilots
sit below 0%.

FIELD_ANALYSIS_VERSION 17 -> 19 rolls stored reports onto the new
label, explanation, direction and values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…or-metric-explanation-2ffc88

# Conflicts:
#	web/engine/src/field-analysis/metrics/decision.ts
#	web/engine/src/field-analysis/version.ts
@pokle
pokle enabled auto-merge (squash) July 31, 2026 09:36
@pokle
pokle merged commit 29120f1 into master Jul 31, 2026
3 checks passed
@pokle
pokle deleted the claude/altitude-floor-metric-explanation-2ffc88 branch July 31, 2026 09:37
@github-actions

Copy link
Copy Markdown

Preview Deployment
https://a8563cb3.glidecomp.pages.dev
Commit: 5ea51ef

pokle pushed a commit that referenced this pull request Aug 1, 2026
18 commits, of which #519 ("Simplified Technical English across the field
analysis, and metric labels that name the quantity") lands squarely on the
charts this branch changed. Git auto-merged all four overlapping files —
their edits were to caption prose, ours to axis geometry — but the merge
being clean is not the same as the work still being right, so:

- #519 rewrote every metric's FULL label. RankScatter's x-axis title is
  built from `metric.label`, so this is where phase 1 could have broken:
  a label that already carried its unit would have produced "Glide speed
  (km/h) (kilometres per hour)". None do. Checked by walking all 22 rows
  of the ranking in the running app and reading every title the scatter
  drew: the longest is 69 characters ("How much of the thermal the pilot
  climbed before leaving it (percent)") and still fits the plot without
  clipping, and the ratio-unit metrics correctly get no parenthetical.
  The new labels are a straight improvement to the axis titles.
- The heatmap's axis titles and the day-profile stack's shared time-axis
  title survived intact.
- #521 added a thermals chart with its own inline x title. Left alone on
  purpose — 9px and left-anchored are deliberate choices for a 320-unit
  panel, and adopting the shared helper would silently overturn them.

Plan doc updated where master has overtaken it: §D is half-answered (full
labels done by #519/#520, FIELD_ANALYSIS_VERSION now 20; the cryptic
SHORT labels in the heatmap remain), §A is marked shipped with the two
places it came out differently, and the baseline table is marked as a
snapshot — the same page now measures 17,523 px / 9,081 words, so a phase
claiming a reduction must prove it against a fresh measurement. §B, §C,
§E, §F, §G, §H are untouched: the legends #519 reworded are still inline,
and the comp page still has zero ⓘ.

Verified: typecheck:all, 1,238 engine and 533 frontend unit tests, 52 e2e,
33 SSR e2e.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UyMHUij8wpMk6rZzX3yqaY
pokle pushed a commit that referenced this pull request Aug 2, 2026
Merges master (2cb994c) and audits MetricLanes across the whole
glidecomp-archive: 184 tasks, 1,104 families, 3,239 lanes.

New web/scripts/audit-metric-lanes.ts, following audit-score-charts.ts:
it scores every task in a comp library with the real engine, builds the
real field-analysis report, runs buildLanes over each family the way
MetricFamilySection does, and checks the invariants the drawing code
relies on — dots pair by trackFile, every percentile sits strictly
inside (0,100), the value printed at a lane's end is the value of the
dot at that end (the one most likely to be silently wrong, since a
'lower'-is-better lane is flipped), the order is deterministic under
reversed input, and no outcome metric gets a lane.

Zero invariant violations across the archive. The three fixes below all
come from things the audit COUNTED rather than things it failed on —
each renders as a blank or an average-looking dot, which is why no test
was going to catch them.

1. A lane measuring ONE pilot is dropped (136 of 3,401 lanes). Midrank
   puts a lone value at exactly 50, so the lane stated "this pilot was
   dead average" when it meant "only one pilot could be measured". There
   is no field for a percentile to sit within. The value is still in the
   per-pilot table, which is where a single reading belongs. This also
   halved the all-identical lanes, 283 -> 127.

2. The default profile falls past a leader with nothing to draw (5 of
   861 charts). It was rank 1 unconditionally; a winner who never shared
   a thermal has no gaggle metrics at all, so those charts drew no
   connector — the rows-of-dots state the default exists to prevent.
   Now the best-ranked pilot appearing in at least two lanes, because a
   single dot is not a profile. Zero such charts remain.

3. An all-identical lane says so. 127 lanes stack every dot on P50 under
   a bare figure, which reads as a broken chart; it now reads "every
   pilot 0.4 m/s".

Also adopts two conventions master landed while this was open: the
shared XAxisTitle/YAxisTitle from charts/AxisTitle.tsx (#501 — every
chart names its axes in the plot, because a caption is read once and an
axis continuously), and nothing needed for #520's decision.altitude_floor
going 'higher' -> 'neutral', since the orientation is data-driven.

bun run test:all (1279 engine/script, 584 frontend, 508 comp, 92 auth),
typecheck:all, and test:e2e:ssr (33 passed). Re-driven in a browser:
axis titles clear the gutter, connector and readout unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1Zrf2tMEP2QHK6jWboAQq
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