Price the cutline gap as a finish, and add a head-to-head panel - #75
Merged
Merged
Conversation
The cutline panel said "137 short" and stopped there, which is a true number nobody can act on. It now draws the line where it lands in a kind season and a cruel one — the 10th and 90th percentiles either side of the median — and a table under it prices all three as a finishing place at the closing event: the worst place that still carries a player over each line. The two annotated gaps and the tick readout carry the same figure. The arithmetic has to run through each player's own counting pools rather than off the points table, because a result is only worth what it beats: a player whose best two playoff finishes are already banked keeps only the difference, which is why the same gap costs two players different finishes. `poolSum` and `countingPools` move up out of the leverage panel, since two panels need them now. The new panel answers the other half of the question. Pick any two players still in contention and it walks the leader's weekend down the points curve — a win, a podium, a 20th, dead last — giving the finish the chaser needs and how many places clear of the leader that is. The margin is not fixed, because the curve is top-heavy; against a lead the event cannot pay it says so outright rather than printing a column of dashes. Two pickers over the possibility cloud's list, not a matrix: every pairing of 52 players is 1,326 answers nobody reads. Both are exact arithmetic on the published curve rather than a simulation, and hold two things still: the rest of the season where it stands (exact with one event left, a ceiling before that), and the frozen cutline the row expander already runs on. Verified against the published MPO and FPO bundles at three viewport widths: six panels render, no page errors, no horizontal overflow, and the not-in-the-field paths checked by withdrawing contenders from the field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBC4pyUqbxW9y5o9tAeRr
`test_a_staged_mvp_roster_still_admits_gmc_performers` was the one test in the file that let `fields._waves_all_open` reach the real `now()`. That function compares config.REG_PHASES against the clock, and the MVP's last phase — "GMC performance qualifiers" — opens 2026-09-21, so the test was asserting something true only between Sep 1 and Sep 21. It passed all September and turned red on the 21st with nothing in the code having changed. Production is behaving correctly: GMC finished Sep 20, so a staged MVP roster genuinely is the field now. Only the test was time-dependent, so it gains a pinned date the same way the two roster-staging tests beside it already freeze theirs. The phase table is still what is under test — deleting the performance phase still fails this — but the date it is read at is fixed. Also corrects a neighbouring docstring that named the waves as the reason a page-derived list stays open. It is `staged` that decides that: only a PDGA Live roster ever closes a field, which is why the test keeps passing after Sep 1 when the stated reason stopped being true. Two findings logged under HARDENING item 9: the clock-shift sweep that finds this class of bug (the suite passes at +0/+3/+14/+60/+200 days), and the fact that the MVP's last phase is keyed to a hardcoded date rather than to GMC's schedule row being complete — a proxy that would come apart if GMC were ever delayed past it. 258 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsBC4pyUqbxW9y5o9tAeRr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The cutline panel said "137 short" and stopped there, which is a true number nobody can act on. Two additions to the What's left tab turn distances into the unit the weekend is actually played in: a finishing place.
Front-end only — no pipeline change, no bundle-schema change. Both panels are exact arithmetic on fields the bundle already ships.
1 · The cutline gap, priced
The chart now also draws the line where it lands in a kind season and a cruel one — dashed marks at the 10th and 90th percentiles either side of the median — and a table under it prices all three as a finish at the closing event (the MVP Open while it is unplayed, otherwise whatever is last on the calendar):
The two annotated gaps on the chart carry the same figure — "Smith · 137 short · needs 7th", "Marwede · 4 short · any finish does it" — and so does the tick readout, on a second line.
The arithmetic has to run through each player's own counting pools rather than off the points table: a result is only worth what it beats, so a player whose best two playoff finishes are already banked keeps only the difference. That is why the same gap costs two players different finishes.
poolSumandcountingPoolsmove up out of the leverage panel, since two panels need them now.The note's interval moves from the 5th–95th to the 10th–90th so the chart marks, the table heads and the prose all quote the same two numbers.
2 · Head to head
Two pickers over the possibility cloud's list — top-of-table players included, not just the bubble — defaulting to the pair either side of the automatic-bid cut. It walks the leader's weekend down the points curve and gives the finish the chaser needs plus the margin:
Deliberately a pair rather than a matrix: every combination of 52 players is 1,326 answers nobody reads.
Three things the margin column gets right. The two are in the same field and cannot share a place, so "1 ahead" is the tightest margin there is. "N behind is fine" appears when the caps have eaten the leader's result. And the out-of-reach threshold is read off the whole curve rather than the sparse ladder, so it says 4th or better rather than rounding to the nearest listed row. Against a lead the event cannot pay, the panel says so in a sentence instead of printing eleven dashes.
Honest about what it is not
Both panels are arithmetic on the published curve, not a simulation — they say what has to happen, not how likely it is, and the notes say so. They hold two things still: the rest of the season where it stands (exact with one event left, a ceiling before that, since anything banked elsewhere only lowers the bar), and the frozen cutline the row expander already runs on.
Also: a failing test, fixed
test_a_staged_mvp_roster_still_admits_gmc_performerswas already red onmainbefore this branch. It was the one test in its file lettingfields._waves_all_openreach the realnow(), and the MVP's last phase — GMC performance qualifiers — opens2026-09-21:So it asserted something true only between Sep 1 and Sep 21, passed all September, and went red on the 21st with nothing in the code having changed. Production is correct — GMC finished Sep 20, so a staged roster genuinely is the field now. The test gains a pinned date, the same way the two roster-staging tests beside it already freeze theirs; the phase table is still what is under test, so deleting the performance phase still fails it.
A neighbouring docstring that credited the waves for keeping a page-derived list open is corrected too — that is
stageddoing the work, and only a PDGA Live roster ever closes a field.Two findings logged under
notes/HARDENING.mditem 9: the clock-shift sweep that catches this class of bug, and the fact that the MVP's last phase is keyed to a hardcoded date rather than to GMC's schedule row being complete — a proxy that would come apart if GMC were ever delayed past it. Neither changes behaviour here.Verification
n/a(with the leverage grid's own wording) and head-to-head falls back to a sentence.python -m pytest tests -q→ 258 passed, and still 258 with the clock shifted +3 / +14 / +60 / +200 days, so nothing else in the suite is date-dependent.Touch-reachable throughout (
tipAttrs/probe), player names linked per the PDGA attribution rule,docs/CLAUDE.mdupdated from five panels to six, and a changelog entry added tohow-it-works.html.🤖 Generated with Claude Code
https://claude.ai/code/session_01JsBC4pyUqbxW9y5o9tAeRr