Skip to content

Price the cutline gap as a finish, and add a head-to-head panel - #75

Merged
dgoodenough merged 2 commits into
mainfrom
claude/tender-dijkstra-fvvlf9
Sep 22, 2026
Merged

dgoodenough merged 2 commits into
mainfrom
claude/tender-dijkstra-fvvlf9

Conversation

@dgoodenough

@dgoodenough dgoodenough commented Sep 22, 2026

Copy link
Copy Markdown
Owner

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):

# Player Points Short Low 777 Median 798 High 826
25 Marwede 794.42 4 clear any 46th
26 Ulibarri 761.33 37 68th 43rd 27th
36 Smith 661 137 12th 7th 4th
41 Welck 596.2 202 3rd 2nd 1st

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. poolSum and countingPools move 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:

Albert Tamm leads Kyle Klein by 3.37 points. A win from Tamm puts it out of reach.

  Tamm finishes   Ends on   Klein needs   Margin
  1st             980.1     —             —
  5th             891.77    4th           1 ahead
  30th            790.1     27th          3 ahead
  76th            743.43    64th          12 ahead

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_performers was already red on main before this branch. It was the one test in its file letting fields._waves_all_open reach the real now(), and the MVP's last phase — GMC performance qualifiers — opens 2026-09-21:

2026-09-02  gmc open: True | mvp open: False   <- the test passed here
2026-09-22  gmc open: True | mvp open: True    <- and fails here

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 staged doing the work, and only a PDGA Live roster ever closes a field.

Two findings logged under notes/HARDENING.md item 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

  • Rendered against the published MPO and FPO bundles at 390 / 760 / 1180px: six panels, no page errors, no horizontal overflow at any width.
  • Every tab walked and returned to, both divisions, to confirm nothing else broke.
  • Not-in-the-field paths exercised by withdrawing contenders from the field at runtime — the needs table reads n/a (with the leverage grid's own wording) and head-to-head falls back to a sentence.
  • Tooltips and both dropdowns driven headlessly; the same-player guard and the unbridgeable-lead branch checked.
  • python -m pytest tests -q258 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.md updated from five panels to six, and a changelog entry added to how-it-works.html.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JsBC4pyUqbxW9y5o9tAeRr

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
@dgoodenough
dgoodenough merged commit f55a6de into main Sep 22, 2026
1 check passed
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.

2 participants