Skip to content

Add a "What's left" view: five reads of the remaining possibility space - #44

Merged
dgoodenough merged 2 commits into
mainfrom
claude/remaining-possibilities-viz-rmfd1c
Aug 17, 2026
Merged

Add a "What's left" view: five reads of the remaining possibility space#44
dgoodenough merged 2 commits into
mainfrom
claude/remaining-possibilities-viz-rmfd1c

Conversation

@dgoodenough

@dgoodenough dgoodenough commented Aug 17, 2026

Copy link
Copy Markdown
Owner

With four points events to go, the forecast table answers "will this player get in", one row at a time. The questions it can't answer are the shape ones: how wide is everyone's range, which positions are still contested, how far the chasers are from the line, which door they walk through, and where their season actually gets decided.

This adds a second top-level view (Forecast | What's left, beside the division toggle) with five panels for those. The table keeps the page it had; deep links still land on it.

Front-end only — docs/app.js, docs/index.html, docs/style.css. No pipeline change, no new exported fields.

The panels

Four are pure re-reads of fields the bundle already ships (hist, cutline, p_cut, p_mvp_qual, att):

1 · The possibility cloud. The row sparkline drawn for every contender at once, normalised per row exactly as sparkCell scales to each player's own max. Rows whose distribution is mostly the overflow bin are dropped — that bucket is "50th or worse", so such a row draws as one lone mark at the right edge and reads as a rendering fault rather than as data. Ships with a "Numbers" table twin so nothing is encoded in colour alone.

2 · How contested each position is. The same matrix read down its columns: effective candidates per finishing position (inverse Herfindahl). Stops one short of max_hist_rank, since the overflow column sums to far more than one player's worth of probability and would render as false chaos at the right edge. Today MPO position 1 has 1.0 effective candidates and #28 has 19.

3 · The cutline, and how far away you are. Points only go up, so a total today is a hard floor and the gap to the line is closeable. The axis reaches down to the chasers rather than just covering the cutline — the horizontal distance between the two is the chart. Annotates the nearest chaser and the furthest back with a real shot (p_cut ≥ 10%); the deepest tick on the rug is a sub-1% longshot 397 points adrift, whose gap is just noise.

4 · The ways in. p_champ decomposed into standings cut, MVP-performance bid, and the event-winner invite (the residual). Two semantic hues plus a structural grey for the invite, which is an entitlement rather than a competitive route. Surfaces cases the headline number hides — Paul McBeth at rank 75 is 11.6% via the standings and 40.9% via one weekend at the MVP Open.

The fifth re-runs the cutline replay held fixed at one event's result:

5 · Where the season actually gets decided. Per contender, per remaining event, the swing in auto-bid odds between a 90th-percentile week there and a 10th-percentile one. This is what makes the 2026 counting caps visible: a new result only counts for what it beats, so winning Worlds is worth the full 300 to a player with a thin major pool and 200 to one whose best two are already banked. An event a player isn't entered for comes back n/a rather than a leverage of zero dressed up as a real number — Lizotte and Evan Smith aren't in the Idlewild field, so one of the four remaining events literally cannot move their season.

Notes on the leverage panel

  • Computed one player per tick, ~1.2 s for a 24-row grid, cached per division. A generation token means a run abandoned mid-flight (reader switched divisions) drops its own partial cache instead of leaving a permanently half-empty grid.
  • replay()'s per-event pre-sampling is factored out as samplePoints() so the grid draws each event once and reuses it across nine conditional runs rather than redrawing per run.
  • It re-derives the model, so it was checked against the model: the counting pools reproduce every player's current season total to the cent, and the unconditioned estimator lands within ~1.5 points of the exported p_cut in both divisions.
  • Figures carry about a point of Monte Carlo noise, which the panel's own caption says.

Second commit: probClass's colours never rendered

probClass has always returned pos/pend/dim, and its comment has always promised "green = effectively in, yellow = live bubble". But tokens.css defines .pos/.pend only as .table-ledger td background fills, and every caller puts the class on the number inside the cell — a span or a b element. Only .dim, a plain global colour rule, ever did anything, so five probability columns in the forecast table have been rendering as flat ink.

Two lines of CSS colour the number instead of filling the cell. Filling would put five adjacent solid columns across the table, and a filled cell also swallows tr.expandable:hover's fill, which is the table's only cue that rows are clickable. Both hues clear 5.7:1 against the paper and against the hover fill, in dark, newsprint, and print (@media print redefines the tokens). The cell-fill form stays available and is still used where a single anchor column earns it.

Testing

  • Rendered against the live published bundle for both divisions; no console errors, no page-level horizontal overflow at 1160 / 760 / 400 px. Every chart scrolls inside its own container.
  • Leverage grid fills all 96 cells after an MPO → FPO → MPO round-trip, exercising the cache/generation guard.
  • Conditional-format classes verified by computed style rather than by eye: across the top 26 Cup cells, 20 resolve posrgb(79,214,136) and 6 resolve pendrgb(217,171,38); the 99% threshold lands where it should.
  • Existing Python suite unaffected (no Python touched).

Caveat carried into the copy

Playoff fields still assume every qualifier attends, so GMC and MVP Open leverage is an upper bound until signups open. Both the ways-in and leverage panels say so.

🤖 Generated with Claude Code

claude added 2 commits August 17, 2026 14:57
The forecast table answers "will this player get in", one row at a time. With
four points events to go, the questions it can't answer are the shape ones:
how wide is everyone's range, which positions are actually still contested,
how far the chasers are from the line, which door they walk through, and where
their season gets decided. This adds a second top-level view for those.

Four of the five panels are pure re-reads of fields the bundle already ships
(hist, cutline, p_cut, p_mvp_qual, att) — no pipeline change:

- Possibility cloud. The row sparkline drawn for every contender at once,
  normalised per row exactly as sparkCell scales to each player's own max.
  Rows whose distribution is mostly the overflow bin are dropped: that bucket
  is "50th or worse", so such a row draws as one lone mark at the right edge
  and reads as a rendering fault rather than as data. Ships with a table twin
  so nothing is encoded in colour alone.
- Contested positions. The same matrix read down its columns: effective
  candidates per finishing position (inverse Herfindahl). Stops one short of
  max_hist_rank, since the overflow column sums to far more than one player's
  worth of probability and would render as false chaos.
- The cutline. Points only go up, so a total today is a hard floor and the gap
  to the line is closeable. The axis reaches down to the chasers rather than
  just covering the cutline — the horizontal distance between the two is the
  chart. Annotates the nearest chaser and the furthest back with a real shot;
  the deepest tick on the rug is a sub-1% longshot whose gap is just noise.
- The ways in. p_champ decomposed into standings cut, MVP-performance bid, and
  the event-winner invite (the residual). Two semantic hues plus a structural
  grey for the invite, which is an entitlement rather than a competitive route.

The fifth re-runs the cutline replay held fixed at one event's result:

- Leverage. Per contender, per remaining event, the swing in auto-bid odds
  between a 90th-percentile week there and a 10th-percentile one. This is what
  makes the 2026 counting caps visible: a new result only counts for what it
  beats, so winning Worlds is worth the full 300 to a player with a thin major
  pool and 200 to one whose best two are already banked — and an event a player
  isn't entered for comes back n/a rather than a leverage of zero dressed up as
  a real number.

  Computed one player per tick (~1.2s for a 24-row grid) and cached per
  division, with a generation token so a run abandoned mid-flight drops its own
  partial cache instead of leaving a permanently half-empty grid. Verified
  against the model it re-derives: the counting pools reproduce every player's
  current season total exactly, and the unconditioned estimator lands within
  ~1.5 points of the exported p_cut in both divisions.

replay()'s per-event pre-sampling is factored out as samplePoints() so the
leverage grid can draw an event once and reuse it across nine conditional runs
rather than redrawing per run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRdyv62j1tvtkA9NzZhSGx
probClass has always returned pos/pend/dim, and its comment has always
promised "green = effectively in, yellow = live bubble". But tokens.css
defines .pos/.pend only as `.table-ledger td` background fills, and every
caller puts the class on the number inside the cell — a <span> or a <b> — so
only .dim, which is a plain global colour rule, ever did anything. Five
probability columns in the forecast table have been rendering as flat ink.

Colour the number rather than filling the cell. Filling would put five
adjacent solid columns across the table, and a filled cell also swallows
tr.expandable:hover's fill, which is the table's main "click me" affordance.
Both hues clear 5.7:1 against the paper and against the hover fill, in dark,
newsprint, and print.

The cell-fill form stays available and is still used where a single anchor
column earns it — the ways-in and possibility-cloud tables put the class on
the <td>. probClass's comment now says which application does which.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRdyv62j1tvtkA9NzZhSGx
@dgoodenough
dgoodenough merged commit 8fcb6dc into main Aug 17, 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